Repository navigation
v6.0.0
Control Tower. 6.0 makes an unattended run something a person can read at a glance and trust to
say when it needs them. The console's home is the Tower (#/runs): what needs you, what is
moving, what waits and what is next, each strip one sentence and one action, and amber only when a
person must act — a status word alone never paints it. A run that stops itself says why in one card
and comes back with one press; a failure is a failed phase, a declared wait is a wait, and a resumed
phase keeps its seat, its settings and its session. A person's turn — a sign-in, a code, an approval
at the machine — is a typed step with its own card and its own proof. The plan page is one phase table
with four views and a route map that holds seventy stations; runs, sessions, the repository and the
numbers were rebuilt on one data grid, one status model and one design law. Quick start is one screen,
and the guide explains every page in English and Persian. The free tree stays MIT. It is a major
version because the home, the plan page's tabs and the colour law changed under people's feet — the
list is below.
Migration
- The home is the Tower. An empty or unknown address opens
#/runs, and#/nowand#/dashboard
redirect there, keeping whatever half of the address still means something. A bookmark, a handoff
link or a notification from 5.x still lands. - A plan page has three tabs — Phases, Run, Source. The four lists that were Route, Phases, QA
and Handoffs are the Phases tab's views (?view=table|map|qa|handoffs); the old tab addresses
redirect into them. - Amber is a summons, never a status. A board
stuckphase reads Stuck in the waiting paint
with an alert glyph;halted,parkedandgatedread as waits. Anything that asked you for
something still turns amber, through its inbox item. - A
- **Human step:**bullet in the 5.1.0<who, what, proof ref>spelling fails the lint (F37).
Write the six-field grammar (references/plan-format.md) or leave the label off. - The failure streak counts failed phases, not endings. The same phase failing twice is one
failure, and a refused credential or a lost network never counts. - The decision manifest has nineteen keys —
issuesandplan-approvaljoined the seventeen.
A plan that answers neither keeps the shipped defaults (offandhold). - Update the console's scripts and the plugin together, as at 5.1.0: a 5.1.0
phase-outcome.sh
rejectsneeds-human --step, and a 5.1.0phase-graph.shhas no--human-stepsarm. - The console's heap is a number it chooses.
viewer/runpasses--max-old-space-sizeas a node
argument — neverNODE_OPTIONS, which everyclaudechild would inherit — fromheapMbin
~/.config/phase-console/fleet.json(6144 by default), clamped to half of RAM. A console started
some other way keeps V8's default heap until it is started throughviewer/runagain. - For forks of the client: the 2.x
Chip, its tones and the--line-*/ 2.x colour aliases are
gone; badges are the typed status family undercomponents/ui/status/, and ESLint refuses a raw
StatusBadgeimport outside it.
Added
- The guide explains every page, in English and Persian (control-tower phase 32). The help sheet
gains four sections — Quick start, Getting around (a card for every page on the rail and every
overlay), Tower and Halts — and every section now has a Persian twin beside its English body
(viewer/client/src/content/guide/<id>.fa.md), held to the English section for section by
docs-parity.test.tsand required byguide-coverage.test.ts, which also fails a page or an overlay
that has no card. - A person's turn has a language (control-tower phase 41; operator decision 7). An act only a
person can do — a sign-in that opens a browser, a device code, a token to paste, a password at the
machine, an approval on somebody else's dashboard — is a HUMAN STEP: one of sixteen typed kinds
(viewer/shared/human-step-model.js, twinscripts/human-steps.env), eight states, andKIND_META
as the one place a kind's icon, label, defaultwhereand proof hint are read. A session declares
one withphase-outcome.sh … needs-human --step <kind> --title … [--open-url | --open-command] [--where] [--proof] [--step-line]… [--code] [--credential]; a value shaped like a token, a password,
a one-time code or a URL query secret is refused with nothing written. A plan declares one with
- **Human step:** <kind> · <what> · open: … · proof: … · where: host|any · window: … [· auto-open: host], read byphase-graph.sh --human-steps [N]and its JS twin; the 5.1.0<who, what, proof ref>spelling, which nothing ever parsed, is superseded and fails the lint by name (F37), and a step
with no proof is advised about (F38). The console writes each step to an append-only ledger
(human-steps.ndjson: last state wins, a torn last line dropped, a retention sink), raises ONE
human-stepinbox row and ONEneeds-youpush naming Open and I did it (and a device code),
and parks the phase on a PERSON — an unbudgeted wait, situationblocked-declared:human-acts, no
rung. No code, token, password or URL query secret reaches the ledger, a journal line, a push
payload, the log or a transcript; asecret-entrysecret goes to the credential registry (the
keychain, else a 0600 file) and is never read back. - A person's turn can be acted on (control-tower phase 43; operator decision 7). Six routes —
GET /api/human-steps[?open=1]andPOST /api/human-steps/:id/{open,check,snooze,cannot,dismiss},
each a move through the ledger's one door, attributed, journalled (phase.human-step-opened,
-checked,-proven,-snoozed,-cannot,-dismissed,-expired,-reminded). Open
answers the link for the caller's browser, or opens it on the machine behind--allow-terminal/
--allow-agentonly once the caller has sent back the full URL it was shown; a command step opens
the embedded terminal, which prints the command and runs it on the person's Enter, and nothing
typed there reaches the ticket, a journal or the ledger. Open again is unlimited short of a settled
step. A step's proof is a WATCH ref, polled off the declaration on thecmd:back-off and bounded
by the step's window (seven days when it names none), never by a wait budget; when it lands — or
I did it — check finds it true, a terminal's exit 0 proves a step with no proof ref, and a person's
word one with no proof at all — the step readsprovenand the SAME session is resumed told what
was proven. An unlanded check says what the proof read, in its own words. Reminders at +15 m, +1 h,
+6 h, then daily, deferred out of the newreminderQuiethours and past a snooze, replace the
step's notification; at the window's end the step expires into an errand, and I can't do this
becomes one at once carrying the reason — neither loops. I did it is a signed notification button
(checkinPUSH_ACTION_VERBS), and the step's inbox row offers check, snooze and cannot. - The console notices a person's turn (control-tower phase 44; operator decision 7, its third
birth channel). The sign-in guard: a supervised Bash call that starts an interactive sign-in
(SIGN_IN_SHAPES—gh auth login,az login,npx wrangler login, … through wrappers,
sudo, package runners, env prefixes, groups and chains; never a here-doc's data) is denied
before it runs on every profile,phase.tool-deniedrulesign-in, with the whole
needs-human --step …declaration to make instead (SIGN_IN_STEPS: the kind, and the tool's own
status verb as the proof only where the console's command judge runs that tool —gh,npm,
pnpm,yarn; a password given with-pnever reaches the journal); a status verb (gh auth status) and a sign-in with a non-interactive
flag (--with-token,--password-stdin,--identity, …) run, and a call no run token names is
untouched. The stall reading: asilentlane whose last output named an http(s) link beside
waiting words (SUSPECT_WAIT_WORDS; "open this link in your browser" counts only for a link that
is not the machine's loopback) carriessuspectedStepand journalsphase.human-step-suspected;
its inbox row is raised at once, offers Make it a person's turn
(POST /api/human-steps/suspected,phase.human-step-converted, a step bornconsole; a second
press answers the same step) and never converts or opens anything by itself; the reading is made
over a result's whole text, and neither its link nor its words ever carry a device code. The launch door: probe 9 lists the plan's
- **Human step:**bullets for the phases the run will drive, runs every proof at once through
the watch clock's own probe, pre-clears the proven and returns the rest with their open actions
(Prelude.humanSteps); a start records the owed ones in the ledger, bornplan, just before the
runner starts, and the reminder clock withdraws one once its phase's handoff reads complete. The folds:
every inbox row that asks a person for an act carries onehumanStepview (humanStepView,
HUMAN_STEP_FOLDS) — a sign-in asclaude-login, an MCP sign-in asmcp-login, the verification
card asperson-check, a plan to approve, a gate, a relayed question and a QA verdict as
decision, phase 39's protected path asprotected-pathwith the act and the path the session
named (Errand.step) — andos-permissionnames its settings pane,physicalcarries a QR for
its link. - Runs is the Tower (control-tower phase 20; the operator's "a full live management page for
runs", and #19's fence made visible).#/runsanswers "does anything need me?" in one look: every
run sits in exactly one bay, most urgent first — Needs you, Live, Waiting, Queued, Ready to start,
Settled — read off the status model, so a run's bay is the one its strip computes. One situation
line under the title counts the bays and the day's spend ("8 need you · tower is running — phase
3 · 2 queued · $49.50 today", drawn as separate words). An annunciator of the nine halt families
counts exactly the halt card's own categories; a lamp with nothing behind it is dim, and pressing
a lit one narrows the bays to that family. What the Now page showed is absorbed rather than
copied: its inbox rows sit in Needs you under the strips, its lanes ride the Live strips, and its
Next up is Ready to start — the same set, with a Start that opens the launch dialog through its
lazy door. Waiting and Queued say on each strip what holds it up: a hold, a scope fence with the
fencing phase and the refs it waits on, an errand folded over several phases, and a phase queued
behind a sibling run's branch.?bay=needs-you…?bay=settledopens the page on a bay; the
filters (a lamp, a plan name) and Settled's fold are remembered. On a phone the bays stack and
each strip's one action is the width of the strip, at its foot. - A run is a flight strip (control-tower phase 19; the operator's "plan cards … make them
better", the UI of #25 and #28, and #163's lane card). Each run on the Runs board is one strip:
a left edge in the run's paint, its precise word and icon, whether it needs you, the name, the
phase track, ONE clock that says what it measured ("running 12m 03s", "halted 4m ago", "ran
1h 04m"), the cost with the session in flight counted, and exactly one action chosen by where
the run stands — the halt card's own recommended verb for a stop, Pause for a live run, Thaw for
a frozen one, Hold or Release in the queue, Open run on a console that cannot drive runs.
Everything the old card showed is one press away, in place: the halt card, the lanes with their
labelled clocks and last activity, the Now panel of each live lane, every clock of the phase,
the cost meter, every other verb, Inspect and its raw record. Arun:progressframe moves the
strip with no refetch; the badge breathes only while a lane is observed working; the name's
peek rides the link itself. Expanding opens with agrid-rowstransition (no motion library)
and is remembered per strip. - Starting a run is one screen. The launch form is no longer five stages: it opens on what will
run, the findings that need an answer (each with its own button), a preset row — Careful, Balanced,
Hands-off, or your last launch — and nine tiles (Scope, Engine, Safety, Git, Money and stops,
Review and QA, Tools, Accounts, Decisions) that each say what they are set to and open in place.
How many values differ from a fresh console is one line above Launch. On a phone a tile opens as a
page inside the same sheet, under the same fixed buttons. A value a preset set says from preset.
When the plan's git lines disagree with the launch, the Git tile asks for honour or override
before Launch, and each phase's permission mode says where it comes from. A run's launch also says
how a QA recovery's fix session starts and what one QA round may spend. - Every status says what it is, not only what colour it is. A status badge now always carries
an icon and a word, so it reads without colour, and a word the console does not recognise says
"Unknown" instead of borrowing another's look. A run whose plan was closed reads as superseded
rather than waiting, and a finished run that had failures reads as partly done rather than green.
Amber means one thing — a person is needed — so primary buttons and the keyboard focus ring are
drawn in ink, and the only amber button is the one that asks you to allow something. - Figures you can read closer. Every chart with an axis — weekly completions, the calendar of
completions, the ranked lists and the stacked bars under Insights, and a run's timeline — now has
a crosshair: hover, tap, or Tab to it and use the arrow keys, and the line under the figure says
what it is on. ⌃ or ⌘ with the scroll wheel, or a pinch, zooms in; a plain scroll and a swipe
still move the page. A live run's timeline follows its lanes as they report progress, with a
"now" line and no reload, and a cost strip underneath shows what each attempt spent. Debug ▸
Health opens on the console's own heap, event-loop delay and stream count. First paint is
unchanged: the charts load when a page with one opens. - One data grid: filters, grouping, picks, a kept view and a window, on any table. A table can
now offer a filter per column (text, a list of values with their counts, or a number range),
group its rows under a column's values, let rows be picked for a bulk action, remember how you
left it, and draw only the rows in view once it holds more than 150. On a phone every setting is
in one View sheet above the cards. A column you hide is still in each row's detail — nothing is
removed from the page — and a table that asks for none of this looks exactly as it did. The
delivery ledger under Debug is the first to use it. First paint went down by 8 KB. - A plan can say which permission mode its phases need, and a plan-mode phase waits for you.
**Permission mode:** plan(plan-wide) and- **Permission mode:** acceptEdits(one phase) set
the mode a phase's session starts in; a run can set a default. A phase in plan mode now hands its
plan to you: the run parks it, the inbox shows it with Approve and Reject, and Approve resumes
the same session to carry the plan out. - A launch tells you which of the plan's git lines it will not honour, before anything runs — a
named branch it will not create,Worktrees: onorCheckout: mainit cannot grant — and asks
whether to honour them or run over them. Sessions are then told the branch they are really on. - The route map at seventy stations and more (control-tower phase 30, #32 gap 3). A plan too
big to show whole at the map's 0.45 zoom floor no longer opens as a field of specks (a 71-phase
plan opened at 6 % on a phone, 1.9 px stations): it opens fit-to-width, every row across the
frame at a tappable size, looking at the station that wants a person. Only what the window shows
is drawn, so the DOM stays the size of the frame however long the plan grows (1,107 SVG nodes for
71 stations before). A minimap under the frame shows the whole plan and the window's place on it,
with the waves in view in words ("Waves 4–18 of 30"); a press on it moves the window. A station
search (from twenty stations) moves the window to a phase by number or name and rings it; Enter
walks the matches. The stations are one tab stop, and the arrow keys walk them — every station
reachable, the window following. A phase the console is verifying now wears its own mark on the
map. The spike that chose this over React Flow measured both engines on the same plan (React
Flow's pane took a phone's vertical swipe);check-distnow asserts the plan route's static
graph carries no map library, in either tree.viewer/e2e/route-map.spec.tsproves it in a real
browser at all four viewports. - The console is now measured in a real browser before a release. A tour of every page at
phone, tablet and desk widths checks for rows that run off the screen, controls a thumb cannot
reach, text too faint to read in either theme and keyboard focus you cannot see, against a
recorded baseline that may only shrink (npm run test:e2einviewer/, for contributors). - Clear the failure count from a stopped run's card, without retrying a phase. The card also shows
the count as n of max, and says plainly when every account you have registered is unusable — so
"switch to another account" is never advice you cannot take. - Clocks that say what they measure. A phase reports how long since it first started, how long
its current attempt has run, how long it actually worked, how long it queued and how soon it made
its first tool call — and its recorded duration is the worked figure, so two pages showing one
phase show one number. A plan reports when work on it began, to the minute, and for how long.
Every interval is written with one rounding rule: 119 seconds reads1:59on a clock and1m 59s
in prose and tables, never2m. A quiet session says which silence it is in — no output at all,
output but nothing produced, a tool call out, waiting on its own job, or waiting on somebody else's
clock — and the threshold it is measured against. - A page for locks. Sessions ▸ Locks lists every phase claim the console can see — who holds it
and on which host, its session and whether that session is still alive, the branch and working
tree it claimed, when its lease ends, whether it has lapsed, and which queued phases it is holding
up — worst first, with the Release button beside each. The same rows areGET /api/locks, which
can be narrowed to the live claims, the lapsed ones, or the claims on one repository.
Changed
- The record band says its status in words (control-tower phase 26). In Repo, a working tree's
role (Root, Run, Lane, Staging, By hand, Debris), whether its run is live, and each settle event
(Settled, Landed, Pushed, Pending, Not supported here, Failed, Released, Pruned) are badges with an
icon and a word, read off the status model like every other status, where they were a raw word on
a hue; the words are owned once, inshared/worktree-model.js. In Insights a health issue's
severity is a badge of its own beside the issue's kind, the orphan-folder count no longer paints
amber, and the per-phase spend and each run against its budget sit under a fold named for their
rows, one press away, the way a chart's numbers do. - Runs is the home, and Now is gone (control-tower phase 21; the operator's "redesign the pages
and integrate them").#/and every address Now answered land on the Tower in one hop:#/now→
#/runs,#/now?focus=inbox|lanes|next→#/runs?bay=needs-you|live|ready,#/now?focus=plans
→#/plans,#/dashboard→#/runs,#/ready→#/runs?bay=ready,#/pulse→
#/runs?bay=live;#/search,#/guideand#/notificationsopen their overlays over Runs. The
rail is slim — 80 px, a glyph over each name, Runs first with the needs-you count — and the
situation line moved from the Runs page into the shell header, on every page: on a desk each figure
links to its bay, on a phone it is a plain row. The phone's tab bar is Runs · Plans · Sessions ·
Insights · More. An empty Needs-you bay says when the convergence loop next looks, as Now's empty
inbox did. The destinations are Runs, Plans, Sessions, Repo, Insights, Debug and Settings. - The license stays MIT. Phase Console's source is now also offered under separate terms, and
this free edition is unaffected:LICENSEis the same MIT text every earlier release carried, and
package.jsonstill saysMIT. README and USAGE say so in a short Licensing section.
Fixed
- Unlocked, the route map lets a vertical swipe scroll the page again (control-tower phase 30).
overscroll-behavior: containon the interactive frame — a scroll container that never scrolls —
stopped the swipetouch-action: pan-yhands the browser from reaching the page. - A resumed phase keeps working, and its cost is counted once. A phase the console resumed —
after a wait, a limit or your instruction — used to run under a 60-turn cap meant for paperwork,
and a resumed conversation's earlier spend was counted again on every resume. A resume now gets
what is left of the phase's own turns (never fewer than 120), a resume that runs out is continued
rather than stopped, one the console shut down waits to be resumed instead of failing, and each
session's dollars are counted once; runs stored before this are corrected once, when the console
starts. A "wrap up soon" notice that did not reach the session is sent again. - A run that was asked for some phases finishes only when those phases are really done. Before,
a run limited to a few phases could say "finished" while one of them was still waiting for you.
Now it stops and says which phases are not done and what each is waiting on. A phase you skipped
counts as settled; one with a request still open for you does not. Runs an older console called
finished show the honest sentence when you open them. - A request for you goes away when its phase is done. Finishing or skipping a phase now
withdraws what it was asking you for, so the inbox stops showing work that no longer needs you. - An edit only you can make is named as one. Claude Code does not let an unattended session edit
files under.claude/. A session that runs into this now leaves you a request naming the edit and
the path, instead of a permission card no setting can answer. - A phase can no longer wait on its own lock.
phase-outcome.sh … --watch lock:<slug>/<N>that
names the declaring phase is refused, with a sentence saying what to write instead: that lock is
released by the phase's own closeout, so the wait would end the moment the phase did. Name the lock
thatphase-lock.sh conflictsreported. A phase blocked on a decision for you keeps no timer. - A phase no longer builds or verifies against a tree another run left on its branch. A run
that checks a shared repository onto its branch now holds it until the run is over, not just while
one phase works in it, and puts it back on the branch it found when it can. Another run's phase
that needs that repository waits for it — the queue names the repository, the branch and the run —
andphase-lock.sh conflictssays the same to a session started by hand. Every verification now
records which branch and commit it compared against, and a failed one says so in its first line. - A problem outside the console no longer pulls more sessions into it. When a phase stops on
something outside the console — the repository refusing every check, say — the phases working in
the same repository now wait behind it instead of starting and running into the same problem.
Phases in other repositories carry on.- They start again when the problem clears, when you retry the phase that reported it or release
its lock, or when its waiting time runs out. - A second phase reporting the same problem joins the first request for help instead of opening
another one, and you are not notified twice. - A check the console runs for a waiting phase now slows down over time — after 5 minutes, then
15, then an hour, then every 6 hours — and keeps going until the phase's waiting time runs out,
instead of giving up after an hour. - When the thing a phase was waiting for arrives while its run is still going, the phase continues
in that run, ahead of the queue, instead of being counted as a failed attempt. - A phase whose wait ended while another phase was using its repository now continues its own
session when its turn comes, instead of starting over. - The console's messages say which checks it is still watching and which it has stopped running.
- They start again when the problem clears, when you retry the phase that reported it or release
- Recovery comes back when the machine does. An outage could leave a phase waiting for a person
long after the outage had passed.- A recovery step that failed because the machine could not reach the API, or had no account to
use, is no longer counted as tried: it is offered again once things are working, a few times at
most, and what it cost still counts against the spending limits. - Clearing or signing in an account now wakes the recovery up by itself, and a waiting "sign in an
account" request is withdrawn as soon as one is usable. - The limit on recovery attempts per run counts only the phases still open, and an operator's Retry
gives a phase back the attempts that restarts and outages used up. - When Claude Code is asked to hand off because its context is filling up, the fresh session that
follows is no longer counted as a recovery attempt. - A run stopped by a recovery limit now says which limit, the numbers, and the setting that raises
it — and a run can carry its own limits (Recovery rungs in the launch dialog), set when it
starts or continues, or changed while it runs.
- A recovery step that failed because the machine could not reach the API, or had no account to
- An outage is not a credential fault. A network failure — or even a phase writing about one —
could take every Claude account on the machine offline until somebody cleared them by hand.- A phase that finished its work and mentioned a TLS error code in its handoff had its account
retired for saying so. Error patterns now demand the framing Claude Code actually prints, and a
session that booked real turns or real spend is not overruled by what its output quotes. - "This machine cannot reach the API" is now a condition the console can name. It waits the outage
out — a minute, two, five, ten, then a quarter of an hour — and charges it to nothing: no attempt,
no failure count. - A certificate error takes several sightings, from several sessions, over several minutes before the
console believes it, and then pauses one account rather than retiring it. A successful call
afterwards reopens it by itself. - One account's refusal no longer retires every account in its organisation unless the API actually
said the organisation was the problem — so switching accounts still works when one login cannot.
- A phase that finished its work and mentioned a TLS error code in its handoff had its account
- A refused credential no longer counts against the run's failure budget. That count means "this
plan keeps failing"; one network event spent a whole run's allowance in 39 seconds and left it stuck
past its own ceiling.