Skip to main content

Pilot

A cross-project view of every agent run: grouped by project, ordered by what needs you, with attention bands, park, snooze, and a demand chip per project.

Reviewed

What Pilot Is

Once you are delegating across more than one project, the question stops being what is this agent doing and becomes which of the twenty things I started actually needs me right now. Nothing in a per-project view answers that. You end up switching projects to check, which is the expensive part.

Pilot is the answer to that question. It is a read-across list of every agent run in every project and every scratch workspace, including runs whose view has been evicted from memory, grouped by workspace and, inside each workspace, ordered by what needs you. The unit is the run (one agent terminal in one worktree), not the project. A project-level count tells you where to go; a run tells you what is stuck.

The same list can be cut on a second axis. Scoping to one project keeps every row, band and control exactly as they are and regroups them by worktree instead of by workspace, for the case where the project you need to triage is the one you are already in.

In the interface the feature presents itself as "View all agents": that is the name on the keyboard shortcut, on the action in the launcher, and on the palette's own header, which reads All agents. Pilot is the internal name for the surface, and the one this documentation uses because it is shorter and unambiguous. Both refer to the same dialog.

Opening it

  • Option-Command-O Control-Alt-O Control-Alt-O toggles it from anywhere.
  • Option-Command-I Control-Alt-I Control-Alt-I opens it already scoped to the project the current view owns — see Scoping to One Project.
  • The action palette, under View all agents.
  • The All agents button in the footer of the project switcher, one step further out than the switcher itself: the switcher picks a project, this shows every project at once.

Nothing about the dialog persists. Not whether it was open, not the search query, not the filter segment, not the scope. Every opening starts from the same place, so what you see is always the current state of the fleet rather than a view you configured last week and forgot about.

Groups follow workspace order, not severity: Harbor Freight holds the only demand and still sits second.

Fleet vs Pilot

Fleet and Pilot both deal with many agents at once and are easy to confuse. They answer different questions and sit on opposite sides of the read/write line.

  • Fleet is a write surface inside one project. You arm a set of terminals, and from then on your keystrokes and bulk actions fan out to all of them. It is a mode you enter.
  • Pilot is a read-across view over every project. It never sends anything to an agent. The only things you can do from it are open a run, park it, narrow the list, or regroup one project by worktree. It is a dialog you glance at.

The overlap is that both speak the same state vocabulary, which comes from the agent state model and is also what drives worktree card badges and the sidebar's quick filters.

Attention Bands

Every run lands in exactly one of nine bands, ordered worst first. A run that has stopped and may not restart on input outranks one merely asking a question, which outranks a run that is nominally working but has gone silent, which outranks finished work, which outranks work still in flight. Everything below that is context rather than a demand.

BandLabel on the rowGlyphColorArises from
blockedBlockedProhibition circleDangerWaiting with an error as the reason
needs-youWaitingHollow circleAmberWaiting on your input
quietQuietSpinner, not spinningAmberWorking, and silent past the stall threshold
reviewReady for reviewCheck circleBlueCompleted and not yet acknowledged
runningWorking / DirectingSpinner / interacting circleGreenWorking or directing
doneFinishedCheck circleNeutralCompleted at or before the workspace's completion watermark
parkedParkedPause circleNeutralYou parked it
snoozedSnoozedDashed circleNeutralYou snoozed it and the snooze is still live
idleIdle / ExitedDotted circle / exited circleNeutralAnything else

A demand is exactly three bands: Blocked, Waiting, and Ready for review. That is the load-bearing idea of the whole surface. A working agent is not a demand: it is doing its job. A finished-and-acknowledged run is not a demand: you already saw it. A parked or snoozed run is not a demand because you said so.

Sitting alongside the demands is a slightly wider set. The attention bands are the three demands plus quiet. Nothing is asking you for anything when a run goes silent, so folding it into the demand count would make "3 agents need you" promise three conversations and deliver two. But a stall is still the thing a header has to be able to point at, so the group chips and the footer's summary are computed over the attention bands while the demand counts stay at three. The two are deliberately not the same number, and each is used where it is true.

Note
"Attention" names two different things on this surface, and they are not the same size. The attention bands are the four above — the set the group chips and the footer summary read. The Attention segment in the filter bar is narrower: it holds blocked and needs-you only, because review and quiet each have a segment of their own. This page always writes the filter one as the Attention segment.

Two precedence rules follow. A park beats every state, including blocked. Parking is you saying "this one does not need me until further notice", and a model that second-guesses that promise the moment something looks urgent is a model you have to keep re-checking, the exact cost parking exists to remove. And a run carrying both a park and a snooze reads as parked: the stronger, indefinite decision wins.

Color is reserved for the three demand bands, amber for quiet, and green for working. Shape always carries the state as a second channel, so the list is readable without relying on hue, and the state is in the row's accessible name either way. The invariant is not that every band draws a different shape — review and done share the app's finished mark, and quiet shares working's spinner — it is that no two bands share a shape and a tone. The vocabulary deliberately matches the worktree sidebar's quick filters: one state with two names across two surfaces is a vocabulary you have to learn twice.

The quiet band

A run in the working band that has produced no output for more than ten minutes leaves running and becomes quiet. It keeps working's spinner, because a silent run is working — that is the whole difficulty with it — but the spinner stops turning and takes the amber of the thing that may need a hand. A working glyph next to a long silence is the one combination the age column cannot express on its own: without it, "working" and "working but wedged" render identically.

The silence is a band rather than an annotation because an annotation could not be counted. Previously the only trace of a stalled run was a chip on one row: the fleet counts, the filter bar and the group summaries all reported it as ordinary work in flight, so the one run worth finding was the one nothing pointed at. As a band it has its own segment, its own count, its own group chip and its own footer sentence.

It ranks directly under needs-you and above review, which is the product decision it is easiest to argue with. A stall that goes unseen costs the rest of the morning; a hand-back you read an hour late costs a minute.

The silence is anchored on the later of the run's last output, its entry into the current working stint, and its spawn — so a run resumed after twenty quiet minutes of waiting does not inherit that silence and get stamped quiet 20m before it has had a chance to make a sound. The threshold lives in the main process, which only puts the silence on the wire once it has been crossed, so nothing in the interface needs a clock or a threshold of its own. The snapshot polls every five seconds, so the crossing shows up within one poll.

Note
The band is named for what was observed, not for what it means. The fleet knows an agent has said nothing for twelve minutes; it does not know the agent is stuck. An agent midway through a long compile and one wedged on a dead socket look identical from here, and only one of them is a problem — which is why the segment is called Quiet and the footer says "gone quiet" rather than "stalled".

Reading a Row

A row is one line, about 32px tall, in this order: state glyph, the agent's brand mark, the run title, the park note if there is one, and a right-aligned age in a tabular column so every row's age starts at the same offset.

Two things are deliberately not drawn. The status word, because the glyph already says it. And the worktree label, because it is a scratch workspace's UUID as often as it is a branch name, and it was charging the truncating title for width to say nothing it needed. Both still ride the row's accessible name and both are still searchable.

One row, one clock

The age column is the row's only clock, and which duration it measures belongs to the band:

  • A parked row is aged from the park.
  • A snoozed row is aged from the snooze.
  • A quiet row is aged from the silence.
  • Everything else is aged from the run's last state transition.

Otherwise parking a run that had been waiting three hours would immediately read "Parked · 3h", which answers a question the row is no longer about, and two silent agents would rank by how long they had been working rather than by how long they had said nothing. One function decides it, and the row's clock, the within-band ordering and the initial cursor all read it — so the number on screen, the position in the list and the row Enter opens can never be measuring three different things.

Only quiet takes a word in front of the number, and it is drawn in amber: quiet 12m. Every other band gets a bare duration in the neutral tone, because "how long has it been like this" needs no qualifier. A quiet row used to draw two clocks — a mid-row quiet 12m chip and a right-hand 47m — with only one of them labelled, so the reader had to work out which of the two the surface wanted them to act on. There is now one column, one number, and a word where naming is what disambiguates it.

The accessible name spells the same thing out as a sentence, per band: "waiting for 38 minutes", "finished 38 minutes ago", "parked 3 minutes ago". A quiet row carries both durations there, since the name has room for what the column does not: "quiet for 12m, working for 47m".

Grouping and Order

Groups are workspaces (projects and scratch workspaces alike) ordered by workspace MRU: most recently opened first, then name, then id to settle ties into a stable total order. Severity never lifts one project above another. A blocked agent in a project you left an hour ago does not outrank the one you are working in now, and because the order keys on when you last opened a workspace rather than on activity inside it, the list does not reshuffle between openings you did not cause. Spatial memory is worth more here than a marginally better ranking.

Rows within a group are ordered worst band first, then oldest first. Oldest-first is the anti-starvation rule: a stream of fresh completions must never bury the run that has been stuck for forty minutes, which is the exact failure that teaches you to stop trusting the top of the list. "Oldest" is measured on the band's own clock, so parked rows order by oldest park and quiet rows by longest silence.

Group headers are structure, not options. Arrow keys walk agent rows and never stop on a heading. A workspace header carries the workspace's tile, its name, a muted Current marker if it is the workspace you are already in, the attention chips, and — on a project with a worktree axis — a chevron saying it can be drilled into.

Where the cursor starts

The highlight opens on the worst row in the fleet's attention bands: worst band first, then oldest by that band's own clock. The list scrolls to it. If no run is in an attention band, nothing is singled out.

Groups are ordered most-recently-opened first, which answers "where was I" — the right question for finding a project and the wrong one for finding a fire. Left alone, opening the palette put the cursor on whatever happened to be at the top of the project you were last in, so the surface built to answer "is anything asking me for something" opened with Enter aimed at something that wasn't.

Reordering the groups by severity would answer it too, and was rejected: workspace MRU is the one ordering that does not move while the dialog is open, and buying triage with a list that reshuffles under a reader costs more than it returns. Moving the cursor costs nothing. The rows are already ranked worst-first inside every group, so the worst row in the fleet is just the best of those firsts.

It is a starting point only. The moment you arrow, it is dropped, so a run that blocks while you are reading cannot pull the highlight off the row you are on.

The attention chips

A group header holding anything worth pointing at draws one chip per band — the band's glyph in the band's hue, with its count — worst first, with empty bands omitted. Five headers then answer "which project is on fire" without reading a single row.

"Worth pointing at" is the attention bands, so a chip can be blocked, waiting, quiet or ready for review. It is not the narrower Attention segment from the filter bar, which admits only the first two — a project whose one unreviewed completion is its only news still gets a chip, and the segment that shares the word would show you nothing.

One chip per band rather than one chip for the group, because the older form could lie. It drew the worst band's glyph beside the total demand count, so a project holding one blocked run and one waiting one rendered as a red prohibition sign beside "2" — and there is no honest reading of that other than "two blocked". A pair of chips costs about twenty pixels and says the true thing.

Two chips is the realistic ceiling and three the absolute one, so the strip never grows to outweigh the name beside it. The counts are recomputed rather than inherited whenever a search or a filter narrows the group, so a header can never announce a blocked run that the query has already filtered out from under it. The same chips ride both axes, so a worktree's chip cannot come to mean something different from a project's, and the group's accessible name carries the same counts in words.

Order holds under the pointer

The ranking is live: a run that blocks moves up while you are looking at it. But the order freezes from the first pointer movement over the list, and releases again when the pointer leaves, the keyboard takes over, or the window is backgrounded. A row cannot move out from under a click. Only positions are held, never the rows themselves, so a glyph or an age still updates the instant it changes.

Search and Filters

Typing filters immediately. The query matches the run title, the worktree label, the agent name (the brand is drawn as an icon, but "codex" is a plausible thing to type) and the park note. Your own words about a run are often the string you remember it by. A workspace name matching admits every row in that workspace.

Matches stay grouped rather than flattening into one relevance-ranked list, and band order is preserved: demoting a blocked agent because a fresher one matched the query better would defeat the point of the surface. That is exactly why matching is gated on match quality rather than a bare subsequence test: with the ranking held fixed, a weak match is not sorted away from a strong one, it is interleaved with it, so quality has to be settled at the match or not at all.

Under the search box is a seven-segment radiogroup, with All as the null option. Note that the segment named Attention is narrower than the attention bands the chips and the footer count: it holds blocked and needs-you, while review and quiet have segments of their own.

SegmentBands it admits
AllEverything
Attentionblocked, needs-you
Quietquiet
Workingrunning
Finishedreview, done
Parkedparked
Othersnoozed, idle

Points worth knowing about that table:

  • The six narrowing segments partition the fleet. Every band is in exactly one of them, so their counts sum to All. A bar reading "All 12" over segments totalling ten invites you to work out which two runs the surface is not telling you about, and there is no answer you can reach from the bar.
  • Other is what makes that true: snoozed and idle are the two bands that belong to none of the questions the other segments ask. An exited shell is not working, not finished and not asking; a snooze is your own silence and must not read as a demand. It is named for what it is rather than borrowing a state's name it would then misapply to the other member, and it carries no glyph and no hue, because nothing in it is news.
  • The Attention segment deliberately excludes review, so the footer's demand sentence and the segment cannot disagree about what is outstanding. The segment is "an agent is stopped and cannot continue without you"; a completed run waiting to be looked at is a different errand, and it lives under Finished.
  • Parked gets a segment of its own because it is the one band you authored. "Show me everything I shelved, with my notes" is a real question, and a run parked while waiting has to be findable somewhere other than the bottom of All.
  • Counts are computed over the query-intersected population before the segment applies. Counting afterwards would give every segment the length of the list already on screen, which tells you nothing you cannot see.
  • Selection follows focus, so arrowing across the bar applies each filter as you reach it.

The search box and the filter bar intersect with AND, so "show me the blocked agents in this repo" is one question rather than two.

Why the first segment is called Attention

The bucket holds blocked as well as needs-you, and calling a pair by the name of one of its members made the segment contradict itself the moment it drew the blocked glyph: a red prohibition sign beside the word "Waiting" is two different claims about the same two runs. Beside "Attention" it reads correctly — two runs want you, and the worse of them is blocked.

The row still says "Blocked" or "Waiting". Those are state names and they stay narrow, because a row is never ambiguous and so gets to be exact. Only the bucket that holds both takes a name wide enough to cover them, and the sidebar's quick-filter bucket — the same union — carries the same word rather than being left to drift.

"Stuck" was tried first and reads as a fault. An agent holding for your approval is not broken, and a bucket that says it is teaches you to flinch at the ordinary case. "Attention" says what the bucket wants — a look — without claiming anything about why, which is the only thing true of both an errored run and a polite question.

When a segment is hued

  • Working and Quiet are hued unconditionally — green and amber. Neither is a demand, but green for working is the vocabulary the sidebar, the assistant header and the panel chrome already speak, and a run that has gone silent is a live fact about the fleet, which is exactly what this surface hues.
  • Attention and Finished are hued only while they actually hold a demand. Finished holding three acknowledged runs and Finished holding three unreviewed ones are the same number and opposite situations.
  • Attention escalates. Holding a blocked run, it swaps the amber hollow circle for the blocked glyph in the danger hue. A segment whose glyph never moves can only ever describe one of its members. Finished does not escalate, because review and done are the same app-standard check circle at two tones — that mark is not this surface's to reinterpret, so the distinction rides the hue and, for anyone the hue cannot reach, the segment's spoken name ("including 2 ready for review", or "all ready for review").
  • Parked and Other never hue. Parked is your own silence, and a colored segment would re-demand the attention parking just released. Other has no glyph at all.
  • An empty bucket keeps its glyph and its "0" and mutes the glyph, so the zero registers without having to be read.

Scoping to One Project

A project with twenty agents spread across eight worktrees is one long section in the fleet list, and "which unit of work owns these agents" is a question that section cannot answer. Scoping cuts the same population on the other axis: same rows, same bands, same query box, same filter bar, same footer — grouped by worktree instead of by workspace.

The dialog's header changes from All agents to Agents by worktree, and a breadcrumb reading ‹ All agents / project name appears above the search box. Everything downstream of the scope — the counts, the segments, the summary, the empty states — is computed over the scoped population, so a scoped opening's numbers describe the project rather than the fleet behind it.

The main checkout leads, and the worktree holding the demand does not climb above it.

Three ways in, three ways out

In:

  • Option-Command-I Control-Alt-I Control-Alt-I from anywhere, which scopes to the workspace the current view owns. It is read off the view, not off whatever the app is globally pointed at.
  • ⌘↵ (Ctrl+↵ on Windows and Linux) on the highlighted run, which drills into that run's project. This is the gesture the footer's Worktrees hint advertises, and it lands on the selected row because that is where the arrow keys already are — headings are not in the arrow-key domain.
  • Clicking a project group header. The chevron that says a header is clickable is only rendered on a project that can actually be drilled, so the affordance never promises a list identical to the one already on screen.

Out:

  • The breadcrumb's All agents button.
  • Option-Command-O Control-Alt-O Control-Alt-O , which widens to the fleet rather than closing. The pair is one axis — Option-Command-I Control-Alt-I Control-Alt-I narrows to the project, Option-Command-O Control-Alt-O Control-Alt-O widens to the fleet — and a second press from the fleet still closes, so the toggle is intact. Closing outright from a scope was the wrong half of that pair: it made the "show me more" key the one that showed nothing, and dismissal already belongs to Esc.
  • Backspace, but only with the search box empty. The box owns focus by default, so a bare Backspace is an editing key first and a navigation key second; gated this way it can never take a character you meant to remove. It is the same arbitration Home and End already get, and it holds wherever focus sits, so you never have to know where the caret is to predict what the key will do.

Esc is deliberately not one of them. It dismisses the dialog, the way it does everywhere else in the app. The park editor earns a first press because it is a mode: it replaces the body, owns focus, and holds an edit you have not committed. A scope is not a mode — it is a narrowing of the same list, drawn in the same place, with a breadcrumb and an empty-box Backspace already saying how to widen it. Putting it under Esc too would make the key that dismisses every other dialog take two presses here, with the first landing on a surface you had not asked to see.

Option-Command-I Control-Alt-I Control-Alt-I pressed while Pilot is already showing that exact target closes it, which keeps the chord a true toggle. "That exact target" includes the fallback case below, because a fallback opening is the plain fleet plus one line of explanation — two openings that differ only by that line are two different answers.

When scoping is offered

Regrouping has to say something new, so a project is drillable only when it has a genuine worktree axis. That is true when either of these holds:

  • Its runs fall into two or more worktree buckets, counting the runs the snapshot files under no worktree at all as a bucket of their own.
  • The current view's worktree store knows about two or more worktrees — how many the project actually has, which its runs cannot answer.

The second half exists because a fleet row is only created for a terminal the app classifies as an agent. A plain shell, a dev-preview pane and a trashed panel contribute no bucket, and a worktree nobody has opened a terminal in contributes none either. Deriving the axis from runs alone tracked where agents happened to be sitting rather than whether the project has an axis at all: a project with three worktrees whose agents shared one of them failed the test, and closing an agent could take the chord away.

The two counts are OR'd and never joined. Comparing individual identifiers — a run's worktree id against a worktree-store key, or against the project's own path — puts two independently minted spellings of one directory beside each other, and this app has been bitten by that before. Aggregates cannot diverge that way.

The store's count is only ever lent to the one project the current view owns, because that is the project it describes; a neighboring project is judged on its runs alone. And a scratch workspace never scopes: it is not a git repository, so no count of its worktrees can be anything but zero, whatever a store happens to be holding. The chevron and the chord apply the same test on the same displayed rows, so the two forms of one gesture can never disagree about which projects have an axis — and a query that narrows a project down to a single worktree withdraws both rather than offering a regrouping of what is already one section.

When the chord cannot scope

Option-Command-I Control-Alt-I Control-Alt-I on a project with no worktree axis opens the whole fleet, and says so, in a line above the search box:

project name has nothing to group by worktree, so this is every agent

The fallback lands on a surface byte for byte identical to the one the unscoped chord gives, so without a word here the key would be indistinguishable from a dead one. It stays a line of text: nothing has gone wrong, there is nothing to retry, and you are already looking at the surface it describes — a toast or an inbox entry would outlive the opening it belongs to and interrupt for a routine outcome. It is a role="status" region, because the whole point is that this is not the surface that was asked for, and a screen reader hearing only the dialog's "All agents" label would be told exactly what the unscoped chord says.

It appears only when the app can actually name the workspace and its topology is known: a line that says something has no worktree axis without saying which something is worse than the silence it replaces, and a project whose view store has not hydrated yet has not answered the question at all. And it clears the moment its premise stops holding — create a worktree, or launch an agent in a second one, and the same press would now scope, so leaving the line up would put a chevron offering the drill beside a sentence saying there is nothing to group.

Worktree groups

Order is main checkout first, then the no-worktree bucket, then label, then id. Severity never reorders them, exactly as it never reorders projects: a worktree whose agent blocks does not climb over the others, it turns its own header's chip on and stays where it was.

The root checkout leads because root work is the most likely to be what you are working on right now, and burying it under thirty alphabetically-sorted branch checkouts hides it. It only leads where the app can name it — the drill is reachable from a neighboring project's heading too, and a view's worktree store describes its own project only, so a foreign project keeps the plain label order, which is the honest answer there.

No worktree is what it says and not "project root". A run loses its worktree both when it was launched with no worktree target and when the pty host positively proves the worktree belongs to another project, and the two are indistinguishable from here. It sits high because a live agent with no worktree is an anomaly worth seeing.

Labels are the shortest trailing path fragment that tells each worktree from the others: a basename is the part worth reading, but two worktrees in one project sharing a basename is ordinary, and two headers reading the same word file their agents under a distinction you cannot see. Only the paths that actually collide grow a segment.

A worktree header carries no git state — no branch, no dirty count, no ahead/behind. It is one compact line: a glyph, a name, and the attention chips. The fleet snapshot is global and git state is not; branch, dirty count and divergence live in the per-project worktree store, which exists for the one project a renderer view owns and for no other. The question a worktree heading answers is which unit of work owns these agents, not whether that branch is safe to push. Twenty to thirty worktrees in one project is the case the header is sized for.

Searching keeps working the way it does in the fleet, with one addition: a query that matches the project's name still admits its rows after the project heading is gone. Drilling in replaces that heading with worktree labels, and the project name is how you found the project to drill into in the first place — without this, the query would suddenly match nothing and empty the very list the drill just opened.

A scoped list with nothing in it says "Start an agent in project name" rather than naming every project. If the scoped project drains entirely while you are looking at it, you get that empty state rather than a silent drop back to the fleet: a surface that navigates itself somewhere you did not ask for is harder to follow than one that says it is empty and leaves the way back on screen.

Nothing about a scope persists. It resets to the fleet on every close, so a project removed in the meantime cannot come back as an empty heading. A scope change also drops the park editor — otherwise you would land in one project's worktrees with another project's park note open on top of them — and releases the pointer's order hold, whose held ids belong to one axis and would strand every incoming group at the tail of the other.

Keyboard Model

Physical focus stays in the search box, which drives the list through aria-activedescendant.

KeyAction
Option-Command-O Control-Alt-O Control-Alt-O Toggle Pilot ("View all agents"). While scoped to a project it widens to the fleet instead of closing
Option-Command-I Control-Alt-I Control-Alt-I Open Pilot scoped to this project's worktrees; pressed again on the same target, it closes
TypingFilters the list; focus stays in the search box
↑ / ↓Move between agent rows; group headers are skipped
Home / EndFirst / last row while the search box is empty; otherwise they are ordinary caret keys
TabMove real focus from the search box to the active filter segment
← → ↑ ↓ on the filter barMove between segments; selection follows focus. Home / End jump to the first / last segment
EnterOpen the highlighted run, switching workspace first if it lives elsewhere
Alt+Enter (⌥↵)Park the highlighted run, or edit its park if it is already parked
⌘↵ / Ctrl+↵Regroup the highlighted run's project by worktree
BackspaceLeave a project scope, while the search box is empty; otherwise an ordinary caret key
EscClose Pilot, or return to the list while the park editor is open

The two Enter chords are checked in a fixed order, park before drill, so Alt+⌘+Enter parks rather than regrouping the project. And a modified Enter this surface cannot act on right now does nothing at all rather than falling through to Open: ⌘↵ over a project with no worktree axis, a held ⌘↵ whose key repeat lands after the first press already drilled, or ⌥↵ over a snapshot too stale to park against would each otherwise open a run on a keystroke that asked for something else. Doing nothing is the right answer to an unavailable verb; doing a different one is not. Shift+Enter is deliberately outside that set and stays plain activation.

The footer hints mirror this — ↵ Open, ⌥↵ Park / Edit park, and ⌘↵ Worktrees — and all three are buttons you can click, not inert keycaps. As spans they made Park and Worktrees keyboard-only in practice: driving the palette with the mouse, you could open a run by clicking it and had no way at all to park one, and the drill gesture's only pointer form was an undiscoverable click on a heading that gives no sign of being a control. The keycap sits inside the button rather than beside it, so the accessible name is "⌥↵ Park" and voice control's "click Park" still matches on the visible word.

All three disappear while focus sits on a filter segment or the list is empty, because a footer promising a key that visibly does nothing is worse than no footer. Park also disappears when the snapshot is not live, since a park is validated against a healthy snapshot before it is accepted, and Worktrees disappears with its target.

Parking a Run

Parking is intent, not state. The agent underneath keeps doing exactly what it was doing; nothing is paused, interrupted, or signalled. What changes is how every attention surface treats the run: it drops to the parked band, stops counting toward any demand count or attention chip, and leaves the Attention segment for the Parked one.

The point is that a deliberate wait stops reading as something that needs you. "This one is blocked on the migration finishing in the other repo" is a decision you made once; without a way to record it, that run re-asks you the same question every time you open the list, and the list stops being trustworthy.

The editor is a mode of the Pilot dialog rather than a second dialog stacked on it. It has two fields:

  • A Note, up to 500 characters, prompted with "Why is this parked? (optional)". It shows on the parked row, truncated to at most 45% of the row width so it shares space with the title rather than starving it, and it is carried in full in the row's accessible name and in the search index.
  • A Park until radiogroup. Its first option is "I unpark it myself" which is an indefinite park with no release condition. The remaining options are the other runs in the fleet: pick one and it becomes the park's gate. A run cannot gate its own park.

The buttons are Park (or Update park when re-parking), Unpark (only shown when the run is already parked), and Cancel. Enter commits from the note field and from the gate list.

The editor is a mode of the same dialog, not a second one stacked on it.

How a gated park releases

Gate release is edge-triggered and time-fenced. A park gated on run B lifts when B transitions from busy (working or directing) to ready (idle, waiting, completed, or exited) with a timestamp strictly after the park was created.

Both halves matter. The edge requirement makes the common flow work without a race: "park A until B is free" is usually said while B is still waiting, just before you hand B the work whose completion the park is actually about. The time fence closes the other half: a busy-to-ready edge that happened before the park but was still in flight must not count as the gate coming free.

Ready is deliberately broader than "completed". Completion is pattern-detected from output, and most agents end a stint sitting in waiting and never emit a completion at all.

Note
If the gate never runs again, the park holds until you lift it by hand. That is the honest consequence of an edge-triggered rule, and it is stated rather than papered over with a timeout that would fire at an arbitrary moment.

Trashing a gate releases the parks it held, because a park that can no longer release must resurface rather than hide its run forever. Trashing the parked run does not unpark it: trash is restorable, and restoring a run should restore the intent recorded against it.

Park released

When a park lifts on its own you get a notification titled Park released, with a message naming the run and the reason ("its gate came free", or "its gate terminal closed") and your note quoted back. It carries an Open action that jumps straight to the run, switching workspace if needed. The parked run usually lives in a different project from the one you are working in, so the row reappearing in Pilot is not otherwise visible from where you are. See Notifications & Sound for how these surface and how to route them.

Parks are owned by the main process, so they survive a project's view being evicted from memory and they survive an app restart. Two bounds apply: at most 200 parked runs, and a park expires after 14 days. The expiry is a product decision rather than housekeeping: a park nobody has looked at in two weeks is stale intent, and honoring it silently forever hides a run behind a decision you no longer remember making.

Snoozing a Run

Snoozing is the shorter-lived cousin of parking. Right-click an agent terminal → Snooze, and pick one of four durations:

OptionEffect
15 minutesQuiet until 15 minutes from now
30 minutesQuiet until 30 minutes from now
6 hoursQuiet until 6 hours from now
Unlimited (until I interact)Quiet with no expiry: only your typing ends it

Wake now appears in the same menu once a run is snoozed. The menu only shows for a terminal Pilot can actually see, a live agent run it already tracks.

A snooze suppresses attention, not presence. A snoozed run that is working still counts as running everywhere it should; only the demand it makes on you is withdrawn.

Any typed input wakes it, including typing at an agent that is already working, which produces no state change at all. The duration you pick is a ceiling on how long the run can stay quiet, not the mechanism that ends the snooze. Most snoozes end because you came back and answered the agent.

Wake times are absolute wall-clock instants, so a machine that sleeps through the window wakes with the snooze already over. Expired snoozes are stripped before a row is built, so nothing in the interface needs a countdown or a clock. Snoozed runs also show on the project switcher's rows as a dashed dot and an "N snoozed" count. The same bounds as parks apply: 200 records, 14-day expiry.

Tip
Park or snooze? Park when the wait has a reason you want recorded and possibly a condition you want watched: it takes a note, it can be gated on another run, and it announces itself when it releases. Snooze when you simply do not want to think about this run for the next half hour. A park ends on a gate or by hand; a snooze ends on a clock or on your keystrokes.

This ladder is deliberately different from the notification inbox's snooze (1 hour, 4 hours, tomorrow, next week). An agent blocked on a question is a much shorter-lived thing than an inbox thread, and sub-hour defers are the common case for one and not the other.

The right side of the footer summarizes the fleet, counted by band rather than off the raw row total, over the population the query has left — not the segment. It says one thing, picked from a ladder in band order: something asking, then something that has stopped talking, then work handed back, then the all-clear.

WhenWhat it saysPressable
Anything blocked or waiting"Agent needs you" · "3 agents need you"Applies the Attention segment
Otherwise, anything quiet"Agent has gone quiet" · "2 agents have gone quiet"Applies the Quiet segment
Otherwise, anything unreviewed"Ready for review" · "3 agents ready for review"No
Otherwise, anything working"Nothing needs you · 4 agents working"No
Otherwise, any runs at all"Nothing needs you · 12 agents"No

The sentence is a button only where one segment reproduces the counted population exactly. "3 agents need you" counts blocked plus waiting, which is precisely what the Attention segment admits — not the wider attention bands, which would count a review hand-back the segment then would not show. The quiet sentence counts the one band the Quiet segment admits, so both can promise a number and then deliver it. The review sentence stays inert for the opposite reason: its segment, Finished, also admits acknowledged completions, so a button there would answer "3 ready for review" with a list of five. The summary is counted over the query-filtered fleet so that the control delivers what it advertises; counted over the whole fleet it would promise five agents and reveal the two that also match the query.

When the snapshot is stale the whole sentence is prefixed with "Last known:" and stops being pressable. The sentence is allowed to be historical; a control acting on it is not. It is also inert when the segment it would apply is already applied.

Note the phrasing. "Needs you" is a sentence, not a state label: the state is called Waiting everywhere it is named as one, and the filter segment that holds it is called Attention. Three registers, each used where it is exact.

When Pilot Cannot See the Fleet

The snapshot comes from the main process and can be late, unavailable, or stale. Pilot says which rather than guessing.

StateWhat you see
LoadingA skeleton shaped like a group heading and its rows, and, if it is still going after five seconds, "Still reading the fleet…"
Unavailable"Can't reach the agent host", with "Agents keep running. This reconnects on its own." There is no retry button, because the service already re-reads every few seconds: a control that does what the app is doing anyway is a promise you have to keep pressing.
StaleThe last known runs still render, behind "Can't reach the agent host — showing the last known state from 3m ago". The footer summary becomes "Last known: …" rather than claiming an all-clear it cannot vouch for.
Empty"Start an agent in any project" — or "Start an agent in project name" while scoped — plus a Clear filter button when it was a segment that produced the emptiness rather than an empty fleet.

A retained empty fleet never renders the zero-data prompt: "Start an agent in any project" over a feed that stopped answering an hour ago presents an unknown as an all-clear. A stale fleet with rows in it can still narrow honestly, though — retained runs are real rows, so a query matching none of them says something true.