Agent-First Data
A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.
Ask your agent: "Apply the Agent-First Data convention across my project's fields, config, and logs."
The problem: data doesn't say what it means
An agent reads {"timeout": 5000} from a tool. Seconds or milliseconds? It guesses — and a 5-second timeout silently becomes 83 minutes. The same trap is everywhere: {"price": 1200} gets charged as $1,200 instead of $12.00; {"created": 1738886400} is treated as an ID instead of a date.
It reads {"api_key": "sk-live-abc123"} and writes that line straight into a log file, because nothing marked the value as a secret.
Then it moves to the next tool, which calls the same value elapsed instead of duration — so what the agent learned about one tool tells it nothing about the next.
None of this is carelessness. The data never says what it means, so the meaning has to live somewhere else — documentation, a schema, a prompt. That copy goes stale, gets lost, or was never written.
What it does: put the meaning into the field name
Agent-First Data puts the meaning into the field name itself. Call the field timeout_ms and there is nothing left to guess — the name says milliseconds. Call it api_key_secret and any AFDATA output boundary that follows the convention hides it automatically.
It is a convention, not a framework — a small set of name endings, plus a tiny library in four languages that reads and formats them.
- Names carry meaning. Endings like
_ms,_bytes,_secret,_usd_cents, and_percentput units and intent directly into the field name. - One set of data, three ways to show it. The same fields render as JSON or YAML — both keep original keys and types, for machines — or as a single human log line with units formatted for scanning. Secrets are removed in every form.
- Structured secrets are redacted. Anything ending in
_secretis hidden when a structured value passes through an AFDATA redactor or renderer. A_urlfield keeps its address but scrubs the userinfo password and secret-named query parameters. Legacy names likeapi_keycan be protected by passing an explicit secret-name list. - Logging agents can read. Structured logs that follow the same rules, with request-scoped fields.
- One contract in four languages. Rust, Go, Python, and TypeScript share wire behavior and fixtures while keeping idiomatic native API shapes.
A quick look
One record — a log event with a timeout, an API key, and a database URL — rendered three ways. Nothing is configured; the field names carry everything.
JSON and YAML keep original keys and types (structure-preserving) and only redact secrets:
---
kind: "log"
log:
args:
api_key_secret: "***"
timeout_s: 30
db_url: "postgres://user:***@db/app?token_secret=***"
event: "startup"
trace:
duration_ms: 1280
Plain is the one human renderer — it strips unit suffixes and formats values for scanning:
kind=log log.args.api_key=*** log.args.timeout=30s log.db_url="postgres://user:***@db/app?token_secret=***" log.event=startup trace.duration=1.28s
Supported suffixes
| Category | Suffixes |
|---|---|
| Duration | _ns, _us, _ms, _s, _minutes, _hours, _days |
| Timestamps | _epoch_ns, _epoch_ms, _epoch_s, _rfc3339 |
| Size | _bytes (integer everywhere — config and output alike) |
| Currency | _msats, _sats, _usd_cents, _eur_cents, _jpy, _{code}_cents, _{code}_micro (code is 3–4 ASCII letters) |
| Strict strings | _bcp47, _utc_offset, _rfc3339_date, _rfc3339_time |
| Other | _percent, _secret, _url |
JSON and YAML keep suffixes and raw values; Plain strips duration/size/currency/timestamp suffixes after formatting the value, and never strips _url/_bcp47/_utc_offset/_rfc3339_date/_rfc3339_time.
Redaction boundary
AFDATA redaction is intentionally field-name based:
_secret/_SECRETredacts the whole value or subtree to***.- Legacy names such as
api_keyare redacted only when the caller passes an explicitsecret_nameslist; matching is exact field-name equality. _urlfields scrub the userinfo password and query parameters whose names end in_secretor appear insecret_names; broad names such asapi_key,token, orpasswordare not hidden by default.- Free-form strings are not scanned for arbitrary secrets. If a secret URL is embedded in prose, redact the URL first with
redact_url_secrets.
The suffix protects structured fields only after they pass through an AFDATA
redactor or renderer. It cannot remove a live secret from process argv, shell
history, /proc, a parent process, or third-party logs. Avoid putting secrets
in argv; if a tool records its invocation, pass argv through redact_argv
before logging it.
There are no named redaction profiles. Use the default policy (All), an explicit secret_names list, or the documented scoped policies (TraceOnly, Off) for deliberate exceptions.
Reading and editing config documents
Beyond emitting AFDATA, the library and afdata CLI read and safely edit structured documents — JSON, TOML, YAML, dotenv, and INI — by dot-path:
host= # raw scalar, for shell substitution
Use get when the next step must preserve JSON types and structure; value
deliberately turns one scalar into raw shell bytes. set creates missing object
parents along the requested dot-path, but refuses to traverse an existing
scalar or incompatible container.
Edits are source-preserving and atomic: comments, key order, and formatting
survive; a write is read back before it lands, so an edit that would leave a
file its own parser rejects fails instead of succeeding; and a failed write
leaves the original untouched. Values are never guessed — a bare value is
always a string, and an exact type is asked for, not inferred. A name marks its
whole subtree, so a _secret node stays redacted however you address it, and
revealing it is an auditable opt-in. Errors carry stable codes and never quote
the document, because an error event is the thing an agent logs.
Flags, error codes, and exit codes are in docs/cli.md; the
Rust library is agent_first_data::document (Document / DocumentFile).
Token-efficient CLI discovery
Help is a normal result, not an exception to the output contract, and it answers
in one round trip. afdata --help returns a protocol-v1 result whose help-v2
payload carries every legal shape of the command, each complete. afdata set --help, for example, shows the distinct set-value, set-null, and
set-secret shapes instead of an argument catalog that leaves an agent to solve
conflicts. There is no second level to ask for: what it could omit — the
optional arguments — would be registered but undiscoverable to a caller that
stopped at the first. --output plain renders the same catalog for humans, and
--docs renders the whole registry as Markdown. One cli-spec-v1 registry
generates argv parsing, typed invocations, combination validation, output plans,
help, and that reference. Contracts:
cli-spec-v1 and
cli-help-v2.
Writing AFDATA-style Bash scripts
The CLI embeds a sourceable Bash 3.2+ authoring kit:
#!/usr/bin/env bash
_AFDATA_BASH_SOURCE=""
if [; then
else
fi
The helpers handle long-form argument parsing, --help, AFDATA output flags,
raw config reads, and structured log/result/error events. afdata_run
keeps child stdin/stdout/stderr and TTY interaction untouched; only the Bash
script's own lifecycle is structured, with a terminal error on child failure.
Use afdata_call when an AFDATA Bash parent invokes an AFDATA Bash child and
must remain the sole owner of the final result. See the complete
Bash authoring kit guide.
Where to use it: CLI flags, config files, logs, and API responses
- Building a CLI tool an agent will call — your output is understood correctly the first time, with no extra schema to ship. Results land on
stdoutand errors onstderrby default, so a shell capture or pipe never mistakes a failure for data; a single--output-to stdoutcollapses everything onto one stream when a consumer would rather branch onkind. - Writing a config file — keys like
timeout_sordb_password_secretmake settings self-explanatory to whoever edits them, and secrets stay hidden when the config is printed back. - Adding logs to a service — the same lines stay readable for a person and parseable for an agent.
- Designing an API response or event payload — units and sensitivity travel with the data, across every boundary it crosses.
- Auditing for leaked secrets — one naming rule (
_secret) makes redaction automatic instead of case-by-case.
One shared contract, four languages
The shared core surface ships in Rust, Go, Python, and TypeScript (each in its own casing):
- Protocol builders
json_result/json_error/json_progress/json_log→.build()→ an event; readerdecode_protocol_event(text)→ a typed decoded event. - Output
render(value, format, options)— the single value × format × options → string entry point. - Redaction
redacted_value/redact_url_secretsfor paths that bypassrender. - CLI primitives
cli_parse_output,cli_parse_log_filters,build_cli_error,build_cli_version, and theCliEmitter.
The Rust crate is the reference implementation of CliSpec, closed-world
combination resolution, version, and help-v2; the whole CLI surface sits behind
the default-on cli feature. Argv is only ever parsed through a registry —
there is no raw pre-parser. Skill admin, stream redirection, and tracing
initialization are also Rust-only. The shared surface is
enumerated in spec/api-surface.json; other SDK
compilers consume the same serialized CLI spec and fixtures as they migrate.
Adopt it: hand the convention to your coding agent
Agent-First Data is a convention, not a dependency you wire in by hand — and adopting a convention is exactly the kind of work you now hand to an agent. There's even an Agent Skill for exactly that — the convention in a form an agent reads and applies directly. Paste this to your coding agent:
Learn the Agent-First Data convention: read https://agentfirstkit.com/agent-first-data/docs/specification and https://agentfirstkit.com/agent-first-data/docs/agent-skill. Then look at the codebase we're working in and tell me whether adopting the convention would help it — and if so, how: which fields and config keys to rename, and where the output and logging helpers fit.
Install the Libraries
Install the CLI
The afdata CLI provides the same formatting, redaction, and protocol-event
helpers from any shell, with no toolchain required:
# prebuilt binary
&&
# or from crates.io
Prebuilt archives are also available from GitHub Releases.
Validate an Agent Skill
afdata skill validate checks a SKILL.md against the official metadata
constraints with a strict YAML parser. Passing a directory also verifies that
its name matches the front-matter name. Use afdata skill install, status,
and uninstall to manage the bundled skill.
Docs
- Specification — the full convention: every suffix, output formats, protocol, and logging
- CLI reference — discovery and registered-combination examples
- CLI spec v1 and help v2 — the closed invocation registry and token-efficient help contracts
- Protocol v1 and transport mappings — the event envelope across CLI, HTTP, MCP, and SSE
- Bash authoring kit — arguments, config reads, events, and transparent child processes
- Agent Skill — for AI-assisted development
- Per-language API reference: Rust · Go · Python · TypeScript
License
MIT