Agent-First PSQL v0.6.2: Container Transport Family and Self-Describing Sessions
v0.6.2 generalizes the docker transport into a container transport family (podman, nerdctl, compose, kubectl) with structured scope flags and SSH chaining, adds a session_info pipe request so agents can introspect their session's transport, permission default, and limits instead of probing with failing queries, and surfaces two new log events for implicit behaviors that previously had to be inferred.
Agent-First PSQL v0.6.2 builds on v0.6’s SSH transport and v0.6.1’s SQLSTATE-on-connect work along two threads: the container boundary becomes a first-class transport family, and pipe sessions become self-describing for the agent that uses them.
Container transport, generalized
v0.6 had docker transport. v0.6.2 generalizes that into a container transport family that covers the runtimes agents actually encounter:
--container-driver docker (default)
--container-driver podman
--container-driver nerdctl
--container-driver compose
--container-driver kubectl
For Docker contexts, kubectl namespaces, Compose files, and multi-container Kubernetes pods, the scope is set via named flags rather than passed through as raw driver argv:
afpsql --container app-pod --container-driver kubectl \
--container-namespace prod --container-pod-container postgres \
--host 127.0.0.1 --port 5432 \
--user app --dbname appdb --password-secret-env PGPASSWORD \
--sql 'select 1'
The point of the named flags is that the agent never builds a runtime
command line by string concatenation. afpsql is the one that emits
kubectl exec app-pod -c postgres -i -- .... An agent that runs raw
kubectl exec ... -- psql is back to parsing human output and shell
state; the structured contract goes through afpsql.
The permission family is container-read (default) / container-write,
parallel to the existing read/write and ssh-read/ssh-write
families. A container session that requests --permission write is
rejected before execution with a hint to use container-write, the
same way --permission write --ssh ... was already rejected in v0.6.
Container over SSH: one local chain
For containers on a remote SSH host, --ssh and --container compose
into one local afpsql transport chain. The agent stays local; afpsql
runs the container exec command on the SSH host and bridges from
inside the container:
afpsql --ssh root@server --container app-container \
--container-driver docker \
--host postgres --port 5432 \
--user app --dbname appdb --password-secret-env PGPASSWORD \
--sql 'select 1'
This is one chain, not “SSH in, then run psql.” The permission family
stays container (container-read / container-write) because the
database boundary is still the container, not the SSH host. Enable
--log transport to see the resolved chain on each new session, e.g.
ssh:root@server -> docker exec app-container -> tcp postgres:5432.
Container bridge prerequisites are the same regardless of driver: the
target container needs sh plus one of python3, python, or perl.
The container does not need afpsql or psql installed.
session_info: introspect, don’t probe
In pipe mode, the agent now asks a session what it is, rather than finding out by sending a query that fails:
{"code":"session_info","session":"work"}
{
"code": "session_info",
"session": "work",
"transport_kind": "container",
"permission_default": "container-read",
"stream_rows_default": false,
"batch_rows": 1000,
"batch_bytes": 262144,
"inline_max_rows": 1000,
"inline_max_bytes": 1048576,
"statement_timeout_ms": 30000,
"lock_timeout_ms": 5000,
"trace": {"duration_ms": 0}
}
That is the agent’s “what am I connected to” question, answered
without spending a query. transport_kind and permission_default
tell the agent which permission family the next write needs;
inline_max_rows / inline_max_bytes tell it whether the upcoming
result will be inline or streamed; the timeouts tell it which queries
are about to be cut off.
The skill’s standing guidance in pipe mode is now: send session_info
once before running queries, rather than spending a turn on
afpsql --help or sending probe queries to learn the same facts.
Implicit behavior, made visible
Two failure modes that previously had to be inferred from outcomes now emit dedicated log events:
--log mode mode.permission_default_changed
--log connect connect.libpq_env_fallback
mode.permission_default_changed fires whenever --mode psql
bypasses the native read-only default. psql-mode keeps psql’s writable
default for non-interactive script compatibility, but the agent that
translated a script to afpsql still needs to know it just dropped the
write boundary.
connect.libpq_env_fallback lists which libpq PG* environment
variables (PGHOST, PGPORT, PGUSER, PGDATABASE, PGPASSWORD,
PGSSLMODE) filled connection fields the agent did not pass via flags
or secrets. An agent that thought it was connecting to 127.0.0.1
while a stale PGHOST in the parent shell pointed elsewhere now sees
that on the log channel before the query runs, not after the wrong
row comes back.
Container bridge: hardened handshake
The container exec bridge that v0.6 introduced now uses a
per-connection 16-byte hex nonce on its AFPSQL_BRIDGE_OK ready
banner. A container init script that happens to print the literal
string AFPSQL_BRIDGE_OK to stdout can no longer trip the handshake.
Captured bridge stderr placed on ConnectError.hint is also
sanitized — control characters except \n / \t mapped to space,
hint capped at 512 bytes — so ANSI sequences and embedded NULs from a
noisy container cannot reach structured logs.
Hints, everywhere
Every Output::Error path now carries an actionable hint. The
hint-on-every-error contract was already true for connection,
permission, and validation paths in v0.6 / v0.6.1; v0.6.2 backfills
the remaining ones (cancellation, internal, duplicate id, finished,
missing id, invalid params). An agent that branches on error_code
always has a sentence telling it what to try next.
Adoption
brew install agentfirstkit/tap/afpsql # macOS / Linux
cargo install agent-first-psql # any platform
afpsql skill install
afpsql skill status
For the embedded skill and SQLSTATE-on-connect work this release builds on, see the v0.6.1 release post.