@azure-id/orc-cli
v0.3.0
Published
Run an ORC lane in a Claude Code session it drives itself, and watch it work.
Maintainers
Readme
orc-cli
Run an ORC lane and watch it work.
orc-cli is a terminal app that starts a Claude Code session, sends it an ORC slash command, and replaces the wall of scrolling conversation with a live run view: a phase rail, agents running side by side, honest progress, a running token count, and a modal when the lane needs an answer from you.
◒ /orc dispatch 00:39
subagents are writing code, in conflict-free waves
████████████████░░░░░░░░░░░░░░░░░░░░░░░░ 2/7 tasks wave 2 of 3
✔ intake │ write Edit src/api/limit.js 2s
✔ plan │ Splitting the limiter from the route handler first.
● dispatch │
○ review │ agents 2/4 returned
○ verify │ ⠋ orc-executor-opus-5-high ▰▰▱▱▱▱▰▰▱▱ 00:37
○ ship │ ⠋ orc-executor-sonnet-5-high ▱▰▰▱▱▱▱▰▰▱ 00:31
│ ✔ orc-executor-opus-5-high ────────── 01:12
│ ✘ orc-executor-opus-5-high ────────── 00:48
│ trace
│ PAUSE batch boundary after wave 2
────────────────────────────────────────────────────────────────────
in 12 cache-w 30,995 cache-r 22,123 out 411 cost $1.41
────────────────────────────────────────────────────────────────────
[t] transcript [esc] stop [q] quitTable of contents
- What you need first
- Install
- Your first run
- The screens, in order
- Every key
- Every command and flag
- The lanes
- Choosing the model
- What you see while it runs
- When ORC asks you something
- Permissions
- Where your run is written down
- Changing settings
- Troubleshooting
- What it is, and what it is not
- Nine rules the code is held to
- For contributors
What you need first
Three things, and orc-cli checks all three before it lets you start:
| | What | Check it with | If it's missing |
|---|---|---|---|
| 1 | Node.js 18 or newer | node --version | nodejs.org |
| 2 | Claude Code, signed in | claude --version | npm i -g @anthropic-ai/claude-code, then claude once to log in |
| 3 | ORC, installed in the project you want to work on | orc --version | npm i -g @azure-id/orc, then orc init inside that project |
ORC is per project. Every repo carries its own .claude/ — its own config,
its own tier rules, its own wiki. orc-cli reads the settings of whichever repo
you point it at, exactly as if you had typed the slash command in Claude Code
yourself.
Do you already use ORC in Claude Code? Then you already have everything. orc-cli does not install, change or configure anything — it just runs the lanes you already have.
CAUTION — on ORC older than 0.56.0. ORC moved from the unscoped
orcpackage to@azure-id/orcwhile keeping the sameorccommand. npm links a command only if the file is unowned or owned by the installing package, so the old global package makes the new one unlinkable and every install fails withEEXISTon the command file — from any source.npm i -g -f"works" and is the worst outcome: it overwrites the shim and leaves the superseded package installed underneath, owning nothing and updated by nothing. The one-time fix:npm uninstall -g orc && npm i -g @azure-id/orc && orc update
Install
npm i -g orc-cliThat is the whole install. No build step, no configuration file, zero runtime
dependencies. It works on Windows, macOS and Linux because it is plain Node —
npm writes a .cmd/.ps1 shim on Windows and a symlink elsewhere.
Check it worked
orc-cli --version
# orc-cli 0.1.0 · claude 2.1.246 · orc 0.55.0Three versions on one line, on purpose: every support question starts with "which versions?", so orc-cli answers it before you have to ask.
If any of them shows —, that tool is not on your PATH. Fix that first.
Check your project
cd into the repo you want to work on and run:
orc-cli doctorYou get a report and an exit code you can script against:
| Exit | Means | What to do |
|---|---|---|
| 0 | ready | go |
| 1 | degraded but usable | you can run; the findings tell you what is drifting |
| 2 | cannot run | fix the named problem first — there is no key that skips it |
Every finding names the exact command that clears it:
project C:\dev\my-app (walked up from C:\dev\my-app\src)
versions orc-cli 0.1.0 · claude 2.1.246 · orc 0.55.0
log_dir C:\dev\my-app\.claude\orc\logs
run_dir C:\dev\my-app\.claude\orc\run
source orc where --json
WARN Installed payload 0.41.0 vs package 0.55.0.
→ orc update
degraded but usableInstalling from source
git clone <this repo>
cd orc-cli
npm link # puts `orc-cli` on your PATH from the working copy
orc-cli --versionThere is nothing to npm install — the dependency list is empty.
Your first run
cd ~/code/my-app # a repo that has ORC installed
orc-cliThen:
- Preflight appears. Read the findings. Press
enter. - Lanes appears. Move with
↑/↓. The highlighted lane expands to show what it does, its phases, and which model it will run at. Pressenter. - Compose appears. Type what you want done, in your own words. The line
under the box shows you exactly what will be sent —
/orc <your text>— because nothing is templated or rephrased on the way through. Pressenter. - The run view takes over. Watch it. Press
tat any time to see the prose the animation is summarising. - If the lane needs something, a modal interrupts and the clock stops. Answer it. The run picks up exactly where it was.
- Finish shows what happened, what it cost, which files moved, and asks what you want to do next.
Skipping the menu
If you already know what you want:
orc-cli --lane /orc-fast "add a subtract function next to add, same style"On Git Bash / MSYS on Windows, a leading
/gets rewritten into a path. orc-cli detects this and matches on the lane name anyway, so--lane orc-fastand--lane /orc-fastboth work.
Running against a different project
orc-cli --dir ~/code/other-appA --dir that does not exist is an error, never a silent fall back to the
current directory.
The screens, in order
Every screen carries the same header: which project is resolved, and how. That is never off-screen, because it is the one piece of context that changes what every other number on the page means.
| Screen | What it is for | |---|---| | preflight | Claude Code, ORC, versions, paths, and ORC's own drift findings. Blocks at level 2. | | project | Only appears when nothing resolved. Recent projects, plus a folder browser. | | lanes | The lanes installed in this project. An unfinished run is offered here first. | | compose | Your request, plus the exact string that will be sent, plus the permission allowlist. | | running | Phase rail · progress · agent tracks · trace · live token counts. | | ask | The lane needs an answer. The clock stops. | | permission | A tool wants to do something. Shows the exact input, never a summary. | | finish | Verdict · cost · files · what now. | | error | What happened, the exact command, and what to do next. |
How the project is chosen
--dir <path>if you gave one.- Otherwise walk up from your current directory looking for
.claude/. - If nothing is found, the project screen offers your recents and a folder browser.
If you pick the wrong folder, that is your call and orc-cli obeys it — it shows you what it resolved, prominently and permanently, and does not second-guess you. The folder browser lists directory names only; it never reads a file.
Every key
| Key | Where | What it does |
|---|---|---|
| ↑ ↓ | menus | move |
| enter | everywhere | confirm / send |
| esc | most screens | back · during a run, stop the run |
| 1–9 | ask modal | jump to that option (see below) |
| space | ask modal | toggle, on a multi-select question |
| pgup pgdn | anywhere long | scroll. Nothing is ever cut without a marker and a key that reveals it. |
| / | lanes | filter, by name or blurb |
| alt-m | compose | pick the model and effort for this run |
| alt-enter | compose, ask | a newline, rather than send |
| alt-t | running, ask, finish | toggle the transcript |
| alt-c | ask modal | show what the turn said before the question |
| t | running, finish | toggle the transcript |
| a | lanes | show/hide lanes that are installed but untested here |
| r | lanes | resume the unfinished run being offered |
| o | lanes, finish | open orc ui for this project |
| b | project | browse for a folder |
| y / n | permission | allow once / deny |
| s | finish | stay on this lane — same session, context kept |
| n | finish | new lane — fresh session, full context cost again |
| q | everywhere | quit |
| ctrl-c | everywhere | quit, restoring your terminal |
Every command and flag
orc-cli resolve the project from cwd and pick a lane
orc-cli --dir <path> run against a specific project
orc-cli --lane /orc "text" skip the menu, go straight to a run
orc-cli --resume go to the unfinished run in this project
orc-cli ui open `orc ui` for the resolved project, print the URL
orc-cli doctor preflight report (exit 0 / 1 / 2)
orc-cli --replay <file> drive the kernel from a recorded stream
orc-cli --check render every screen headlessly and exit| Flag | Effect |
|---|---|
| --model <row> | default · opus-4-8-high · opus-5-medium · opus-5-high. An unknown id is an error naming the rows, never a silent fall back |
| --all | show lanes that are installed but not in the tested registry |
| --no-motion | freeze every animation |
| --ascii | ASCII glyphs only, for a legacy console |
| --no-color | no colour (NO_COLOR and CI also work) |
| --version | orc-cli, claude and orc versions on one line |
| --help | keys and flags |
--replay and --check are real commands, not test scaffolding. --replay
drives the whole event kernel from a recorded run — no model, no network, no
money — and prints what the run did:
orc-cli --replay fixtures/orc-fast-fallback-to-mini.stream.jsonllane /replay
end finished
agents 1 (1 returned)
orc-executor-sonnet-5-high · done
progress — (no plan — nothing to count)
usage in 58 · cache-w 237776 · cache-r 945498 · out 129
cost $0.4360
files .../app.jsThe lanes
The lane list is derived, never hardcoded — and since 0.3.0 it is derived from ORC's own registries rather than from a directory listing:
orc lane list --json every lane, its command, and how many keys it reads
orc lane phases --json each lane's own phases, its trace tier and token,
and which trace verbs each phase emits
orc lane config --json what this lane resolved to: where it will STOP,
what it must announce, which agents it will dispatch
orc lane calls --json every CLI command the lane will run, free or paidA fact ORC publishes is rendered, never re-derived. So:
- every lane gets a real rail in ORC's own words —
Q1 — LOOK,F2 — Slice build + dispatch,D5 — Outline— including the twenty-four lanes orc-cli has never tested; - lanes are grouped by ORC's own trace tier, which is also how you can tell at a glance which ones write no trace at all;
- a lane ORC lists with no command of its own —
orc-judge,orc-advisor,context-combiner,orc-analyze-mini— is never offered, because typing it does nothing. A directory listing would happily offer all four; - an untested lane is marked with a fidelity chip saying what is degraded
(
rail from ORC · not tested here) rather than a bare(unregistered).
If your ORC has no orc lane (below 1.0.0) nothing breaks: orc-cli falls
back to its own four-lane table and the lane screen says so. It is a
capability probe, not a version floor — MIN_ORC is still 0.49.5.
The four tested lanes
| Lane | What it does | Its rail | Runs at | Why that tier |
|---|---|---|---|---|
| /orc | The full pipeline: intake → plan → scored parallel waves → review → verify → ship | intake · plan · dispatch · review · verify · ship | opus, high | ORC's PreToolUse guard hard-blocks /orc below high effort |
| /orc-mini | One executor, smoke gate, ship. No full review or verify | intake · plan · build · smoke · ship | opus, high | not gated; high recommended |
| /orc-fast | Knowledge-gated single executor. Needs a fresh wiki + a cached pattern, or it hands off to /orc-mini | preflight · build · smoke · ship | sonnet, medium | it is designed to run there — never gate it up |
| /orc-quick | Look → ask → do, looping. Asks which agent before every dispatch | look · ask · do | opus, medium | not gated |
Every rail is the lane's own. /orc-quick has no planning phase and never
shows one; /orc-fast has no review phase and never shows one. The phase words
come from src/kernel/phases.js, which is the single place both the lane menu
and the run screen read.
Choosing the model
Press alt-m on the compose screen, or pass --model:
| Row | What it does |
|---|---|
| default — your Claude setting | reads model from settings.json (project first, then user) and shows which file it read. opus is an alias, so orc-cli reports it as one and does not claim to know which Opus that resolves to |
| opus 4.8 · high | the tier ORC's effort guard is written around |
| opus 5 · medium | cheaper. See the caution below |
| opus 5 · high | strongest |
A model id orc-cli did not read is never printed. That is ORC's own catalogue rule, for the same reason: a shipped model id is wrong within a quarter, and wrong silently.
The choice is per run and is never written into the project. .claude/
stays untouched, as it always has.
The honest caution about opus 5 · medium
ORC's effort guard blocks /orc below high effort unless the session-model
bridge at .claude/orc/session-model.json names an Opus 5 / Fable 5 model and
is under 30 minutes old. That bridge is written by ORC's statusline — and a
headless session has no statusline, so under orc-cli it is normally stale or
absent.
orc-cli reads that bridge (reading is allowed; writing is not) and tells you the truth on the row:
opus 5 · medium — will clear (bridge 4 min old)
opus 5 · medium — blocked for /orc here — ORC's effort guard needs a fresh
statusline bridge, and a headless session writes none.
Every other lane is unaffected.A blocked row is shown, disabled, with the reason — never hidden, and never
silently swapped for a different tier. The guard matches the exact skill names
orc and orc-diy and nothing else, so every other lane is free at any effort.
Writing that bridge ourselves would unlock the row and would break the zero-writes boundary, so we do not. The real fix belongs in ORC.
Why /orc is pinned to high
This is not a guess — it was measured before any UI was written
(spike/M0-RESULT.md). ORC installs a PreToolUse hook that reads
effort.level and blocks /orc unless the session is at high effort or
stronger. Opus 5 can clear at medium, but only through a session-model bridge
that ORC's statusline writes — and a headless session has no statusline, so
that bridge is stale or absent. orc-cli therefore launches /orc at high and
never leans on the medium allowance.
Driving the real guard, for the record:
effort=<none> -> blocked effort=high -> allowed
effort=low -> blocked effort=xhigh -> allowedorc-cli never bypasses this guard. If a lane is blocked, that is ORC telling you something true.
What you see while it runs
The run screen answers one question: what is happening right now. It answers it in four places, and each one is honest about where it got its answer.
1 · The phase, and what that phase means
✦ /orc-quick do entry 2 00:38
the agent you picked is workingThe second line is not decoration. dispatch, smoke and preflight are ORC's
words, and a word is not an explanation — so every phase of every lane carries
one line of plain English saying what is going on inside it.
entry 2 appears only after orc-cli has watched the rail go backwards: a
second request in /orc-quick, a repair round in /orc. It is a count of what
happened, never a prediction of how many there will be.
2 · A rail fed by two sources, which do not rank equally
ORC writes PHASE-EDGE <family> into its trace whenever the role family of
a dispatched agent changes. That is authoritative — but it only exists when a
pinned orc-* agent is dispatched. /orc-quick dispatches its recon ad-hoc
by model and effort on purpose, so a whole /orc-quick run emits one edge,
near the end, called execution. A rail that can only ever light one lamp is
not a rail, and that is exactly what earlier versions showed.
So the rail is also fed by what Claude Code is observably doing — a dispatch, a write, a test run, a commit — and it says which it is:
| Marker | Meaning |
|---|---|
| ● | ORC named this phase itself. Authoritative. |
| ▸ plus an inferred label | orc-cli worked it out from the stream. |
| ✔ | done · ○ upcoming · ✘ skipped (a skipped phase keeps its slot) |
Since 0.3.0 there is a third source, and it is deterministic. orc lane
phases publishes the trace verbs each phase emits:
orc-quick Q0 [GATE] · Q1 [GATE, WIKI-CONSULT] · Q3 [FINISH, OUTCOME, VERIFY]
orc-fast F2 [GATE, VERIFY, WIKI-CONSULT] · F4 [FINISH, OUTCOME]A verb belonging to exactly one phase places the rail outright — a
WIKI-CONSULT line in a /orc-quick run can only be Q1. A verb belonging to
several narrows the candidates and moves nothing: an ambiguous signal is
still a signal, but it is not a decision. Certainty only ever goes up — when
ORC names a phase we already inferred, the lamp stays where it is and the marker
becomes authoritative.
That is what retires the limitation this README used to describe as permanent.
Two rules keep the inference honest, and both are tested:
- A tool call may only ever move the rail forward. Reading a file during
shipdoes not mean the run went back tolook. - Only a dispatch, a question, or a real
PHASE-EDGEmay move it backward — and when one does, that is a new round, so the counter goes up and the screen says so.
Because the same Bash tool is a knowledge probe, a test run, a build and a
commit, the command itself is classified before anything is drawn — which is
also where the read / write / test / build / ship / probe word on
the NOW line comes from.
3 · The NOW line
write Edit src/api/limit.js 2s
Splitting the limiter from the route handler first.The tool that is running and the one thing it is about, plus the last thing ORC actually said. A spinner tells you a process is alive; this tells you what it is doing. If a tool has been running a while, its age is shown — a slow step and a stuck step should not look the same.
4 · Each lane's own animation
An indeterminate bar is drawn when — and only when — there is genuinely nothing to count, and the caption always says why. But the motion belongs to the lane, so you can tell which lane is running from across the room:
| Lane | Motion | Reads as |
|---|---|---|
| /orc | ▰▰▱▱▱▱▰▰▱▱ a conveyor flowing right | a pipeline moving work through in waves |
| /orc-mini | ····˙˙˙▪··· one small walker, one small step | one executor, no parallelism |
| /orc-fast | ─────═»»▸── a streak crossing the width in under a second | speed |
| /orc-quick | ···✧✦✧······ a repair head that darts, sparks, darts again | short bursts of fixing |
| anything else | ░░░████░░░░ the plain sweep | we have not tested this lane's shape |
The same motif appears as a single character next to the lane in the menu and on the compose screen, so the animation is introduced before you commit to it.
--no-motion removes the motion rather than slowing it, and every motif has
a still frame designed as a still frame — a capped animation freezes mid-cycle,
and a frozen cycle looks exactly like a hang. --ascii swaps every glyph.
When ORC asks you something
Knowing that the lane is waiting is deterministic — it comes from facts, not from reading the prose:
turn ended + .current present -> ASK (the lane wants something)
turn ended + .current SEEN, now gone -> FINISH (ORC's own FINISH removed it)
turn ended + .current NEVER seen -> ASK (no run started, so none finished)
turn ended + RESUME.md present -> PAUSED (a wave stop; resumable)That third line matters more than it looks. ORC creates .current on its
first dispatch, and every lane asks its intake questions before it
dispatches anything — so "no .current" means "hasn't started yet", not
"already done". A run that never started cannot have finished.
Note also that a turn ending is not the child process exiting: in stream-json mode the session stays alive and accepts the next turn. Only ORC's own state says a run is over.
Only the shape of the question is a guess, and it degrades in three steps:
- The lane used a structured question → you get the real options as a list. No guessing at all.
- The lane wrote prose ending in a numbered list → you get the prose exactly as written, and the numbers work as shortcuts.
- Anything else → you get the assistant's message verbatim and a text box.
A question is never hidden because parsing failed, and an option the lane did not offer is never invented. The worst case is "here is exactly what ORC said, type your reply" — which is the normal terminal experience, not a broken one.
The number keys
Pressing 3 moves the highlight to option 3 and types 3 into the box, so
you can always see what you pressed. On enter:
- if the box contains only a digit naming an option → that option is sent;
- otherwise → whatever you typed is sent, exactly as typed.
So 2 enter picks option 2, and 22 requests per second enter sends that
sentence. Typing an answer that starts with a number never loses characters.
The clock stops
While a modal is up, the elapsed timer freezes and the header says
waiting on you. Your thinking time is not counted as run time. Answering
resumes it exactly where it was.
Permissions
A headless Claude Code session resolves permissions from the settings already on disk — there is no interactive prompt to hook into (measured in M0). So orc-cli uses an explicit allowlist, shown to you on the compose screen before the first run:
Read Glob Grep Write Edit Task Skill TodoWrite Bash- orc-cli never passes
--dangerously-skip-permissionsor any blanket bypass. A test asserts this against the actual argv. - Anything Claude Code refused is reported on the finish screen. A denial is a fact you must see, not something to quietly swallow.
- If a future Claude Code version does offer an interactive callback, the modal is already implemented and nothing else changes.
Where your run is written down
Every run writes a full transcript to disk, regardless of what is on screen. The animation is a view over the truth, never the only copy of it. There is deliberately no setting to turn this off — a record you can switch off is off on the run you needed it for.
| | Location |
|---|---|
| Windows | %APPDATA%\orc-cli\transcripts\ |
| macOS | ~/Library/Application Support/orc-cli/transcripts/ |
| Linux | ~/.config/orc-cli/transcripts/ |
Two files per run:
run-<lane>-<slug>-<DDMMYY>-<HHMMSS>.md— readable: the prompt, the prose, the dispatches, the questions and your answers, the ending.….stream.jsonl— every raw event, so a run can be replayed later withorc-cli --replay.
The path is printed on the finish screen and again when you quit.
Transcripts live in your config directory, not in your repo. orc-cli writes
nothing into a project, ever. The same directory holds recents.json — the list
of projects you have opened. That is the only file orc-cli owns.
Changing settings
It doesn't. That is deliberate.
ORC's configuration belongs to the repo it lives in, and is edited by the tools
that already own it — the orc config CLI and orc ui. A second place to
change a setting is a second source of truth, and the losing one is always the
one you last used.
So when you want to change something, press o on the lane or finish screen
(or run orc-cli ui). That shells out to the real orc ui for the resolved
project and prints the URL. orc-cli never starts the server itself, never
manages its lock, never passes it a port and never renders any part of it.
If the panel cannot be launched, you get the exact command to paste and exit code 0 — a door that could not open is not an error page.
Troubleshooting
orc-cli: command not found
The global npm bin directory is not on your PATH. npm bin -g prints it; add
that to your PATH and open a new shell.
orc-cli needs a terminal.
You piped or redirected the output. The interactive app owns the terminal. For a
non-interactive report use orc-cli doctor, which is built for exactly this.
No .claude/ found walking up from …
You are not inside an ORC project. Either cd into one, pass --dir, or let
the project screen browse for one. If the repo genuinely has no ORC, run
orc init there — orc-cli will not do it for you.
Preflight says "cannot run" and enter does nothing.
That is correct. Level 2 means a prerequisite is missing, and the finding names
the command that fixes it. There is no key that skips a level 2.
/orc is blocked by the effort guard.
It should not be — orc-cli launches it at high. If you see this, ORC's guard is
telling you something real about that project's configuration; run
orc doctor there.
The lane I picked is not the lane that ran.
Look for a HANDOFF line in the trace. /orc-fast is knowledge-gated: with no
wiki or no cached code-pattern it hands off to /orc-mini by design. orc-cli
surfaces the handoff and ORC's own stated reason rather than hiding it.
Progress shows a sweeping bar instead of a percentage.
There is no plan yet, so there is no denominator. The bar says
no plan yet — nothing to count rather than inventing a number. Once the
planner returns you get a real percentage.
A number shows as —.
That value could not be computed. Unknown is not zero, and orc-cli will not
print a figure it did not get from a real source.
The screen went blank when I resized the terminal. Fixed in 0.2.0. A resize erases the terminal and the painter's memory of what was on it; the old build stopped there, and since a resize is not a state change, nothing was redrawn until the next key press. Clearing and repainting now always happen together, coalesced on a short timer so dragging a VS Code panel does not fire a full repaint per pixel. Rows are also clipped to the terminal's real width, so a window narrower than the 52-column floor no longer wraps a row and desynchronises every row below it.
A question was cut off, or the options were missing.
Fixed in 0.3.0, and it was four separate bugs. The option parser scanned
backwards from the last line and stopped at the first non-list line — so any
question ending with a recommendation, or with the RESUME.md pointer ORC's
hard rule 16 makes mandatory at every question, parsed as having no options at
all. The body was hard-truncated at 14 wrapped lines with no marker and no
scroll. The option list was unbounded on top of that, so on a short terminal the
options were what got cut. And an option longer than 120 characters was
dropped rather than clipped. The question now scrolls, the options and the
answer box are reserved before the prose gets a row, and clipping belongs to the
renderer rather than the parser.
I pasted a multi-line request and only the first line was sent.
Fixed in 0.3.0, and this was the worst one. Every
was read as enter, and
enter on the compose screen submits. orc-cli now asks the terminal for
bracketed paste, so a pasted block arrives as one event, is inserted verbatim,
and never submits. On a terminal without bracketed paste, a draft that already
has a newline needs a blank last line to send.
PageUp typed [5~ into my prompt.
Fixed in 0.3.0. The key reader recognised only the four arrow sequences and
shredded everything else into its literal bytes, so PageUp fired esc (which is
"back") and then typed the rest. A CSI sequence is now consumed whole even when
it is unmapped: an unknown key does nothing, rather than something wrong.
/orc-explain sat on an empty question box forever.
Fixed in 0.3.0. orc-cli's completion signal is ".current was seen, and now it
is gone" — and ORC publishes trace_tier: null for orc-explain, orc-wait
and orc-retro, which write no trace at all. That transition could never
happen, so every turn end was read as a question. orc-cli now asks ORC which
lanes trace, and treats the turn ending as the whole signal on the ones that do
not.
The run went quiet for half an hour.
Check for a WAIT line. ORC v1.1.0 can stop a run — nothing runs during a
wait, by design, and a hand-back is written before it starts. orc-cli reports it
and stops the clock, the same way it does for a question. Before 0.3.0 a wait
was indistinguishable from a dead session.
The rail says inferred.
ORC has not named a phase for this run yet, so the current phase was worked out
from what the session is observably doing. This is much rarer since 0.3.0: ORC
publishes, per phase, the trace verbs that phase emits, so a verb belonging to
exactly one phase places the rail with no inference at all. /orc-quick used to
be the worst case — it dispatches its recon ad-hoc on purpose, so a whole run
emits one PHASE-EDGE near the end — and it now lights its rail from
WIKI-CONSULT, FINISH and OUTCOME instead. See
What you see while it runs.
The question box has no question in it. Fixed in 0.2.0 — it took only the last text block of the turn, which is empty when a lane ends its turn on a tool call. The whole turn is used now, and if the turn genuinely said nothing, the last thing that was said is shown instead.
The display looks scrambled in an old Windows console.
Use --ascii, and consider Windows Terminal. Legacy conhost is detect and
degrade, not a supported target.
Colours or animation are distracting.
--no-motion, --no-color, --ascii. NO_COLOR=1 and CI=1 are honoured
too. Motion is removed rather than slowed, because a capped animation freezes
mid-cycle.
What it is, and what it is not
It is a lane runner.
It is not a Claude Code replacement. No plan mode, no IDE integration, no
@-file completion, no image paste, no /compact, no MCP management, no
general chat. If you want those, open Claude Code — that fence is what makes
this finishable, and the surface re-implemented here is the surface owned
forever.
The two hard boundaries
| Boundary | Rule |
|---|---|
| ORC | Zero modifications. No file under .claude/ is ever written — not config, not settings, not the manifest, not a run folder. A test asserts this by snapshotting the directory around a full command run. If a feature needs an ORC change, that is a separate ORC release. |
| Claude Code | The documented headless interface only. No PTY-wrapping the interactive TUI, no scraping rendered output, no patching settings.json, no writing a credential. |
Deliberately not in v1
Written down so nobody re-proposes them as oversights: any lane beyond the four
· writing anything into .claude/, including "just fixing" a doctor finding ·
any settings editor of its own · guarding you against choosing the "wrong"
project · a switch that disables the transcript · multi-run or multi-project
dashboards · a web UI (orc ui exists and is the right surface) · its own auth
(claude login is the only path, forever).
Nine rules the code is held to
- A number the CLI did not compute is never rendered. Unknown is an em
dash.
--checkfails the build if any screen renders the wordundefined. - A fact is never relayed through a model's memory. Every displayed fact
comes from the event stream, from a file on disk, or from
orc <cmd> --json. The orchestrator is never asked to report its own state for the UI's benefit. - The animation is a view over the truth, never the only copy of it.
- A state the CLI cannot determine is shown as unknown, never guessed.
- A path ORC can compute is never hardcoded.
log_dirandrun_dircome fromorc where --json; if it cannot answer, the documented defaults are used and the screen says so. - Degradation is designed, not discovered. Narrow terminal, no colour, no
TTY, no motion, missing
orc, missingclaude— each has a defined rendering and a defined next action. - A fact ORC publishes is rendered, never re-derived. The lane list, each
lane's rail, its trace tier, its stops and its agents are ORC's, read through
orc lane. A second copy of someone else's table is a copy that drifts. - A question is never silently truncated. It scrolls, and the options and the answer box are reserved before the prose gets a single row.
- Nothing is ever cut without a marker and a key that reveals it. One viewport, everywhere. "Widen the terminal" is not an answer.
For contributors
Layout
bin/orc-cli.js entry, flags, TTY guard, exit trap, non-interactive commands
src/kernel/
bus.js normalise the three sources into ONE event vocabulary
phases.js per-lane phase model: the rail, the trace->lane word map,
the VERB->phase resolver, and the stream inference with
its two honesty rules
question.js reading a question out of prose: the option-block parser,
the markdown pass, and the turn split
reducer.js (state, event) -> state PURE
state.js the shape, one place
src/sources/
claude.js THE ONLY FILE that knows how the session is driven
trace.js resolve log_dir, follow .current, incremental tail (read-only)
orccli.js `orc <cmd> --json`, with a timeout and the exit code honoured
lanes.js ORC's own registries, READ not copied: `orc lane`
list/phases/calls, and the capability probe behind them
model.js the four model rows, the settings read, and the honest
report on ORC's session-model bridge
src/preflight/
doctor.js claude · orc · paths · drift, and the exit-code contract
project.js resolution, recents, the directory-name-only picker
src/ui/
ansi.js screen.js theme.js ui.js layout.js openpanel.js
input.js full CSI parsing + bracketed paste, chunk-safe
textinput.js a text field with a CURSOR: {text,cursor} in, out
viewport.js THE one place a long thing is made short
motifs.js one animation per lane; exact-width, ascii + still twins
screens/*.js one per screen, PURE: (state, size) -> string[]
src/log/transcript.js the always-on disk log
src/registry.js lane metadata; discovery is from .claude/commands
src/app.js the state machine — tick / onKey / render are exported
fixtures/ RECORDED real runs. Never hand-authored.
spike/ M0-RESULT.md, plus the harnesses that produced it
test/ node:test, zero-depThe architecture in one paragraph
Three inbound sources — Claude Code's stream-json, ORC's trace file, and
orc <cmd> --json — are normalised into one event vocabulary before
anything else sees them. That normalisation is the most important decision in
the codebase: the reducer, the renderer, the fixtures and the tests all speak
our vocabulary, never Claude's and never ORC's. The reducer and every screen
are pure functions, which is what lets the whole app be driven from a recorded
fixture with no terminal and no model.
The stream is primary for agents and progress — it has real ids and real timing. The trace is supplementary: phase edges, decisions, and the deterministic run-finished signal. Its narration is written by a dispatched agent per phase packet and therefore lags by a whole phase, so it never drives a progress indicator.
Phase is the one thing both sources speak to, and they are not equals: a
PHASE-EDGE is ORC's own word and wins outright, while an inference from the
stream is labelled as one and may only ever move the rail forward. That split
lives entirely in src/kernel/phases.js, so the two halves cannot drift.
Working on it
npm test # the kernel, preflight, the state machine and the CLI contracts
npm run check # 496 renders: 31 screens x 4 modes x 4 terminal sizes
npm run verify # both. Runs automatically on prepack.ORC_BIN=/path/to/orc points the orc <cmd> --json reads at a specific ORC —
which is how the derived-registry path is exercised against a known version
rather than whichever one happens to be global.
--check is not a smoke test. It asserts contracts: no row overflows its
width, no screen renders undefined or NaN, every screen keeps a visible way
out, and the finish screen's "what now" choice is never what gets cut as the
terminal shrinks.
Fixtures are recorded, never written by hand
A hand-authored fixture tests our idea of the stream, which is exactly the
failure mode being guarded against. Every fixture in fixtures/ is a real run.
This is not theoretical. The recorded run in
fixtures/orc-fast-fallback-to-mini.* caught three bugs that all four of the
plan's own descriptions had missed:
- the dispatch tool is called
Agentin Claude Code 2.1.246, notTask— so a run that plainly dispatched an executor was displaying zero agents; PHASE-EDGE execution :: first=orc-executor-sonnet-5-highput the entire tail into the phase rail;- the real trace filename had no lane and no slug in it.
There are now regression tests for each, written against the recording.
Recording a new fixture
spike/e2e-drive.js runs the real app against a real lane with no terminal:
node spike/e2e-drive.js --dir <project> --lane orc-fast --answer "yes" "do the thing"It prints each screen as plain text and dumps the final kernel state. The raw
stream lands in your transcripts directory; copy it into fixtures/ to keep it.
Adding a lane to the registry
Add an entry to REGISTRY in src/registry.js (title, blurb, phases, whether
it asks questions, its shape) and a row to LANE_TIER in
src/sources/claude.js if it needs a model or effort other than the default.
Discovery already finds the command file; the registry is only the metadata that
makes the run view specific. An unregistered lane still runs — press a.
License
MIT.
