Agent-First HTTP v0.12.0: Two Ceremonies Before the Command
Reaching a local host cost two steps before the actual command: read the token out of the container by hand, then push it through the shell as an environment variable. This release makes discovery answer for every command, gives --token-secret a typed source grammar, and turns the takeover panel into a window whose closing is the signal.
Here is what talking to a local host used to look like:
takeover_secret="$(docker exec afhttp-host cat /data/afhttp/host-token)"
AFHTTP_TOKEN_SECRET="$takeover_secret" afhttp cdp Runtime.evaluate \
--endpoint-url ws://127.0.0.1:9222 --tab 0E10... --params '{...}'
And here is the same command now:
afhttp cdp Runtime.evaluate --tab 0E10... --params '{...}'
Everything removed there was ceremony. The endpoint was the standard local one. The token was sitting in the host’s own data volume. Both were discoverable, and neither was being discovered — so every invocation carried a secret through the shell, into the environment, and usually into the shell history, to tell afhttp something afhttp could have found out.
Discovery already existed. It answered for one command.
fetch --takeover had always found the local host on its own. Nothing else had,
which is the kind of asymmetry that survives because each command looks
reasonable in isolation.
Now every command that needs a host discovers one. With no --endpoint-url, the
standard local host container is inspected read-only, its token read from its own
data volume, and /health verified before anything is sent. Two limits keep that
from being surprising: discovery never starts or recreates a container — it
inspects, and fails if there is nothing to inspect — and inline stays the
default, so a plain fetch still needs no host at all. An explicit
--endpoint-url is never second-guessed.
fetch --profile without an endpoint used to be an error whose entire content
was “pass an endpoint.” It discovers now, because that error was asking the
caller to supply the thing the command could work out.
A token has a source, not just a value
The other half of the ceremony was AFHTTP_TOKEN_SECRET=…. An environment
variable is one answer to “where does this credential come from”, and hardcoding
one answer means every other one has to route through it.
--token-secret now takes a typed source:
env:NAME an environment variable
file:PATH#DOT_PATH an address inside a JSON/TOML/YAML/dotenv/INI file
container:NAME the host token inside a named volume
literal:TOKEN the value itself, when you mean it
container:NAME is the one that has to exist here rather than anywhere else: the
host token lives in a named volume, invisible to every path on this machine, so
no file source can address it. The rest is AFDATA’s value-source grammar, and
adopting it deleted a private copy — 356 lines became 110, and what is left is
only the part no shared crate could have provided.
Three things got better by moving the grammar rather than merely deduplicating
it. The registry now knows which sources --token-secret accepts, so a
malformed or unaccepted one is refused as cli_invalid_argument_value at exit 2
while argv resolves, beside every other usage error, instead of failing later
during projection — and --token-secret prompt answers with the list of what it
does accept. The syntax is rendered from the declaration, so one sentence stops
being duplicated across every row of the CLI reference that mentions the flag.
And a resolved token is an AFDATA SecretString the whole way down: the bytes
are exposed at the two places that need them, and every other Debug on the
path renders ***.
Sources are classified while argv is projected and read only at connect time. A
rejected invocation therefore never opens a file and never spawns a container
runtime, and no error message can echo what it read. container install and
container status emit a runnable client command now, instead of an
AFHTTP_TOKEN_SECRET=<host-token> template for you to paste.
The panel is a window, and closing it means something
Taking over a session had a worse problem than ceremony: the agent had no way to know when you were done. It guessed.
afhttp ui takeover opens the takeover panel as an Agent-First UI window, and
the window closing is the signal. The panel’s short-lived credential is now an
AFUI access token rather than a private handoff store — which was not a tidying
exercise. That store compared credentials with a HashMap lookup where the
long-lived token had always used a constant-time compare, it had no revoke path
at all, and its cleanup ran only when a new credential was minted.
Also in this release
agent-first-data moves to 0.32.1 and agent-first-ui to 0.3.1. Five new
regression entries pin the properties above: no source content in errors, no
reads during projection, a plain fetch discovering nothing, an explicit endpoint
never second-guessed, and Debug never printing a literal token.
Upgrading
Client commands keep working. A bare --token-secret TOKEN is still accepted —
a value that names no source is the value itself — and AFHTTP_TOKEN_SECRET is
still read when the flag is absent, now as one source among several rather than
the only alternative to typing the secret on argv. The precedence is
--token-secret, then that variable, then the discovered host’s own token, so
the common case is passing neither.
The break is afhttp host, whose --token-secret names the token the host will
require from callers rather than one it presents. That side reads no
environment fallback: an invocation that relied on AFHTTP_TOKEN_SECRET being
set has to say --token-secret env:AFHTTP_TOKEN_SECRET, which is the same
variable, read because it was named rather than because it happened to be in the
environment. A server deciding what it accepts is not a place to inherit a
credential silently.