Agent-First Data v0.12: Help Scope and Format Are Two Knobs, Not One
v0.7 made --help expand the whole command tree so agents could read a CLI in one call. That conflated two decisions — how much to show and how to render it. v0.12 splits them: --recursive controls scope, --output controls format, and the two compose. Same model across Rust, Go, Python, and TypeScript.
Back in v0.7 we
argued that --help should be complete: one call, the whole command tree, so an
agent never has to crawl subcommands one at a time. That was the right instinct
for agents. But baking it into --help itself was the wrong mechanism.
Making --help always-recursive solved the agent’s problem by breaking the
human’s. A person typing tool --help to remember one flag now gets a wall of
every descendant subcommand. And a separate --help-markdown flag had quietly
appeared to cover doc generation — which meant “how much help” and “what format”
were tangled into two overlapping flags that didn’t compose.
v0.12 untangles them into two orthogonal knobs.
Scope and format are different questions
- Scope — how much of the tree to show — is controlled by
--recursive. - Format — how to render it — is controlled by
--output.
They compose cleanly:
tool --help # one level, plain text (the human default)
tool sub --help # one level, scoped to `sub`
tool --help --recursive # whole tree, plain text (the agent read)
tool --help --recursive --output json # whole tree, structured schema
tool --help --recursive --output yaml # whole tree, structured schema
tool --help --recursive --output markdown # whole tree, docs export
The --help-markdown flag is gone. Markdown is just one value of --output,
reachable only in a help context — it never becomes a general business output
format.
Humans get a short help back
The default --help is one level again. A person sees the current command, its
flags, and the names of its direct subcommands — the conventional shape they
expect. Drilling in with tool sub --help still works exactly as before.
Agents lose nothing: --help --recursive gives them the complete map the v0.7
post asked for, and --output json|yaml gives them that map as a parseable
schema instead of text they have to scrape.
One subtlety: --recursive without --help
--recursive on its own is a scope modifier, not a help trigger. Many tools
already use --recursive for their own operations. So a bare --recursive
without --help falls straight through to the application’s own parser — the
help layer leaves it alone. It only changes scope when --help is also present.
The handler that makes it work
clap (and the equivalent in each language) wants to short-circuit on --help
before you can inspect the other flags. So the v0.12 helpers scan argv before
parsing:
match cli_handle_help_or_continue(&raw, &Cli::command(), &HelpConfig::human_cli_default()) {
Ok(Some(help)) => { print!("{help}"); std::process::exit(0); }
Ok(None) => {} // not a help request — keep parsing
Err(err) => { println!("{}", output_json(&err)); std::process::exit(2); }
}
HelpConfig::human_cli_default() makes --help one-level plain;
HelpConfig::agent_cli_default() flips the default scope to recursive for tools
whose primary caller is an agent. Either way, --recursive and --output still
override per invocation. Under the hood,
cli_render_help_with_options(cmd, path, &HelpOptions { scope, format }) does the
rendering, and the old cli_render_help / cli_render_help_markdown remain as
recursive wrappers.
Same model in four languages
As with every AFDATA capability, the scope/format split ships identically in
Rust, Go, Python, and TypeScript — same flags, same precedence, same structured
schema for --output json|yaml. A team standardizing CLIs across languages gets
one help contract, not four dialects of it.
v0.7 was right that --help is part of the API. v0.12 makes it an API with two
parameters instead of one overloaded flag.