Agent-First Data v0.16: One Protocol, Builders Everywhere

by Agent-First Kit Contributors

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:

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.