4 Principles for Agent-Facing CLI Design
/ 3 min read
Everyone’s talking about making tools “agent-friendly.” Most of the advice — use flags, don’t be interactive, return structured output — is just good CLI design. It applies equally to humans scripting in CI.
After building mtor, a Temporal-based coding task dispatcher that’s consumed almost exclusively by AI agents, I found exactly four principles that are agent-specific. Everything else is table stakes.
1. Structured output contract
Every response is a JSON envelope. Not text, not tables, not pretty-printed summaries.
{ "ok": true, "command": "mtor list --since 24", "result": {"workflows": [...], "count": 12}, "next_actions": [...], "version": "0.5.0"}This isn’t just “output JSON.” The envelope shape is the API — ok, result, error, fix, next_actions, version. It’s versioned and stable. The agent parses one schema for every command, not a different text format per subcommand.
Human CLIs can get away with tabular output because humans read flexibly. Agents can’t.
2. next_actions drives the loop
This is the one that matters most. Every response includes exact, runnable commands for what the agent should do next:
"next_actions": [ {"command": "mtor status ribosome-zhipu-abc123", "description": "Poll workflow status"}, {"command": "mtor logs ribosome-zhipu-abc123", "description": "Fetch output when complete"}]The agent never constructs commands from memory. It follows suggestions. This eliminates the entire class of “agent doesn’t know the right flags” failures. The CLI is the expert on its own interface — let it drive.
I haven’t seen a formal standard for this pattern, but Anthropic’s tool design guidance describes exactly this: outputs that “steer agents towards more token-efficient tool-use behaviors.”
3. Errors are actionable
Bad error:
{"ok": false, "error": {"message": "Connection refused", "code": "TEMPORAL_UNREACHABLE"}}Good error:
{ "ok": false, "error": {"message": "Cannot connect to Temporal at ganglion:7233", "code": "TEMPORAL_UNREACHABLE"}, "fix": "Start Temporal worker: ssh ganglion 'sudo systemctl start temporal-worker'", "next_actions": [{"command": "mtor doctor", "description": "Run health check"}]}The fix field is the agent’s recovery path. It doesn’t need to understand Temporal infrastructure — it just runs the fix command and retries. Anthropic’s guidance is emphatic on this: helpful errors include “specific and actionable improvements, rather than opaque error codes.”
4. Self-describing in one call
$ mtor{"ok": true, "result": {"commands": [ {"name": "dispatch", "description": "Dispatch a task prompt to Temporal"}, {"name": "list", "description": "List recent workflows"}, {"name": "status", "description": "Get workflow details"}, ...]}}Bare invocation returns the command tree. The agent discovers all capabilities without reading docs, READMEs, or man pages. One call, full capability map.
For deeper discovery, mtor schema emits full JSON schema of every command’s parameters. The agent can go from zero knowledge to correct invocation in two calls.
What’s NOT agent-specific
The rest is just good CLI practice:
- Flags for optional modifiers, positional for the primary input — clig.dev has been saying this for years
- Non-interactive — no prompts, no spinners, progress to stderr
- Semantic exit codes — 0/1/2/3+ scheme
- Token-efficient responses — truncate, paginate, filter by default
These help agents, but they help bash scripts and CI pipelines equally. They’re not the differentiator.
The real test
If you took away all documentation, could an agent use your CLI effectively by just calling it and reading its responses? If yes — it’s agent-facing. If no — it’s a human CLI with JSON output bolted on.
The next_actions pattern is what makes the difference. Everything else follows from it.