CLI tools MUST separate data from diagnostics and offer machine-readable output formats. Mixing status messages with data forces agents into fragile regex extraction that breaks on any format change.
Why Agents Need It
An agent calling a CLI needs three things from each invocation: the data, the error (if any), and the exit code. When data goes to stdout, diagnostics go to stderr, and errors carry machine-readable fields, the agent parses the result reliably without heuristics. Mix these channels or ship human-formatted output only, and the agent falls back to best-effort text parsing that fails unpredictably across versions, locales, and edge cases: silently at first, catastrophically later.
Audited live by anc audit <tool> --principle 2: behavioral and source checks.
Requirements
Evidence
Rust reference implementation:
OutputFormat enum with Text, Json, Jsonl variants deriving ValueEnum.
OutputConfig struct with format, use_color, and quiet fields threaded through every output-producing function.
serde_json in Cargo.toml.
No println! in src/ outside the output module: every print goes through OutputConfig.
Exit-code constants or match arms mapping error variants to distinct numeric codes.
eprintln! (or an equivalent diagnostic macro) for every diagnostic line.
Anti-Patterns
println! scattered across handlers instead of routing through the output config.
A single exit code (1) for everything: agents cannot distinguish auth failures from config errors.
Status lines ("Fetching data…") printed to stdout where they contaminate JSON output.
process::exit() in library code, bypassing structured error propagation.
Human-formatted tables as the only output mode with no JSON alternative.
Measured by audit IDs p2-output-json, p2-output-format, p2-stderr-diagnostics. Run anc audit --principle 2 . against the CLI under test to see each.