Agent Skill

Use this skill when an agent needs PostgreSQL access that is structured, read-only by default, safe for scripts, or reachable only across SSH/container boundaries. Prefer afpsql over parsing human psql tables, SSHing in to run psql, or docker exec/kubectl exec with human output.

For flag-level detail, run afpsql --help, or afpsql <command> --help for one command. This skill covers behavior, decisions, and recovery only.

Calling Convention

afpsql is compiled from a closed registry: an invocation runs only when it matches exactly one registered shape.

Core Rules

Discovering Schema

Prefer afpsql inspect over hand-writing information_schema / pg_catalog queries:

Showing Something to a Person

afpsql ui schema, afpsql ui table, afpsql ui indexes, and afpsql ui connections open the same data a person can read in a window instead of returning it. Reach for one only when a person asked to look, or when you have already read the data and they need to see its shape to answer you. Never use ui to read data yourself: the result carries no rows, only that the panel closed.

These are watch sessions, so the call blocks until the panel closes. Treat that closure as “they are done looking”, never as approval of anything. Delivery defaults to a window on this machine; when there is no browser here, or the person is not at this machine, pass --mode session instead — afpsql only registers the panel, and the person reaches it through afui session serve. A window that cannot open on this machine is that environment problem; report it and retry with --mode session rather than retrying window unchanged, or fall back to the matching inspect command if nobody needs the window itself. An administrator-locked readonly profile is a different case: it refuses every panel outright, under either delivery, before anything connects — not an environment problem to retry past, so read the same data with inspect instead.

ui connections is the one panel meant to outlive a single snapshot: while at least one visible page is checking in, it pushes fresh retained snapshots over its typed AFUI runtime. A registered session with no page open keeps the last snapshot and stops querying PostgreSQL within 30 seconds. Open it, report that it is open, and go back to work rather than waiting on it. Run it as a long-lived process when you have anything else to do, and leave the interval alone unless the person asked — while observed, it is a repeated query against a server other people are using.

That same panel lets the person enter SQL for plain EXPLAIN (FORMAT JSON). The request travels back over the live channel and the plan appears in the panel; it does not reload the page, run ANALYZE, or return plan rows on the agent’s stdout. The ordinary read permission and PostgreSQL read-only transaction still apply. Use the CLI --explain analyze path only when the user explicitly asks to execute and measure the statement, with the normal write permission when applicable.

Asking a Person to Approve a Statement

afpsql ui plan --sql '...' [--param N=V] [--permission write] shows one statement to a person and runs it only if they approve. Use it when a write is consequential enough that a person should see it first, not as a substitute for knowing what your own statement does.

For query plans, add --explain plan (EXPLAIN (FORMAT JSON)) or --explain analyze (also runs the statement; writes still need write permission). The plan JSON arrives in a normal kind:"result" event under result.rows.

Validating Before Executing

afpsql --dry-run --sql '...' --param 1=... [--param 2=...] opens a connection, runs PREPARE inside a transaction that is rolled back, and emits a kind:"result" event whose result.code is dry_run, with the inferred param_types, output columns, and any prepare error. Use this to catch placeholder mismatches, missing tables, and type confusion before letting a query actually run.

Branching on Failures

Row Encoding Fidelity

Rows are normally encoded by PostgreSQL itself, so numeric, timestamptz, uuid, interval and friends keep their exact server representation. A few statements cannot be encoded that way — utility statements such as EXPLAIN and SHOW, and any SQL whose text prevents the wrapper from being built — and those fall back to a narrower client-side decoder that only handles booleans, integers, floats, JSON, bytea, and text-like types.

The fallback is announced by the query.row_encoding_degraded log event; ask for it with --log query.row_encoding_degraded whenever exact value fidelity matters. A statement whose columns the narrow decoder cannot represent fails loudly instead of returning an approximation, so a kind:"result" is always trustworthy — the log only tells you which decoder produced it.

Results that Don’t Fit Inline

If a kind:"result" event carries result.truncated:true, the underlying statement still ran in full, but result.rows is only a prefix (see result.truncated_at_rows / result.truncated_at_bytes). For UPDATE ... RETURNING this means the writes happened; only the RETURNING projection was capped. Either narrow the query (WHERE / LIMIT) or rerun with --stream-rows to receive the full set in batches.

Multi-Statement Atomicity (Pipe Mode)

Each query is its own transaction by default. For atomic multi-statement work, open an explicit transaction:

{"code":"begin","id":"b","permission":"write"}
{"code":"query","id":"q1","sql":"insert into orders ...","options":{"permission":"write"}}
{"code":"query","id":"q2","sql":"update inventory ...","options":{"permission":"write"}}
{"code":"commit","id":"c"}

Non-Obvious Behaviors

Setup Checklist

Only run setup when asked to prepare or repair the machine; do not run it before every query.

afpsql --version || brew install agentfirstkit/tap/afpsql
cargo install agent-first-psql  # fallback when Homebrew is unavailable
afpsql skill install            # personal Claude/Codex skill
afpsql psql install             # optional: psql-compatible wrapper

Troubleshooting