Agent-First Data v0.24: Help Is a Result, Not an Exception

by Agent-First Kit Contributors

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:

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.