Agent-First PSQL v0.10.0: The Window and the Agent Cannot Disagree
Five panels that open a window onto PostgreSQL — four you read, one you approve. Each runs the exact SQL its inspect sibling runs, an approved statement runs the ordinary path, and only an approval runs anything: a closed window, a refusal and a lapsed credential are the same answer.
An agent can tell you a table has forty-one columns and three foreign keys. What it cannot do in a sentence is let you see the shape, and shape is usually what you asked about — which columns are nullable, what the indexes actually cover, whether that connection count is climbing.
The same gap exists in the other direction. When an agent is about to run a statement that cannot be taken back, “shall I run this?” in a chat window is a poor place for you to read a statement carefully.
v0.10.0 adds afpsql ui: five panels, four you read and one you approve.
The panel is not a second source of truth
The obvious way to build a schema viewer is to write a schema viewer. That produces two programs that answer the same question — the one the agent calls and the one you look at — and they drift, quietly, and the day they disagree is the day you needed them not to.
So every view panel runs the exact SQL its afpsql inspect sibling runs.
ui schema is inspect schema in a window. An approved statement goes through
handler::execute_query exactly as afpsql --sql would: the same connection,
the same transport, the same permission resolution, the same readonly policy.
There is no second path to be wrong on.
ui schema, ui table, ui indexes and ui connections are watch
sessions. Nothing on the page submits, the value type is (), and the session
ends when you close the window. Closing it means you are done looking. It does
not mean you approved anything, and nothing in the result pretends otherwise.
ui connections is the one that reloads itself, because it is a view of
something that moves — and it does that without shipping a line of JavaScript.
script-src is absent from the panel’s CSP, so default-src 'none' governs it.
Only an approval runs anything
ui plan --sql '...' is a decide session: it shows one statement, and runs
it only if you approve.
The interesting part is what counts as “no”. Closing the window is a refusal.
Pressing refuse is a refusal. Letting the credential lapse is a refusal. All
three produce the same terminal event — result.code: "ui_plan_refused" with
executed: false — because the alternative is a system where walking away from
your desk eventually means yes. Absence of an answer is never consent, and that
judgement is made in exactly one place in the code rather than at each of the
three call sites that could have gotten it wrong.
The statement is fixed when the command is invoked. Editing a --sql-file after
the window opens changes nothing, and there is no way to amend what you are
looking at — the agent has to refuse and ask again with a new statement. What
you approved is what runs.
Refusing before the window, not after the approval
Two refusals were moved earlier, which is a smaller change than it sounds and a more important one.
afpsql-readonly refuses writes everywhere. It would have refused an approved
write too — at execution, after a window had opened and a person had read a
statement and pressed approve. That is a working system that wastes your
attention and teaches you that approval does not mean much. The refusal now
happens before the window opens: if the statement was never going to run, you
are never asked about it.
The same applies to an administrator-locked profile. Opening a window spawns a browser and writes a profile directory, and a locked profile withholds exactly those host capabilities — so it is refused up front rather than part-way through launching something it cannot finish.
What a panel computes is ours; what it looks like is not
None of these pages is written in Rust. Each is a MiniJinja template rendered against a typed document, and you can replace any of them through AFUI’s frontend overlay — trusted by content fingerprint, so an edit revokes trust until you look again.
That makes the documents a contract rather than an implementation detail. The panel decides what to compute; the template decides what it looks like; and the boundary between those is a thing you can hold onto, because replacing the second cannot change the first.
Also in this release
afpsql inspect connections [--all] returns one row per server backend with its
state, wait event and ages, alongside the max_connections the count should be
read against — a count without its limit is not an answer. --all includes the
backends PostgreSQL runs for itself, which that limit does not govern.
Connection secrets now use the shared value-source grammar from
agent-first-data instead of a private implementation of the same rules, which
deleted a parallel copy rather than merely deduplicating one. agent-first-data
moves to 0.32.1 and agent-first-ui to 0.3.1.
A note for agents
ui is for when a person asked to look. It returns no rows — only that the
window closed — so an agent that uses it to read data has learned nothing and
spent someone’s attention doing it. Read with inspect; show with ui.