Agent uses flat verbs: run, follow, fork, interrupt, wait, ls,
inspect, turns, rm, prune, approvals, answer, models, start, stats,
shutdown, and serve. All named
bots use the same commands. There is no parent/child command hierarchy.
Use agent --help, agent COMMAND --help, or agent help COMMAND for help;
-h also works. agent --help gives each command in one line; a command's own
help lists its flags, and run --help lists a new bot's settings apart, after
the everyday flags. Help writes to stdout and exits successfully without
opening a store or connecting to a daemon.
Inside a bot's shell tool, AGENT_BOT and AGENT_BOT_ID identify that bot.
The client sends both as created_by and created_by_id when creating or
forking a child. The store rejects missing or stale creator identities before
creating the child, including after a daemon restart or name reuse;
AGENT_PARENT and AGENT_PARENT_ID identify the running bot's recorded creator
when its identity was known at creation. Address that creator with
run --detach --bot "$AGENT_PARENT" --bot-id "$AGENT_PARENT_ID" -- TASK.
The ID stays pinned after deletion, so a replacement with the same name rejects
the call instead of receiving the work.
run without --bot creates a fresh identity. run --bot NAME continues an
existing bot; add --new to create that name. A prompt of - reads stdin.
A command that lost its reply is resent unchanged. run --request-id ID keys
both the creation and the submission, so run --new --bot NAME --request-id ID
resent gets the bot and the turn it made, with duplicate: true, rather than
bot_exists; fork --request-id ID does the same for a fork, wherever its
source has moved since, even once the source is deleted. Once the bot a keyed
command made is deleted, any command with that name and key is bot_deleted
rather than a new bot. The same key with any other field changed is
idempotency_conflict, with the field named in detail and field, and a
name taken by any other request is still bot_exists. rm --bot NAME --bot-id N deletes that identity: resent while it runs or once it is gone, it
succeeds as a duplicate and leaves a later bot of the same name alone. An
identity that name never held is bot_not_found.
follow --bot NAME replays and follows the selected current turn to its end;
an idle bot returns after replay. follow --all stays connected for future work.
inspect --bot NAME prints one bot's whole record: the fields of its ls
row plus its instructions, settings and compaction. It changes nothing, so it
is safe to repeat; with --bot-id N, a bot that no longer holds that
identity is bot_not_found.
- Put the command first. Options may appear before or after its operands.
- Long value flags accept both
--flag VALUEand--flag=VALUE. --ends option parsing. For example,agent run -- --helpsends the literal prompt--help. Use--instructions=--examplefor a flag-like value.- Prompts and wait handles are positional operands. Every bot selector is
--bot NAME; a fork uses--source NAMEand--bot DESTINATION. --allselects all bots forfollow;--anyselects first-completion behavior forwait.follow --all --bot NAMEis an error.run --delivery MODEisreject,queue,steer, orattach: what the submission does when the bot is busy. Without the flag,AGENT_DELIVERYapplies, thenreject.--turn Nwithsteermakes it strict: for that running turn orstale_turn. The daemon has no default of its own: the client always sends the mode it resolved, so one person's preference never changes what a program's submission means. A blockingrunwithqueuefollows the turn from its queued state to its end; withsteerit ends with exit 0 when the message is absorbed (steered, naming the turn it joined) or when the turn it started completes. A steer never waits behind a blocking call and never cancels one: a turn parked onwaitresumes at once with its unresolved handles pending (still valid) andsteered: true, and a foregroundshellcommand still running 250 ms after the steer moves to the background and returns itsproc:handle.queuedeliveries leave both alone. A steer with an explicit workspace or model that differs from the running turn stays queued and runs separately with those choices.attachadds a message that needs no reply to the bot's next model call, mid-turn or in its next turn, without starting a turn;runprints the submission and returns at once.run --turn-budget-tokens Ncaps this turn's input plus output tokens, beside the bot's lifetime--budget-tokens. The turn fails withturn_budget_exhaustedbefore a call once it has spent N; the cap is the turn's own, so the next turn starts from zero. A steer joins a turn that already has its cap, sosteerwith the flag isinvalid_delivery.run --workspace DIRchooses the folder. A new bot starts in it, or in the directoryrunwas invoked from. A bot keeps its folder: a laterrunwithout the flag runs there wherever it is invoked, and one with it runs inDIRand moves the bot there; a steer's folder never moves it. Aforkstarts in its source's folder unless it names one.- Unknown flags, flags belonging to another command, unexpected operands,
and repeated singleton flags are usage errors.
--provideris repeatable. --instructionsand--instructions-fileare mutually exclusive.--agentscomposes the shared client policy instead: the harness preamble, every AGENTS.md and.agents/AGENTS.mdfrom the workspace up to the root plus~/.agents/AGENTS.md, and indexes of the skills in.agents/skills/NAME/SKILL.mdand the profiles in.agents/agents/ROLE.md, and the memory indexes in~/.agents/memory(CLIENT.md). It is opt-in on the CLI, the default in the app, and exclusive with--instructions.--profile ROLEcomposes the same text with that role last, and takes the role'smodelandtoolsunless--modelor--toolsnames them; a missing role isprofile_not_found.forktakes none of these: a fork is an exact copy of its source, instructions included. Withrun, instructions and token budget apply to new identities; passing them while continuing an existing named bot is an error.run --new --approval MODEchooses whether a new bot's tool calls wait for a verdict:fullruns every allowed call (no gate),manualwaits for an answer from any client, andautohas a judge model decide each call. Forauto, and wheneverruncontinues orforkcopies a bot anautogate answers, the CLI startsagent approverdetached when no session serves theautotag, and gives a call 45 s before its gate lapses. Without the flag,AGENT_APPROVALapplies, thenfull.--approve LISTpicks the gated tools and must name at least one; the default is every tool buthistory,wait,note, andecho, and inautoreadtoo. A fork keeps its source's gates and a created bot its creator's (APPROVALS.md);forktakes no approval flags, and a program that wants a fork gated further passesapproveon the protocol'sfork.approver [--tag TAG] [--judge PROVIDER/MODEL] [--effort LEVEL] [--note FILE] [--judge-url URL]serves a gate tag (defaultauto) and has a judge decide every call waiting on it, one request per round, printing one JSON line per round. The judge is--judge, elseAGENT_JUDGE, elseAGENT_MODEL. OpenAI's Decisions API (openai-decisions/MODEL, keyOPENAI_API_KEY) is called on its own API, at--judge-urlwhen given; any other model runs through the daemon, with a tag of at most 67 bytes, and--effortsets its effort.--note(defaultAGENT_APPROVER_NOTE) is a regular file of at most 96,000 bytes the judge always sees, such as trusted remotes and hosts (APPROVALS.md).approvals [--bot NAME] [--tag TAG] [--full]lists the calls waiting on a gate. Each previews its arguments;--fullreads the whole arguments of a call whose preview was cut from the node that planned it. With--pretty, each call shows what it would do (every line of its command, or of what awriteoreditputs in its file, terminal controls escaped) and, for each gate still open, two whole commands, one that allows and one that denies, so a pasted line never carries a verdict its reader did not pick. The call id is written--call=ID, so an id that starts with--stays the value.answer --bot NAME --turn TURN --call ID --request N [--tag TAG] [--reason TEXT] allow|denyrecords one gate's verdict;--tagmay be left out when the call has one gate, and a denial's--reasonis what the model sees (an allow takes none).answerrefuses to run inside a bot's own tool shell (answer_in_tool_shell).run --prettyandfollow --prettyprint the same commands when a call waits. A printed command carries--storeor--socket, as absolute paths, whenever the daemon it came from is not the default one, so it answers that daemon from any shell.- Every time flag takes a duration with its unit:
500ms,30s,5m,1h, or a bare0. A bare number is refused, so no flag guesses a unit:--timeout,--approval-hold,--grace,--stall-timeout,--idle-exitand--keep-warm. The last three count whole seconds. The protocol keeps its own units (timeout_ms,grace_ms,approval_hold_ms,keep_warmseconds).--approval-holdis how long a new bot's gated calls wait live for a verdict before the turn parks (default2s;0parks at once).--afteris an exclusive event cursor forfollowand an exclusive turn ID forturns.--checkpointis a history node ID. - Listings read only what they print.
--limit Nstopsls,turnsandapprovalsafter N entries.ls --name GLOBkeeps the bots whose name matches ('project.*','*.lead'; SQLite GLOB, case-sensitive) andls --activethose with a turn running; the daemon filters, so neither pages through every bot.turns --newestlists newest first, read from the end, soturns --bot NAME --newest --limit 3is a bot's last three turns. --keep-turns Nmeans the same onpruneandrun: keep the newest N turns' records.pruneapplies it once; onrun --newit is the bot's setting, applied after each of its turns.runsets a new bot's own settings with--context-bytes,--context-items,--note-turns,--compact-at,--compact-keep,--compact-tokens,--keep-turns,--approval-hold,--max-output-tokens,--keep-warm, and--cache-ttl; they are not daemon options, and an existing bot keeps its own (see bot settings).
The lightweight command registry in src/cli.rs supplies help and option scope
for both client commands and serve. Keep that registry, the implementation,
and behavior tests aligned when adding an option. No CLI framework or runtime
dependency is required.
Default output is machine-readable JSON. run and follow stream one JSON
object per line. Snapshot commands return compact JSON objects; ls and turns
return arrays. Every command prints its reply: interrupt the turn view,
answer what it answered, and shutdown {"stopped":true,"pid":N} once the
daemon is gone. Every client command takes --pretty; approver and serve
run until stopped and print no reply to render.
shutdown returns once the daemon process has exited and its store is closed.
shutdown --grace DURATION first lets running turns finish for up to that long
while starting none; turns still running then end interrupted with
daemon_shutdown. A daemon whose ready line announces an older protocol than
this agent's cannot be asked in this protocol, so shutdown sends SIGTERM to
the process that ready line names (a process already gone counts as stopped)
and waits for it the same way: its running
turns end interrupted and the store keeps every chat. That is how an upgrade on
a machine replaces the daemon an older agent started. A newer daemon is left
running and shutdown fails with daemon_protocol_mismatch.
--pretty is an explicit human view: rendered streams, tables for lists, and
indented JSON for other results, including detached submission handles. It is
rejected on commands with no output. Diagnostics and failures go to stderr. Rendered model
text and tool output keep their line breaks and tabs; any other character a
terminal would act on is printed escaped, so a stream cannot hide or restyle a
call waiting for approval.
On a terminal, run --pretty and follow --pretty also report program status
(OSC 7501), so a
terminal that supports it can mark the tab: working while the turn runs,
blocked (kind=permission) while a call waits for a person's approval (a
manual gate; a program's gate, like auto, is still working), then done,
error, or idle when the turn completes, fails, or is interrupted. follow --all --pretty reports one record per bot, with the bot's name as its id, and
clears a deleted bot's record; only the bot's running turn moves it, not one
queued behind. History replayed before the stream goes live reports only turns
still running. Each report has app=agent and the bot's
name as its title, and is written once per change. Nothing is written when
stdout is not a terminal.
The recording is run --pretty against the offline playground, replayed in
xterm.js with a small OSC 7501 handler that draws the tab's mark.
A failure prints one JSON object on stderr, the shape the daemon answers with:
error is the code, detail states the rule that was broken, and any other
field is a fact about the current state, named as a request field (such as
running_turn, bot_id, efforts, deliveries or field). When the CLI can say how to get
past the refusal in its own flags, it adds hint. With --pretty the failure
is one line instead: agent: CODE: DETAIL, followed by ; HINT when there is
one. Usage errors use the same object with error set to usage. A detail
that echoes what was sent is cut in its middle to about 4 KiB, so a refusal
always fits in one response. When run or start starts a daemon that fails,
the command fails with the daemon's own error, such as invalid_provider_url.
serve adds its pid to its error line, so a launcher reports its own
daemon's failure from the store's shared log even when several start at once;
a daemon that exits without one is daemon_start_failed, naming the log.
{"error":"bot_busy","detail":"turn 4 is running","running_turn":4,"fork_point":9,
"hint":"resend with --delivery steer --turn 4 to add this to it, or --delivery queue to run it afterwards; ..."}wait --bot NAME waits on that bot's running turn, else its newest, so a
caller can wait on work it did not submit; the result is keyed by the handle
it chose, as if that handle had been given. A turn queued behind the running
one is not it. A bot with no turns is no_turn, and --bot-id pins the
identity as on inspect. --bot and handles do not mix.
wait normally requires all handles to resolve without errors. With --any,
one resolved successful handle suffices and the remaining handles stay valid.
An errored first result or a timeout with no resolved result exits 1.
--timeout 0 polls once and prints the same JSON result with unresolved
handles marked pending. Completed successful handles can still return exit 0.
Every client command accepts --store and --socket. Client store selection is explicit
--store, then AGENT_STORE, then ~/.agent/state.sqlite. Explicit --socket
wins; otherwise AGENT_SOCKET applies unless --store was explicit. Without a
socket override, the socket is derived from the selected store.
run may start the daemon. start starts it if none is running, with the
same startup as run, and prints the running daemon's ready line with
socket, the absolute path it answered on, added; a caller that ran start over SSH
forwards that path. A socket path that is not UTF-8, which the ready line
cannot carry, is refused before anything starts (socket_path_unsupported). When the daemon holding the socket speaks another
protocol, start still prints its ready line and then fails with
daemon_protocol_mismatch, so a caller can tell an older daemon from a newer
one. stats,
turns, rm, prune, approvals, and answer may restart it only for an
existing store. These commands accept the startup
provider/limit flags shown in help and --no-spawn to require an
already running daemon. Startup flags configure a newly started daemon. A
running daemon is never reconfigured: a stated startup flag it does not match
fails the command with daemon_configuration_mismatch naming the difference.
Comparison uses effective limits: --idle-exit 0 means disabled, and positive
context limits are clamped to at least 1,024 bytes and two items.
With no --provider, a started daemon takes AGENT_PROVIDER (the same specs,
separated by whitespace), else the providers whose key variable is set; a
running daemon is not checked against either.
run --model names a new bot's model or an existing bot's turn model; the
daemon has no model of its own, so a new bot needs --model or AGENT_MODEL.
AGENT_MODEL is only a creation default. An existing bot uses its stored
model unless --model explicitly overrides it for this turn.
run --effort LEVEL sets a new bot's effort: low,
medium, high or xhigh on either family, and max on Anthropic's; any
other is refused with invalid_effort. Without it the request carries
no level and the model uses its own default. On an existing bot it sets this
turn's effort, as --model sets its model, and the bot's own is unchanged. A
turn with a level sees it in its shell as AGENT_EFFORT, and a new bot that takes its model
from AGENT_MODEL takes its level from AGENT_EFFORT, so a peer started
on its creator's model thinks as hard as its creator; a bot given
--model or a profile's model gets only its own --effort.
run --instructions and --instructions-file set a new bot's instructions,
with the CLI's built-in text as the default; the daemon has none. run --tools LIST chooses a new bot's tools from those the daemon registers,
default shell,read,write,edit,wait,history; an existing bot keeps its own,
so --tools while continuing one is a usage error.
Other client commands require an already running daemon.
models prints ~/.agent/models, the models clients offer, as JSON
[{id, note?}] (--pretty: one line each). The file holds one
PROVIDER/MODEL per line, with anything after # a note; no file is an
empty list, and a malformed line fails with models_invalid naming it.
Reading it needs no daemon, and the daemon never reads it: any model runs.
models --discover writes a first file from the listings of the providers
the daemon runs (starting one like run), with each model's name and
context size as its note and a comment for a provider that listed nothing.
When no usable model survives rendering within the file limit, it writes
nothing and fails with
models_none_listed, naming each refusal, so it can run again after a
login. It refuses with models_file_exists once the file exists: after
that it is the user's to edit. Bots see the same list by running models.
serve requires explicit --store and --provider options. It runs the daemon
directly, with Unix-socket service when --socket is
provided and the stdio protocol otherwise. Credentials and provider configuration
follow the runtime documentation.

