14 KiB
Agent CLI Specification
This document specifies a command-line interface for the coding agent library defined in the Coding Agent Loop Specification. It is a thin wrapper that configures and runs a Session, designed to be implementable from scratch by any developer or coding agent in any programming language.
Table of Contents
- Overview and Goals
- Invocation
- Permission Model
- Output
- Environment and Configuration
- Exit Behavior
- Definition of Done
1. Overview and Goals
1.1 Problem Statement
The coding agent library (Session) is programmable-first: it gives host applications full control over the agentic loop. But a library alone forces every user to write a host application before they can point an agent at a task. Developers need a zero-ceremony way to run a coding agent from the terminal, and CI systems need a headless way to invoke one from a script.
The CLI is the simplest possible host application for Session. It translates command-line arguments into a SessionConfig, subscribes to the event stream for rendering, and exits when the session completes. It does not add concepts of its own -- no workspace management, no persistent identity, no project-level config files. Every feature lives in the library; the CLI is the wiring.
1.2 Design Principles
Thin wrapper. The CLI configures a Session and renders its events. All intelligence -- tool execution, loop detection, truncation, subagent spawning -- lives in the library. The CLI never duplicates library logic.
Dual-mode. Interactive by default (streaming output, tool approval prompts). Fully scriptable with flags (--auto-approve). Same binary, same flags, different defaults based on whether a TTY is attached.
Single command. No subcommands. agent <prompt> is the only invocation. This keeps the mental model flat and the --help output short.
Opinionated defaults. The CLI picks reasonable defaults (provider, model, permissions) so that the common case requires zero flags. Power users override with explicit flags.
1.3 Relationship to Companion Specs
The CLI depends on the Coding Agent Loop Specification for all agent behavior. It uses Session, SessionConfig, EventEmitter, EventKind, and ExecutionEnvironment directly. LLM communication flows through the agent library's use of the Unified LLM Client Specification; the CLI does not interact with the LLM client directly.
┌─────────────────────────────┐
│ agent CLI │
│ (this spec) │
│ - arg parsing │
│ - event rendering │
│ - permission prompts │
└──────────┬──────────────────┘
│ configures + runs
▼
┌─────────────────────────────┐
│ Session (agent library) │
│ - agentic loop │
│ - tools, subagents │
│ - loop detection │
└──────────┬──────────────────┘
│ uses
▼
┌─────────────────────────────┐
│ LLM Client (llm library) │
│ - provider adapters │
│ - streaming, retry │
└─────────────────────────────┘
2. Invocation
2.1 Usage
agent [OPTIONS] <PROMPT>
PROMPT is a required positional argument: the task for the agent to perform. No subcommands exist. No stdin reading, no REPL. If the user has a long prompt, they can quote it or use shell heredoc syntax.
2.2 Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--provider |
String |
"anthropic" |
LLM provider: anthropic, openai, or gemini. |
--model |
String |
Provider default | Model identifier. When omitted, uses the provider profile's default model. |
--permissions |
Enum |
read-write |
Permission level: read-only, read-write, or full. See Section 3. |
--auto-approve |
Boolean |
false |
Skip all interactive approval prompts. Denied tools are hard-blocked instead. |
--debug |
Boolean |
false |
Dump raw LLM request/response payloads to stderr. |
No other flags. The CLI does not expose: LLM parameters (temperature, max_tokens), subagent configuration, turn limits, session resumption, context injection, dry-run mode, structured output, working directory override, or verbose levels. These are deliberate omissions to keep the surface area minimal.
2.3 Provider and Model Resolution
The --provider flag selects the provider profile from the agent library:
--provider value |
Profile | Default model |
|---|---|---|
anthropic |
AnthropicProfile |
Profile's default |
openai |
OpenAiProfile |
Profile's default |
gemini |
GeminiProfile |
Profile's default |
When --model is specified, it overrides the profile's default model but does not change the profile selection. The provider profile determines the system prompt, tool definitions, and tool-calling conventions.
Why explicit --provider instead of auto-detection from model string. Model naming conventions are not stable across providers and can collide. Explicit provider selection is unambiguous and avoids a mapping table that rots.
3. Permission Model
3.1 Permission Levels
The --permissions flag controls which tools the agent can use without approval:
| Level | Tools available without approval | Tools requiring approval |
|---|---|---|
read-only |
read, grep, glob |
write, edit, shell |
read-write |
read, grep, glob, write, edit |
shell |
full |
read, grep, glob, write, edit, shell |
(none) |
The default is read-write: the agent can read and modify files freely but must ask before running shell commands.
3.2 Interactive Approval (TTY attached, no --auto-approve)
When the agent calls a tool that requires approval, the CLI prompts the user on stderr:
Agent wants to run shell: npm test
Allow? [y]es / [n]o / [a]lways
- y: Allow this single invocation. The agent proceeds. Future calls to the same tool still prompt.
- n: Deny this invocation. The agent receives a tool error:
"shell tool denied by user at current permission level". The agent must adapt. - a: Escalate permissions for the remainder of the session. The tool (and all tools at or below its permission level) no longer prompt. Equivalent to upgrading
--permissionsmid-session.
3.3 Non-Interactive Mode (no TTY or --auto-approve)
When the CLI cannot prompt (piped stdin, --auto-approve set, or no TTY), tools that require approval are hard-blocked. The agent receives a tool error message and must find another way to accomplish the task.
--auto-approve does not implicitly upgrade to --permissions full. It means "don't prompt me, just enforce the stated permission level." A CI pipeline that wants full tool access must explicitly pass --permissions full --auto-approve.
Why hard-block instead of auto-approve-all in CI. Silent full access in CI is dangerous. Forcing --permissions full to be explicit makes the trust decision visible in the pipeline definition.
4. Output
4.1 Event Rendering
The CLI subscribes to the Session's EventEmitter and renders events as follows:
| EventKind | Rendering |
|---|---|
AssistantMessage |
Stream text content to stdout as it arrives. |
ToolCall |
Print a one-line summary to stderr: tool name and key argument. |
ToolResult |
Suppressed (not shown to user). |
TurnComplete |
No output. |
Error |
Print error message to stderr. |
Assistant text streams to stdout character-by-character (or chunk-by-chunk as delivered by the LLM streaming response). This is the primary output.
Tool call summaries go to stderr so they don't interfere with piping stdout. Format:
[tool] read src/main.rs
[tool] edit src/lib.rs
[tool] shell npm test
4.2 Debug Mode
When --debug is set, the CLI additionally logs full LLM request and response payloads to stderr. This includes:
- The complete message array sent to the LLM
- System prompt
- Tool definitions
- Raw response body (streamed chunks or complete response)
Debug output is prefixed with [debug] to distinguish it from tool summaries.
4.3 Completion Summary
When the session ends, the CLI prints a one-line summary to stderr:
Done (4 turns, 7 tool calls, 3.2k tokens)
This always appears, regardless of whether the agent succeeded or failed. Token count is the total across all turns (input + output). The summary goes to stderr so stdout contains only the agent's text output.
5. Environment and Configuration
5.1 API Keys
API keys are read from standard environment variables. No config file, no .env loading, no key management.
| Provider | Environment variable |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| OpenAI | OPENAI_API_KEY |
| Gemini | GEMINI_API_KEY |
If the required key is missing, the CLI exits immediately with a clear error message naming the expected variable.
5.2 Working Directory
The agent always operates in the process's current working directory. There is no --dir flag. Users who need a different directory use cd before invoking agent, following Unix convention.
5.3 Session Lifecycle
Every invocation is a fresh session. There is no session persistence, no resume flag, no checkpoint support at the CLI level. The agent starts, runs to completion, and exits. State between runs is carried only by the filesystem (files the agent created or modified).
6. Exit Behavior
6.1 Exit Codes
| Code | Meaning |
|---|---|
0 |
Agent completed successfully. |
1 |
Agent failed (LLM error, tool error, config error, agent gave up, or any other failure). |
Two codes only. The CLI does not differentiate between failure causes via exit code. Diagnostic information is in stderr output.
6.2 Interruption
Ctrl-C (SIGINT) triggers a graceful shutdown: the current LLM request is cancelled, any running tool is terminated, and the CLI exits with code 1. No cleanup prompt, no "are you sure" -- immediate stop.
7. Definition of Done
This section defines how to validate that an implementation of this spec is complete and correct. An implementation is done when every item is checked off.
7.1 Invocation
agent 'hello world'sends "hello world" to the LLM and prints the response to stdoutagentwith no arguments prints usage and exits with code 1--provider anthropicuses the Anthropic profile--provider openaiuses the OpenAI profile--provider geminiuses the Gemini profile--modeloverrides the default model within the selected profile- Invalid
--providervalue exits with code 1 and a clear error
7.2 Permissions
- Default permission level is
read-write --permissions read-onlyblocks write, edit, and shell--permissions fullallows all tools without prompts- In interactive mode, denied tools trigger an approval prompt on stderr
- Answering "y" allows a single invocation
- Answering "n" returns a tool error to the agent
- Answering "a" escalates permissions for the session
- In non-interactive mode, denied tools are hard-blocked (tool error returned to agent)
--auto-approvedoes not implicitly upgrade permission level
7.3 Output
- Assistant text streams to stdout
- Tool call summaries print to stderr in
[tool] name argsformat - Tool results are not shown to the user
- Completion summary prints to stderr:
Done (N turns, N tool calls, Nk tokens) --debugdumps full LLM request/response payloads to stderr
7.4 Configuration
- API keys read from
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY - Missing API key exits with code 1 and names the expected variable
- Agent operates in the current working directory
7.5 Exit Behavior
- Successful completion exits with code 0
- Any failure exits with code 1
- Ctrl-C cancels the current operation and exits with code 1
7.6 Integration Smoke Test
FUNCTION smoke_test():
-- Setup
SET dir = create_temp_directory()
write_file(dir + "/hello.txt", "world")
SET env = {"ANTHROPIC_API_KEY": valid_key}
-- Test 1: Basic invocation
SET result = run_cli(
args: ["agent", "Read hello.txt and tell me what it says"],
cwd: dir,
env: env
)
ASSERT result.exit_code == 0
ASSERT result.stdout CONTAINS "world"
ASSERT result.stderr CONTAINS "Done ("
ASSERT result.stderr CONTAINS "[tool] read"
-- Test 2: Permission enforcement
SET result = run_cli(
args: ["agent", "--permissions", "read-only", "--auto-approve",
"Write 'test' to output.txt"],
cwd: dir,
env: env
)
-- Agent should complete (exit 0) but output.txt should not exist
-- because write was blocked and agent adapted
ASSERT NOT file_exists(dir + "/output.txt")
-- Test 3: Missing API key
SET result = run_cli(
args: ["agent", "hello"],
cwd: dir,
env: {}
)
ASSERT result.exit_code == 1
ASSERT result.stderr CONTAINS "ANTHROPIC_API_KEY"
-- Test 4: No arguments
SET result = run_cli(
args: ["agent"],
cwd: dir,
env: env
)
ASSERT result.exit_code == 1