The tori binary doubles as a CLI: run from any shell, including a terminal
tab Tori itself spawned, it talks to Tori’s daemon over the
same protocol its MCP server and phone app use. The daemon keeps running after
you quit, so tori works with the window closed. Every shell Tori opens has
tori on its PATH already; for a terminal outside Tori, click Add under
Settings > Advanced > Terminal command.
tori sessions [--live] [--cwd <path>] [--limit <n>] [--json]tori projects [--json]tori ports [--json]tori session tail <id> [--lines <n>] [--agent <id>] [--json]tori wait <id> [--timeout <secs>] [--json]tori session wait <id> [--timeout <secs>] [--json]tori check <id> [--json]tori handoff <id> --to <agent> [--account <id>] [--model <id>] [--note <text>] [--json]tori session pending <id> [--json]tori session answer <session> <id> <text>...tori session answer <session> <id> --each <text>...tori events [--topic <topic>]...tori whoami [--json]tori steer <id> <text>...tori interrupt <id>tori worktree new <branch> [--project <path>] [--from <ref>]tori checkpoints <id> [--json]tori checkpoint diff <id> <n> [<m>] [--why] [--json]tori checkpoint revert <id> <n> [--force]tori spawn [--agent <id>] [--account <id>] [--model <id>] [--mode <id>] [--effort <level>] [--folder <path> | --new-worktree <branch> [--project <path>] [--from <ref>]] [--prompt <text>] [--attach <path>]... [--background] [--json]tori open <path> [--line <n>]tori open --browser [<branch>]tori budget [<id>] [--folder <path>] [--item <id>] [--pr <n|url>] [--json]tori ask <question>... [--option <text>]... [--timeout <secs>] [--approval <json> [--project <path>]]tori ask --wait <id> [--timeout <secs>]tori ask --answer <id> <text>...tori pr create --head <branch> --head-sha <sha> --base <branch> --title <text> [--body <text>] [--draft] [--project <path>] [--approval <id>] [--json]tori pr review <number> --event approve|comment|request-changes --head-sha <sha> [--body <text>] [--comments <json>] [--project <path>] [--approval <id>]tori pr merge <number> --method merge|squash|rebase --head-sha <sha> [--project <path>] [--approval <id>]tori autopilot state [--json]tori autopilot start|stop [--json]tori autopilot item [<id>] [--kind ship|review --issue <key> | --pr <number> --repo <owner/name>] [--project <path>] [--state <state>] [--worktree <path>] [--session <id>] [--pr-url <url>] [--note <text>] [--title <text>] [--contract <text>] [--json]tori autopilot project [--project <path>] [--ships pr|local] [--autonomy ask-everything|auto-until-outward] [--pickup ask|auto] [--agent <id>] [--account <id>] [--model <id>] [--issues <json>] [--json]tori autopilot hold resolve <id> [--json]tori autopilot schedule add --name <text> --cron <expr> (--prompt <text> | --issue <key>) [--project <path>] [--json]tori autopilot schedule list | remove <id> | resume <id> [--json]tori loop --prompt <text> [--project <path>] [<entry>...] [--json]tori mcptori attach <session|tab>tori daemon [--uninstall]--json is available on read commands for scripting. Exit codes: 0 on
success, 2 for tori ask specifically when it returns a question’s id
with no answer yet, distinct from a real failure so a script can tell the
two apart, and 1 for everything else that went wrong.
Who is calling
Every terminal tab and every chat Tori starts gets a token of its own, so
the app knows which session a command came from and fills in its project,
folder, agent and account. That is why tori worktree new fix/retry needs
no --project when you run it inside a project’s tab. tori whoami prints
what Tori thinks you are.
A command run from a terminal outside Tori is an outside caller: it works,
with no defaults, so pass --project or --folder.
Looking
$ tori sessions --liveID AGENT ACCOUNT STATE BRANCH FOLDER TITLE7c1e9a2f-4b0d-4e2a-9f61-3d8c2b7a5e14 claude work needs you feat/webhooks ~/work/api-webhooks Sign webhook payloadsd4b80c13-9e7f-4a55-8c02-61f0e3a9b7d2 codex default working fix/rate-limit ~/work/api-rate-limit Cap the rate limitertori sessionslists sessions, newest first.--livekeeps only the running ones and--cwdonly those in a folder.tori projectsprints the tree the sidebar draws: spaces, projects and branch rows, then Topics with their members. A Topic in a space has the space’s name after its branch.tori portslists each worktree’s dev servers: branch, folder, port block and the ports listening.tori session tail <id>prints the last events of a conversation, with tool output capped.tori wait <id>(ortori session wait <id>) blocks until a session stops working, then prints why it came back, the question it is waiting on if any, and its last message. See Waiting on a session.tori check <id>prints what a session’s work came to: the verdict of its latest turn that changed code, each check that turn ran, and a diff stat over all its turns. See Checking a session’s work.tori session pending <id>lists what a session is waiting on: questions and permission prompts, each with the idsession answertakes.tori eventsstreams what happens in the app as lines of JSON, until you stop it.--topicnarrows it, and can be repeated.tori budgetprints spend for a session and its project against your ceilings, and the agent’s quota windows.--itemadds what an autopilot item cost, and--prwhat a pull request’s branch cost (see What work costs).tori checkpoints <id>lists a session’s checkpoints, numbered by turn.tori checkpoint diff <id> <n>prints what turnnchanged, and<n> <m>spans a run of turns.--whysays who wrote each hunk (below), and--jsonprints the answer as data.
Who wrote a hunk
tori checkpoint diff <id> <n> --why prints the same diff with # why lines
above each hunk, one per run of lines that one turn wrote, the same answer the
hunk panel gives:
# why lines 12 to 14: turn 3 of fix the parser at 20261008T140200Z, Edit /repo/src/parse.ts# said: Splitting the tokenizer so the error carries its position.# why line 20: turn 3 of fix the parser at 20261008T140200Z, shell: Bash sed -i 's/old/new/' src/parse.ts@@ -10,6 +10,9 @@The time is the prompt’s, in UTC. # said: is the first line of what the
agent wrote before the call. Where a turn wrote the lines in several calls it
says one of: and lists them, the ones naming the file first, three at most.
Where nobody can be named the line gives the reason instead:
no session Tori recorded wrote this, it was there at the first checkpointolder than the changes Tori walks backwritten in a turn of <session>, which Tori ran but cannot read backwritten while <session>, <session> were each in a turn that could have written itwritten while no session Tori knows of was in a turn: by hand, or by a session Tori did not see
--json prints the whole answer instead, the diff plus a why list of each
file’s hunks and their claims. A chat running in a Topic’s home is refused with
why reads one worktree, and this session runs across a Topic's worktrees.
Over the socket, provenance.hunks answers the same question for any file in a
worktree, against the working tree.
Acting
tori spawnstarts a session and prints its id.--new-worktreemakes the worktree first (and waits for the project’s setup command when that is turned on).--promptis its first message and--attachadds files to it.tori steer <id> <text>sends a message to a live chat: a steer if a turn is running, its next turn if not. Run by an agent, a message to a chat it is not allowed to reach printsqueuedand waits for you to accept it.tori interrupt <id>stops a chat’s current turn.tori handoff <id> --to <agent>hands a live chat over to another agent and prints the new session’s id. See Handing a session over.tori session answeranswers a question or a permission prompt by id.--eachanswers a question that has several parts, one answer per part.tori worktree new <branch>creates a worktree and prints its path.tori checkpoint revert <id> <n>puts the session’s folder back to turnn. A backstop is taken first.tori open <path> --line <n>opens a file in Tori’s editor.tori open --browser [<branch>]opens the current worktree’s dev server, or another branch’s in the same project, in your default browser.
$ tori spawn --agent codex --new-worktree fix/retry --prompt "retry 5xx deliveries"0f3b7d5e-8c2a-4b1e-9d6f-2a7c4e1b9f30$ tori steer 0f3b7d5e-8c2a-4b1e-9d6f-2a7c4e1b9f30 "also cover 429s"Waiting on a session
tori wait <id> waits up to --timeout seconds (60 by default, an hour at
most) and prints the reason on its first line:
idle: the session finished its turn.asking: it is waiting on a question, printed on the next line asasking <id>: <question>. Answer it withtori session answer.needs_you: it is waiting on a permission prompt or a question of its agent’s own.tori session pending <id>lists it.limited until <time>: its turn was refused by a usage limit, which resets at that time. Claude only for now.ended: the session is gone. A session that was handed over says where, ashanded_toin--json.timeout: it was still working when the wait ran out.
Then comes the session’s last message. --json prints the whole answer:
state, reason, resets_at when limited, handed_to when handed over,
question and last.
Checking a session’s work
$ tori check 0f3b7d5e-8c2a-4b1e-9d6f-2a7c4e1b9f30failed failed cargo test3 files, +48 -6The first line is the verification verdict of
the session’s latest turn that changed code: verified, failed or
unverified, or no code changed when no turn did. Each check that turn ran
follows with how it came back. The last line counts every turn’s changes,
only in files the session wrote itself, so another session’s edits in the
same worktree are left out. With Mark unverified turns off in Settings > Chat it says
verification off instead of a verdict.
A worker may run tori check, so a reviewer can read its writer’s verdict
before reading the diff.
Handing a session over
tori handoff <id> --to <agent> starts a new session of that agent in the
same folder and closes the old one. The new session’s first message is a
brief Tori writes from the old one: its first prompt, its last reply, what
it was waiting on, its tori check result, your --note, and a reference
to the old session that the new agent reads the rest of with its
session_history tool. Nothing spends a turn summarising, so it works when
the old session hit its usage limit. --account and --model pick the new
session’s account and model.
The old session closes once it is idle, never while it waits on a question
or a permission prompt. A session can hand itself over: its own turn
finishes first. The new session takes the old one’s place: it belongs to
the same spawner and runs in the background if the old one did. Waiting on
the old id answers ended, with the new id as handed_to.
Only a live chat can be handed over. A worker cannot hand anything over, and a session the autopilot is driving is refused, the autopilot included.
A writer and a reviewer
A chat writing a change can ask a second agent to review it, wait for the verdict, and hand itself over when it runs out of room. From the writer’s shell:
$ me=$(tori whoami --json | jq -r .caller.id)$ reviewer=$(tori spawn --agent codex --prompt "Review the change in this folder. \ Run tori check $me first, then read the diff. Reply with blocking issues only.")$ tori wait "$reviewer" --timeout 600idleBlocking: the retry loop never stops on a 4xx.The reviewer is a worker: it runs tori check on the writer and reads the
diff, and it cannot spawn, steer, wait or hand off. If it asks a question,
tori wait comes back with asking and the writer answers it with
tori session answer.
When the writer’s context is nearly full, it hands the fix to a fresh agent and finishes its turn:
$ tori handoff "$me" --to claude --note "Stop retrying on a 4xx, the reviewer's one blocking issue"6a1d0c94-2f7e-4b3a-8e51-9c0d7f2b4a68Asking you
tori ask puts a question card in the chat that called it, raises the
needs you dot and a notification, and waits. It prints only the answer, so
a script can branch on it:
if [ "$(tori ask "Deploy to staging?" --option yes --option no)" = "yes" ]; then ./deploy.sh stagingfiIf the wait runs out it prints the question’s id and exits 2. A wait lasts
an hour at most, whatever --timeout says.
tori ask --wait <id> collects the answer later, and
tori ask --answer <id> <text> answers one on your behalf from another
shell.
From a terminal tab or a shell outside Tori, tori ask takes --approval
only, and the card draws in the tab or in a dialog. See
Approvals asked outside a chat.
Pull requests
tori pr create, tori pr review and tori pr merge each take the exact
commit they act on as --head-sha. pr create pushes that commit to the
head branch, never forced, and then opens the pull request; it is refused
if the local branch has moved past that commit. A review or a merge is
refused the same way if the pull request’s head is no longer the one named.
From a background session these three need --approval <id>: the id of an
approval you gave for exactly this draft. See
Autopilot.
From a terminal tab or a shell outside Tori they need your Approve too, but
tori asks for it itself: it prints tori: waiting for approval in Tori with
what it would do, the card shows in the tab or in a dialog, and the command
goes on once you click Approve or exits with rejected in Tori. tori attach, and tori spawn --mode with a mode that runs tools without asking,
wait the same way.
Autopilot
tori autopilot state prints the queue. start and stop turn the
autopilot on and off. item adds or updates a queue item, project sets a
project’s contract, and hold resolve withdraws a pending approval card (it
never approves one; only you do, on the card).
schedule add makes a schedule: at
each --cron time in your local time, a ship item for the autopilot with
--prompt as its work, or --issue to work on that issue. schedule list
prints each schedule with its next time, remove deletes one, and resume
starts one that three failed runs paused. Only a person can resume a
schedule.
tori loop --prompt <text> <entry>... queues one ship item per entry, each
with the prompt as its task: {item} in the prompt is replaced by the entry,
or the entry is appended when there is no {item}. With no entries it reads
one per line from stdin, so a list pipes in:
$ ls packages | tori loop --prompt "Move {item} to the new logger"queued item-3f1c8e20d5a94b7c apiqueued item-9a0e47b1c2d6f853 webskipped cli (already open)An entry whose item is still open in the project is skipped, so running the
same loop twice queues nothing new. A loop never starts work itself: the
autopilot picks the items up, each in its own worktree, up to its worker
limit. With the autopilot off they wait, and tori loop says so. See
Loops.
project --issues takes a JSON list of
issue sources and replaces the
project’s list. Each source has a repo and optional labels,
exclude_labels, milestone, assignee and extra, and its repo has to
be one of the project’s
issue repos. Pass [] to go back
to the issues assigned to you in those repos. A contract printed by
project shows each source as the search it runs.
What a caller may do
Not every caller gets every command. A session another session spawned (a
worker) can ask you questions and run tori check, but cannot spawn, steer,
wait, hand off or queue a loop. No agent session,
worker or not, can turn the autopilot on or off: that takes a person at a
shell or on a paired phone. A paired phone can read, steer, interrupt,
answer and approve, and start a plain chat in a folder, but cannot pair
another device. A chat can steer or read only the chats it is
related to. The full table is on the
MCP server page, since both
fronts share it.