Agent-First Data v0.24: Help Is a Result, Not an Exception
Every output of an AFDATA CLI is structured — except the one an agent reads first. `--help` has always answered in 80-column prose no matter what the tool's output contract said. v0.24 makes help a normal result: it inherits the CLI's own `--output` default, so `afdata --help` returns a protocol-v1 envelope with `result.code:"help"`, and `--output plain` is how a human asks for text. The model is deliberately small — no long-form prose, no repeated inherited globals, no eagerly embedded version values — and the shape is pinned by `cli-help-v1.schema.json`, validated in the test gate against real output from all four SDKs.
An agent meeting an unfamiliar CLI does the same thing you do: it runs --help.
That is the one command whose output it cannot predict. Everything else an
AFDATA tool emits is structured — results on stdout, errors on stderr, a stable
error.code, _secret fields redacted. Then --help hands back 80 columns of
wrapped prose with two-space indentation and a “Commands:” heading, and the
agent is reduced to scraping ASCII layout to learn what the tool can do.
The output contract had an exception carved into it, and the exception was the part read first.
v0.24 removes the exception.
Help follows the same default as everything else
Help is not a special mode. It is a successful result, and it answers in the format the CLI already promised:
afdata --help # a protocol-v1 result — afdata defaults to JSON
afdata --help --output plain # conventional terminal text, for humans
A JSON-default CLI makes afdata --help equivalent to afdata --help --output json. A tool whose normal output is YAML gets YAML help for free. Nothing about
help is hardcoded — scope and format are orthogonal knobs (--recursive for the
whole subtree, --output for the rendering), and the format resolves the same
way every other command resolves it: an explicit --output wins, then the
selected command’s declared default, then the nearest ancestor’s, then the
caller’s fallback.
The structured form is a real protocol event, not a schema dump bolted on the side:
{"kind":"result","result":{"code":"help","help":{ ... }},"trace":{}}
Which means it composes with the tools that were already there. The test gate
pipes --help output straight back through afdata validate --strict --per-event and afdata lint, and both pass. Help is AFDATA data.
Small on purpose
Structured help is only worth having if it is cheaper than the prose it replaces. A naive JSON dump of clap’s model is bigger than the text — it carries every long description, every repeated global, every empty field.
So the model is compact by contract. afdata get --help is 843 bytes of JSON
against 1853 bytes of plain help — less than half, while being parseable rather
than scrapeable. The rules that get it there:
aboutis the concise summary. Clap’s Markdownlong_aboutis never exposed as a genericdescription. If a full-prose mode is ever added, its opt-in field isdescription_markdown— the compact model stays compact.- Inherited globals are named, not repeated. Scoped help carries
inherited_arguments_from: ["afdata"]instead of re-listing--output,--output-to, and the rest on all 25 commands. An agent fetches the defining command’s help only if those options matter. - Empty and default metadata is omitted. No
"global": false, no"". - Version values are not embedded. Help advertises that
-V, --versionexists; it never repeats the version string. Ask--versionwhen you need it.
The e2e enforces this with a byte budget scaled per command and per argument, so “compact” is a test failure when violated, not an aspiration. And because the budget is structural, adding a command costs its structure — not a fresh copy of every global option.
-h and -V are gone
Once help answers in the command’s output format, the short aliases stop making sense — and the reasoning is worth spelling out, because it inverts a reflex.
-h was byte-identical to --help. That buys an agent nothing: it types the
long form and does not count characters. And it actively misleads the human
reaching for it, because -h now returns JSON. To get readable text that
human has to type --output plain — a long flag — so the short never saved
them anything either. An alias that serves neither audience, and whose only
lasting contribution is a collision with every tool that spends -h on
--host, is not a convenience. It is a trap.
So AFDATA spells both out in full. --help and --version are the only
spellings, and -h/-V return a structured cli_error with a hint rather than
silently falling through to the argument parser’s plain-text help at exit 0 —
which is what would otherwise punch a hole straight through the output
contract.
Applications keep their own shorts. afdata itself declares -0 for --null
on paths and keys, and the model reports it in the argument’s short field.
The rule is only that the help layer stops claiming letters it does not need.
One entry point
On the Rust side, version and help are one call before parsing:
agent_first_data::cli_handle_version_or_help_or_continue(
&raw, &Cli::command(), &HelpConfig::output_aware(),
"afdata", Some(env!("DISPLAY_NAME")), env!("CARGO_PKG_VERSION"), build,
)
output_aware() is the blessed preset: inherit the command’s own --output
default. A fixed-format CLI that has no --output argument at all says so
explicitly with output_aware_with_fallback(HelpFormat::Json), rather than
silently degrading to human text — the failure mode that made structured help
unreliable in the first place. Version output now follows the same
explicit-or-inherited rule.
A contract, not an implementation
The clap renderer is Rust-only. The contract is not.
spec/cli-help-v1.schema.json pins the shape, and the gate validates real
--help output from all four SDK examples — Rust, Go, Python, TypeScript — plus
the afdata binary itself, against that schema and a fixture of valid and
invalid events. A language without clap still emits help-v1; it just builds the
model its own way. What ships identically is the envelope an agent parses.
The --output markdown export stays the documentation path, and it is what
generates this repo’s own CLI reference — now with every Usage: line carrying
its full invocable command path, so a reader can copy any line in the file
straight into a shell.
Read only the route you need
The Agent Skill got the same treatment. It was one 358-line rules file that an agent loaded in full to answer any question. It is now a router: a short table that points at four focused references — naming and output, CLI and protocol, documents, Bash — plus the machine-readable schemas. Read the route the task needs, not the whole convention.
Which is the same idea as the rest of this release. The information was always there. v0.24 is about not making an agent pay for the parts it did not ask for.