On this pageOverview
Automation

Autopilot

Hand Tori a queue of issues and pull requests. It makes the worktrees, starts workers, watches them, and brings you each pull request, review and merge to approve.

Autopilot runs as an ordinary Tori chat session that reads a queue of work, issues to ship, pull requests to review, and starts a worker session per item. It never edits code itself, and nothing it does leaves your machine without you approving it first: opening a pull request, submitting a review, and merging are each their own approval, spent once, tied to the exact draft shown to you.

Turn it on and start it

  1. Open Settings (the gear in the title bar) > Autopilot and turn on Enable autopilot. A Cockpit and Workspace switch appears in the title bar.
  2. Click Cockpit, or press Cmd+Shift+J, to switch to the Cockpit.
  3. Click Set sail. The autopilot’s own session starts, on the agent, account and model in Autopilot model.
  4. Tell it what to do in Tell the autopilot…, for example work on #60, review https://github.com/hooli/api/pull/52, or work on #482 and #214, then #215.

Drop anchor stops it. Its session closes; the queue on disk and every worker stay as they are, and the workers go back to being ordinary sessions that notify you themselves. Set sail again and it picks up the same queue.

Once started, the autopilot starts again by itself when Tori launches, until you drop anchor. Only you can start or stop it, from the Cockpit, a shell (tori autopilot start) or a paired phone; no agent session can.

Turning Enable autopilot off stops it and hides the Cockpit.

Per-project contract

Each project autopilot works in has a contract: how work ships (a pull request, or just a local branch), how far it goes before asking you (asking before every step, or going ahead alone on anything that stays on the machine), and whether it queues new work on its own or proposes it and waits. A project with no contract set runs on the cautious defaults: pull request, ask before everything.

Term Choices Default
How work ships A pull request, or a local branch only Pull request
How far it goes Ask before everything, or go ahead alone until something would leave the machine Ask before everything
Picking up work Propose and wait, or start on its own Propose and wait
Workers run on The agent, account and model you pick for this project Not set
Where issues come from The issues assigned to you in the project’s issue repos, or the issue sources you list Issues assigned to you in the project’s issue repos
What one item may spend A dollar ceiling for each item, set by telling the autopilot (What work costs) Stop an autopilot item after in Settings > Chat

Tell the autopilot how a project should work and it records the contract, or set it yourself in the project’s project settings, under Agents (shown while autopilot is on): open Project settings from the project menu, or click the project under Project contracts in Settings > Autopilot. Each choice saves as soon as you pick it. Workers run on picks the agent, account and model the same way the autopilot’s own model is picked. From a shell, use tori autopilot project.

Issue sources

A project’s work does not have to live in its own repo. If you keep tickets in a separate repo, maybe one shared by several projects, give the project one or more issue sources. Each source is a repo plus what an issue there has to match:

Field What it matches
Repo The repo the issues live in, picked from the project’s issue repos. Required.
Labels Every one of these labels.
Exclude labels None of these labels.
Milestone This milestone.
Assignee Left out, issues assigned to you. any is anyone, or give a login.
Extra Raw GitHub search qualifiers added after the rest, such as sort:created-asc.

Labels and milestones can have spaces and colons in them, for example block 1 or Phase 1: Mac and Android. Only open issues are matched.

A source’s repo is one of the project’s issue repos, so list a tickets repo there first, under Issues in the project’s settings. A source naming any other repo, from the CLI or an agent, is refused with the reason, and a repo can’t leave the issue repos while a source reads it.

Once you set sources they replace the default, so if you still want every issue assigned to you in the issue repos, add a source for each. Review requests always come from the project’s own repo.

An issue from another repo is named by its repo, such as gettori/tickets#31. An issue in the project’s own repo stays #31. Starting one links its branch to the issue in the other repo, while the branch itself is made in the project’s repo. Its suggested branch name leads with that repo’s name, such as tickets-31-dogfood, so it never collides with the project’s own issue 31.

Set sources in Settings > Autopilot, under Projects. Add source gives you a new one on the first issue repo, and Repo picks another from the list. A source saved before its repo left the list still shows that repo. Labels and exclude labels are comma separated. Remove source takes one out. Nothing changes until you press Save sources, which saves the whole list at once. If a source is refused, for example because its repo is not one of the issue repos, the list stays as you set it and the reason shows below the list. With no sources, the editor says the project reads the issues assigned to you in its issue repos.

You can also use the --issues flag of tori autopilot project, or ask the autopilot to set them:

Terminal window
tori autopilot project --issues '[{"repo":"gettori/tickets","milestone":"Phase 1: Mac and Android"}]'

The queue

Work items are either ship (take an issue to a pull request or a local branch) or review (review someone else’s pull request). Autopilot can pick these up on its own from your assigned issues and review requests, or you can hand it one directly. Every item you see in the Cockpit, in flight, waiting, done, or failed, is one of these.

To hand it work, type in the Cockpit’s composer, for example work on #60, or ask it to review a pull request. For a ship item it reads the issue, titles the item, links a branch to the issue, makes the worktree, starts a worker on the project’s picks, and brings you a pull request to approve. A review item runs the same way, to one review for you to approve.

With pickup on, each poll of the host reads the project’s issue sources, or with none your assigned issues in its issue repos, plus your review requests. A repo your account can’t read is left out of that poll, and nothing from it is taken as closed. The first poll of a project, or of a source that is new or was just edited, only proposes, so an old backlog never starts itself. An issue that two sources both match becomes one item. An issue another project already has open is left to that project.

A project whose review rule is on starts its own reviews, so pickup files none of its review requests. With Create a worktree and review it, each review the rule starts shows up here as a running review item, and a later push reopens it. The autopilot takes it from there once the chat finishes, and brings you the verdict to approve.

When an issue leaves every list (it closed, it was unassigned, or it no longer matches its source), an item that has not started yet closes. An item already running is not stopped. It gets a note saying so, and the autopilot asks you whether to stop it. If a source fails to answer, or comes back with as many issues as one search can hold, nothing closes on that poll, since a missing issue proves nothing then.

The queue is on disk. Quit Tori and relaunch, and the same items are there; an approval nobody answered comes back pending. An item closes itself when its pull request merges.

Every reference the autopilot shows reads as a number and a place: the number opens the issue or pull request on the host, the place opens that worktree with the worker’s tab.

Schedules

A schedule hands the autopilot the same work on a clock, say “every weekday at 7, bump the dependencies and open a pull request”. Add one from a shell:

Terminal window
tori autopilot schedule add --name "Bump deps" --cron "0 7 * * 1-5" \
--prompt "Bump the dependencies and open a pull request"
  • --cron is five fields in your Mac’s local time: minute, hour, day of month, month, day of week. Shortcuts like @daily work too. A seconds or a year field is refused.
  • --prompt says what each run does, in your words. Pass --issue with an issue’s number or URL instead to have each run work on that issue. One of the two, never both.
  • --project is the project the runs work in. Left out, it is the project of the Tori terminal you run it from. A folder Tori does not trust, or that is not a git repository, is refused.

At each time, Tori queues a ship item named after the schedule, with your prompt as its contract, and wakes the autopilot, which starts it like any item it picked up on its own. Everything that leaves the machine still waits for your approval. With the autopilot off, the item waits in Waiting at the dock and starts when you set sail. With Autopilot switched off in Settings, nothing fires.

A time your Mac slept through, or that passed while Tori’s daemon was not running, fires once when it wakes, for the latest time missed, never once per missed time. Switching Autopilot on in Settings catches up the same way.

A schedule runs one at a time. While its last run is still open, waiting, running or taken over, the next time is skipped, and the open item gets the note “skipped a run of name while this one is open”. A schedule on an issue also skips while anything else holds that issue open, and pickup leaves an issue alone while a scheduled run holds it.

A run fails when its item ends failed. The autopilot also fails a scheduled run whose pull request was closed without merging, with the note “PR closed unmerged”. After three failed runs in a row the schedule pauses: it queues nothing, and the autopilot tells you once (once more if Tori’s daemon restarts). Resume it from the Cockpit or with tori autopilot schedule resume <id>. The autopilot cannot resume a schedule itself, and only runs after the resume count towards the next pause.

tori autopilot schedule list shows each schedule with its next time, or paused. tori autopilot schedule remove <id> removes one: a run still queued fails with the note “schedule removed”, and a run already going carries on with that note.

Loops

A loop hands the autopilot the same task over a list, say “move each package to the new logger”. Queue one from a shell:

Terminal window
ls packages | tori loop --prompt "Move {item} to the new logger"

Tori queues one ship item per entry, named after the entry, with the prompt as its contract: {item} is replaced by the entry, or the entry is appended when the prompt has no {item}. Entries come as arguments or one per line on stdin, blank lines dropped. --project is the project the items work in, the project of the Tori terminal you run it from when left out.

The autopilot is woken with each entry and starts the items like a scheduled run that names no issue: each in a worktree of its own, up to its worker limit, the rest waiting in the queue. Everything that leaves the machine still waits for your approval. With the autopilot off they wait in Waiting at the dock, and tori loop says so.

An entry whose item is still open in the project, waiting, running or taken over, is skipped, so running the same loop again queues only what finished or was never queued. A list that repeats an entry queues it once.

Pull request rules

A rule hands the autopilot a pull request that needs work after the chat that opened it is gone: “when a review requests changes on my PR, start a worker to address it”. Set it per project in Project settings > Automations, under When a watched pull request needs work, by turning on the switches under Hand it to the autopilot when:

  • A review requests changes
  • A check fails
  • It conflicts with its base

A rule only hears pull requests a chat watches, so it needs Let a chat watch a pull request on as well as Autopilot. The Needs line under the switches names both, Pull request watches and Autopilot, each with a dot that lights when it is on. It acts only once the watching chat is closed. A chat that is open, idle, working or stopped at a spend limit gets its usual wake instead, so a worker and a chat never edit one branch at once.

When it acts, Tori files a ship item named after the event, for example Changes requested on #482, under the project’s contract: proposed when the contract’s pickup is Ask, queued when it is Auto. A project with no contract gets nothing. The watch ends, and the chat, if you open it again, hears only that the pull request was handed to the autopilot. One pull request gets one item at a time: while an item for it is open, more news on it goes to that item rather than starting another.

The worker gets its own worktree on the pull request’s head, apart from the chat’s, fixes what the event names and commits. The fix reaches the pull request through a Push card: Tori pushes that exact commit to the pull request’s branch, never forced, once you approve. A pull request from a fork cannot be pushed to, so the card is refused there. The item ends when the pull request merges.

At most per hour, 3 by default, caps how many items a rule files. Once it reaches the cap inside an hour the rule pauses and files nothing more until you resume it from the Cockpit. The autopilot cannot resume a rule.

Keep fixing until checks pass

A project can let the autopilot fix a failing pull request by itself, push the fix without a card, and come back to you only to merge. Turn it on in Project settings > Automations under Keep fixing until checks pass, with Push fixes without asking. It needs the Autopilot on, but not Let a chat watch a pull request.

It covers every open ship item in that project with a pull request: one the autopilot opened, and one a pull request rule handed it. Tori watches each of those pull requests itself, on the same check it makes of the host every 2 minutes:

  • A check fails. For a GitHub Actions job, Tori first reruns the job once, so a flaky test costs nothing. Only a second failure on the same commit reaches the autopilot, which tells the item’s worker which check failed. The worker reads the end of the job’s log (the last 300 lines), fixes the cause and commits, and the autopilot pushes that commit to the pull request’s branch, never forced, with no card. A check from another service has no log Tori can read, so the worker gets its summary and link instead, and it is not rerun. A log GitHub has expired says so, and a GitHub sign-in that cannot read Actions logs is named as the reason.
  • The checks pass. When GitHub says the pull request can merge, the autopilot raises a Merge card: squash, at the commit that passed. Nothing merges until you approve it, and a branch that moved after you approved is refused. A pull request that still waits on a review or a branch rule gets no card; the autopilot tells you instead.
  • It conflicts with its base. The worker brings the base in, and the fix is pushed the same way.

Attempts, 3 by default, caps how many fixes are pushed without asking on one pull request. After that every push asks you on a Push card, as before.

Stop when a person reviews or comments, on by default, ends the unasked pushes the moment someone comments on the pull request or submits a review. From then on every push asks you. Comments from bots (a coverage report, a preview link) and approvals do not stop it.

A worker or the autopilot that reaches its spending ceiling gets no more turns from the loop until you raise or clear it. Like the rest of the polling, the loop only runs while Tori is open.

Approvals

Nothing leaves the machine on the autopilot’s say. Opening a pull request, submitting a review, pushing to a pull request and merging each wait on a card (the one exception is a fix pushed under Keep fixing until checks pass), and the card shows exactly what would be posted: the title, branches and body of a pull request; the verdict and every line comment of a review; the commit and branch of a push; the method and head commit of a merge. You can approve it, edit it, reply to the worker, or dismiss it.

An approval is spent once and is tied to that exact draft and commit. If the branch moves after you approve, the push is refused rather than sending something you did not read.

A worker’s permission prompts are not the autopilot’s to answer. They wait for you, on the card or on a paired phone. A worker’s questions do reach the autopilot, which may answer the ones it can and brings you the rest.

While it runs

  • Workers are locked while the autopilot drives them: nothing to type into, nothing to close. Stop the autopilot and they go back to being ordinary sessions that notify you themselves.
  • Waiting costs no tokens. The autopilot is woken by Tori, not by polling, when a worker asks a question, wants a permission, ends, goes idle, stalls, or its pull request changes.
  • A worker that sits silent mid-turn past Worker stalled after is flagged as stalled.
  • Starting the autopilot resumes its last session. If that session died, or you changed its agent, it starts a fresh one that picks up the same workers, holds and cards.
  • With Conflict radar on, it holds back the later of two running items that are changing the same file until the first one’s pull request merges, and tells a worker whose branch would conflict with its base to bring the base in first.
  • No agent session can turn the autopilot on or off. Only you can: from the Cockpit, a shell, or a paired phone.

The Cockpit

Try it
Holding course

One call for the captain.

The ship holds its heading while you decide.

ship #41 and #7, then PR #52 and #19
Two workers on deck: #41 and #7. PR #52 and #19 wait at the dock.
#41 passes all 18 ranking tests. Its PR goes to GitHub, so it needs your approval.
Captain's call1
PR#41Weight title hits in rankingnow

Open a draft PR from fix/search-ranking into main. 3 files, +46 -9, tests pass.

⌘⇧JworkspaceAapproveRreply
The Cockpit with two workers on deck, one in hooli-search and one in pied-piper. Approve, edit, reply to or dismiss the calls, drop anchor, queue more work from the composer (try: work on #60), or Watch a worker to see the session the autopilot is driving.

The Cockpit has three columns:

  • Crew on deck: the workers running now, each a card ringed in the colour of its state, with its issue number, title and place (work -> api -> feat/webhooks), the last lines of its log, and what it is doing. Watch opens the worker’s session. Beside a worker’s branch is what the item has cost so far. Under it, Waiting at the dock lists queued items, with what each waits for and, for one from an issue, what it will likely cost, and under that Tide table lists your schedules: the name and cron, what each run does, and when it fires next, or waiting on the open run its next time would be skipped for, and how its last run ended. A paused schedule says paused after 3 failed runs with a Resume button. Under that, Pull request rules lists each project with a rule on: what it acts on and how many items it filed this hour against its cap, for example 1 of 3 this hour, or paused at its hourly limit with a Resume button. An item under Keep fixing until checks pass shows its fixes against its attempts, for example fix 1/3, and who stopped the loop when someone did, for example fix 1/3, stopped by @ana.
  • The middle: a banner saying where things stand, the autopilot’s own chat, and its composer. Decision cards appear in the chat under Captain’s call.
  • Ship’s log: what happened, newest last, with what needs you marked.

The banner plays one of six scenes picked from the local hour, dawn to night, and turns to a storm when more workers are out than Workers at once allows. With nothing to do it says The deck is quiet. Hand the autopilot a ticket or a PR.

Leaving the Cockpit detaches from the autopilot’s session without closing it.

Decision cards

Anything that needs your call is a card:

Card Asks
PR Open this pull request: title, branches and body
Review Submit this review: verdict, body and every line comment
Push Push this commit onto a pull request’s branch
Merge Merge with this method at this commit
Question from worker A worker’s question the autopilot could not answer

Each has Approve, Edit, Reply and Dismiss. Edit lets you change the draft before approving; Reply answers the autopilot in the chat; Dismiss declines.

The popup

Cmd+L opens the autopilot as a popup over your workspace, without leaving it: what is In flight, the Decisions waiting on you, and a composer. Its header says how things stand, for example Working on 2, 2 decisions. The expand button opens the full Cockpit. Escape or a click outside closes it.

Sessions the autopilot drives

A worker the autopilot is driving cannot be typed into: its composer is replaced by a bar, The autopilot has this session, saying what it is doing now, with Back to autopilot and Stop to type. Stopping to type stops the whole autopilot, which hands every worker back to you. Workers cannot be closed or deleted while it drives them.

Settings

Settings > Autopilot:

Setting Default What it does
Enable autopilot Off Shows the Cockpit switch. Off stops the autopilot and hides it
Autopilot model Claude The agent, account, model and effort the autopilot’s own session runs on
Worker stalled after 20 minutes How long a worker may sit silent mid-turn before the autopilot hears it stalled. Waiting on your answer does not count
Workers at once 2 How many workers run at a time, kept one under Warn above in Settings > Chat. The rest wait at the dock
Compact at context Agent decides A context percentage past which Tori sends /compact between the autopilot’s own turns

Workers run on each project’s own picks; see Per-project contract.