Agent-First Data v0.16: One Protocol, Builders Everywhere
v0.16 is the convergence release: a single builders-only API, a finite protocol-v1 event stream with semantic emitters, a machine-readable suffix registry shipped offline, and a sub-cent currency suffix for metered pricing.
For a year AFDATA grew one convention at a time. A suffix here, a redaction rule
there, a logging format, a CLI output mode. Each addition was right on its own,
but the surface had started to sprawl: two ways to build a result, a
_with_options twin for half the functions, output rules that lived in prose an
agent could not check.
v0.16 is the release that pays that down. It is a breaking release — AFDATA is still pre-1.0 and carries no compatibility layer — and everything below lands the same way in Rust, Go, Python, and TypeScript.
One way to build an event
The legacy constructors are gone. build_json_ok, build_json_error, and every
_with_options twin are removed. What remains is a single builders-first path:
json_result(payload).trace(...).build()
json_error(code, message).trace(...).build()
json_progress(fraction, message).build()
json_log(level, message).field(...).build()
One entry point per event kind, optional context added by chaining, one
.build() at the end. The functions that used to fork into a plain and an
_with_options form now take one shape with optional named parameters. The
surface is smaller, and there is no longer a “which of these two should I call”
decision to get wrong.
The protocol is finite and typed
AFDATA has always treated stdout as the protocol boundary. v0.16 makes the shape of that boundary explicit. A tool emits exactly this:
(log | progress)* → exactly one (result | error)
Zero or more logs and progress updates, then one terminal event and nothing
after it. Errors always carry a code and a message; any event may carry an
optional trace. In YAML and plain output the events are separated by explicit
--- document boundaries, so a human scanning the stream sees the same structure
the agent parses.
That lifecycle is now enforced at the emitter instead of trusted to the caller. The stateful emitter exposes semantic methods:
emitter.emit_log(...)
emitter.emit_progress(...)
emitter.emit_result(...) # terminal
emitter.emit_error(...) # terminal
Each method validates the envelope, applies redaction and the selected output format, writes the line, and tracks the finite-stream state — so a second terminal event is a bug the library catches, not a malformed stream an agent has to defend against.
Help and version participate too. With --output json|yaml|plain, --help and
--version exit early as structured protocol events rather than free text, so an
agent can discover a tool’s contract without a separate parsing path. Structured
logging flows through the same envelopes: log lines are log events, and the
startup diagnostic captures resolved config, argv, and environment — all with
field-name redaction already applied.
The convention is machine-readable and offline
The suffix meanings used to live in documentation. Now they live in
registry.json: an authoritative, machine-readable table of every suffix with
its JSON type, formatting rule, redaction mode, and integer bounds. Alongside it
ships protocol-v1.schema.json, a JSON Schema for the four event kinds.
Both files — plus the standard agent-first-data Skill — are bundled inside each
language package. A tool can validate a suffix or a protocol event against the
canonical rules with no network and no documentation scraping. The new
skill-admin CLI installs and serves that Skill from the same offline assets.
A suffix for sub-cent money
Cents are too coarse for how agents actually spend money now. A single LLM call
priced per million tokens, a metered API charging fractional cents per
request — round those to _usd_cents and the rounding error is the whole cost.
v0.16 adds _{code}_micro: integer millionths of a major currency unit, the
fiat analog of _msats.
cost_usd_micro: 170000 → 0.170000 USD
Same suffix grammar, same four languages, formatting and bounds pinned by shared fixtures.
Redaction keeps closing more holes
Secrets leak from the edges, so v0.16 tightened three of them:
- Argv. A raw
--api-key-secret sk-live-…on the command line is redacted out of the startup diagnostic and logs. - URLs.
_urlfields scrub userinfo passwords and secret-named query parameters with surgical, byte-for-byte replacement — no re-encoding, no normalization of the rest of the URL. - Help defaults. A default value shown for a
_secretflag is redacted in the help text itself.
The boundary has not moved: redaction is still field-name based, still fails closed, and still refuses to scan free-form prose for arbitrary secrets. There are just fewer places a secret can slip through on the way out.
Upgrading
v0.16 breaks source compatibility on purpose. Replace build_json_* calls with
the matching builder, drop _with_options suffixes in favor of the optional
parameters, and route CLI emission through the semantic emit_* methods. The
registry and schema are in your package already; point your validators at them
instead of at the docs.
The convention did not change. The way you reach for it got a lot smaller.