arc
arc is a plan and task orchestrator built into the same radian binary.
It keeps a plan and its tasks as files in the repository, runs each task as a separate headless radian worker, and merges only what passes a fixed gate.
This build has the storage layer, the subcommands that read and write it: init, add, list, show, adopt, confirm, auto, priority, pause, start, reverify, cancel, reset-budget, merge, reject, run, and serve.
It also has radian arc mcp, the MCP server a lead writes plans and tasks through, the attempt runner behind start, and the gate and merge behind start, merge and the loop.
radian arc serve is the scheduling loop and radian arc run is one pass of it, and a bare radian arc opens the TUI, where you see the plans and tasks and act on them (see arc/tui).
This page holds the radian arc commands; the scheduling loop is in arc/loop, the gate and its failures are in arc/gate, the lead's MCP server in arc/mcp, the TUI in arc/tui, the worker in arc/worker, and the files arc keeps are in arc/files.
radian arc#
radian arc with no subcommand opens the TUI; its pages and keys are in arc/tui.
Without setup, radian arc and every subcommand except init refuse with a line that names radian arc init and exit 3; without a terminal, radian arc exits 2.
radian arc init [--track]#
init sets arc up for this repository.
It runs anywhere inside the working tree and works on the repository root.
It finds:
- the repository root (
git rev-parse --show-toplevel); - the
originURL (git remote get-url origin); - the main branch:
origin's own HEAD when it is known, else the checked-out branch, elsemain.
It writes config.toml with those values, and it creates the layout below.
It writes worker-brief.md with a short default when it does not exist, and never overwrites an existing file (see the worker brief).
A second init keeps an existing config.toml byte for byte and creates nothing twice.
It adds the arc paths to .git/info/exclude, a local file: init never changes a committed .gitignore.
Each line is added once; a second run adds nothing.
| Line | Without --track |
With --track |
|---|---|---|
/.radian/arc/config.toml |
excluded | tracked |
/.radian/arc/plans/ |
excluded | tracked |
/.radian/arc/tasks/ |
excluded | tracked |
/.radian/arc/ledger/ |
excluded | excluded |
/.radian/arc/runs/ |
excluded | excluded |
/.radian/arc/attempts/ |
excluded | excluded |
/.radian/arc/gates/ |
excluded | excluded |
/.radian/arc/state.lock |
excluded | excluded |
/.radian/arc/runner.lock |
excluded | excluded |
/.radian/arc/worktrees/ |
excluded | excluded |
/.radian/arc/worker-brief.md |
excluded | excluded |
--track makes git see config.toml, plans/, and tasks/, so you can commit them when you want; arc never commits them.
Removing a line that an earlier run added is up to you: init only appends.
Then it checks its requirements and prints one line per check:
| Check | What it runs |
|---|---|
provider credential |
Reads the stored credential (radian login writes it to ~/.radian/credentials/auth.json). No entry is a warning. |
`origin` reachable |
git ls-remote origin. |
push to `origin` `<main>` |
git push --dry-run <main>:<main>. A refusal here means the first real push will be refused too. |
workspace sandbox |
Builds a workspace sandbox over the repository: sandbox-exec on macOS, bubblewrap on Linux. |
A failed check is a warning line, not a refusal: init still exits 0, because a missing credential or a slow remote is something you can fix after setup.
Finally it prints the command that registers the arc MCP server with a lead. It does not change any configuration of its own:
claude mcp add arc -- radian arc mcp
init exits 0 when it wrote the layout and 1 when the directory is not a git working tree or the repository has no origin remote.
radian arc add#
add creates one plan or one task from standard input.
The new id is printed on stdout, one line.
Plans and tasks count in separate number sequences, so a plan and a task may carry the same number; the prefix tells them apart (see Plan and task files).
radian arc add plan --slug <SLUG> --title <TITLE>#
Reads the request text from stdin to end of input, writes plans/<id>.md, and prints the id.
| Flag | Value | What it does |
|---|---|---|
--slug |
SLUG |
The part of the id after the prefix and number: 1 to 48 characters from a-z, 0-9, -, with no leading or trailing -. Required. |
--title |
TITLE |
The plan's title. Required. It may not be empty. |
The body is the request text, then a ## Change records section.
A confirmed plan may only gain change records there; the request text itself is never rewritten.
radian arc add task --plan <ID> --slug <SLUG> --title <TITLE> [--after <ID>]... [--allow-noop] [--stage <NAME>]...#
Reads the brief from stdin to end of input, writes tasks/<id>.md, appends the id to the plan's tasks list, and prints the id.
| Flag | Value | What it does |
|---|---|---|
--plan |
ID |
The plan the task belongs to. Required. |
--slug |
SLUG |
The same slug rule as a plan. Required. |
--title |
TITLE |
The task's title. Required. It may not be empty. |
--after |
ID |
A task that must be merged first. Repeat the flag for several. |
--allow-noop |
— | The task may deliver no change on purpose. Off by default. |
--stage |
NAME |
An extra gate stage to run for this task. Repeat the flag. The value is commands, mutation, review, ci, or human. |
A task can only be added to a plan that is draft and not cancelled by its tasks; a confirmed or cancelled plan exits 3.
An --after id must name a task of the same plan or a task whose state is done; anything else exits 3.
radian arc list [--plan <ID>] [--all] [--json]#
Lists plans in claim order — the order radian arc run serves them in: the higher priority first (P0 before P1), then the plan id — each with its tasks in plan order.
Each plan's header line shows its id, status, auto level, priority, progress as <merged>/<tasks> merged, total_cost_usd (its tasks' totals sum) and title.
The progress total leaves out failed.cancelled tasks: one merged task and one cancelled follow-up read 1/1, an all-cancelled plan 0/0.
Each task line shows state.phase, the attempt number, and retry at <UTC time> when the task has a retry_at.
A task's cost_usd is what the budget counts (every worker attempt and every review stage) and what reset-budget sets back to 0; its total_cost_usd is the same spend and is never reset.
An attempt whose worker reported no cost (a worker.command that writes no cost in its Result) adds nothing to either number.
arc records that on the task as cost_unknown, and a task that carries it — or a plan with such a task — prints unknown instead of a number, even after a later attempt reports a cost.
By default finished work is left out, so you see the work in progress:
- every
done.mergedtask is left out; a shown plan still counts them in its header; - a finished plan is left out: at least one task, every task
done.mergedorfailed.cancelled, at least onedone.merged. A plan with no tasks is shown; - a
cancelledplan is left out and--allshows it: itsstatusiscancelled, or every task isfailed.cancelled; that derives from its tasks and never rewritesstatus.
Every other state is shown, failed.* included; a failed.cancelled task stays listed (and in show).
When the default left something out, a last line counts it:
3 finished plans and 12 merged tasks hidden (--all to show)
The count covers every plan and merged task not printed. A 0 part is omitted and 1 is singular: 1 finished plan and 1 merged task hidden (--all to show), 2 cancelled plans and 3 merged tasks hidden (--all to show), or 2 merged tasks hidden (--all to show) when no whole plan was left out.
When everything is hidden, that line is the whole output; nothing hidden means no line.
| Flag | Value | What it does |
|---|---|---|
--plan |
ID |
Only this plan, following the default filter; a finished or cancelled plan still prints its header line, so you see that it exists. |
--all |
— | No filter: print every plan and every task, done.merged history and cancelled plans included. It takes no value. |
--json |
— | Print one JSON array instead of lines. Without --all it follows the same cancelled-plan filter (by status or by its tasks); a plan named with --plan is always included. |
A plan or task whose file does not match arc's record is marked (changed outside arc).
The JSON array holds one object per plan: id, title, status, auto, priority, max_cost_usd, max_wall_time_secs, cost_usd (its tasks' cost_usd sum, the number the plan budget compares), total_cost_usd (its tasks' totals sum), cost_unknown (true when one of its tasks is), changed_outside_arc, and tasks.
Each task object holds id, plan, title, state, phase, attempt, attempt_id, token, cost_usd, total_cost_usd, cost_unknown, auto_rework, retry_count, after, supersedes, allow_noop, followup_pending, extra_stages, max_attempts, max_cost_usd, max_wall_time_secs, stall_timeout_secs, base_sha, head_sha, verified_sha, verdict_set_digest, retry_at, and changed_outside_arc.
radian arc show <ID> [--json]#
Prints one plan or task: every field, whether it changed outside arc, then its body.
For a task it also prints the current attempt's notes as attempt_note, such as worker-brief.md skipped: not UTF-8 (see the worker brief).
show --json carries them as attempt_notes, a list of strings (empty when there are none).
A plan prints cost_usd and total_cost_usd beside its other fields: the first is the sum of its tasks' cost_usd and the number its budget compares, the second is the sum of their totals. Both are computed when the plan is read and are not stored in the plan file. A task prints both of its own numbers.
A task whose attempt reported no cost prints unknown for both, and a plan with such a task prints unknown for both of its sums; show --json keeps the numbers it has and sets cost_unknown to true, so a script can tell an incomplete sum from a complete one. The state stays after a later attempt reports a cost; see Another agent as the worker.
The id names one plan or task. An id that names both a plan and a task is refused with exit 3, naming both files (see Plan and task files).
list --plan <id> always reads the plan.
| Argument | Value | What it does |
|---|---|---|
<ID> |
a plan or task id | Required. An id that names both a plan and a task is refused (3). |
| Flag | Value | What it does |
|---|---|---|
--json |
— | Print one JSON object instead of fields. |
The object holds the same keys as in list --json, plus body, session_id, rework_reason, the after chain's waits_on, blocks, never_runs and cycle (see the after chain), and the newest review verdict's review, review_verdict, review_session_ids, review_cost_micros and review_settings.
An unset field is null; session_id is the current attempt's worker session id, from its execution.json, else its identity.json, else the claim's start.json, and null only when none of them names one; rework_reason is null unless the task is in review.verifying or review.rework_pending and has a recorded reason (a failed stage, a moved branch, a gate that left no record, a gate that died after it started, a reject note, the automatic rework limit that paused the plan, a conflict that reached limits.max_conflict_rework, or an automatic reverify count that is used up).
review is {"summary", "blocking", "suggestions", "verified"} from the newest review verdict, or null; each item of blocking and suggestions is an object with text and, when it has them, level, file, line and pre_existing (see the review stage); review_verdict is its path or null; review_session_ids lists every session the stage ran; review_cost_micros is their summed cost in micros of a US dollar (null when the run reported none); review_settings is {"round", "exec_args", "timeout_secs", "timeout"} (the round first or rework, the config key the session's arguments came from, its wall-time limit in seconds and the key that limit came from), or null.
For a task with a review verdict, the text form also prints review_verdict, review_summary, review_blocking, review_suggestions, review_verified, review_session_ids, review_cost_micros, and review_round, review_exec_args, review_timeout_secs and review_timeout.
The text form prints session_id after attempt_id.
An id that names nothing exits 1.
The after chain#
A task's after lists the tasks that must be merged before it is claimed (see Plan and task files).
show <task> prints two more fields after after:
waits_on: every task in theafterchain, transitively, nearest first, each as<id> <state>.<phase>;-when it waits on nothing.blocks: every other task whoseafterchain contains this task, transitively, nearest first, in the same form;-when no task waits on it.
The walk is breadth-first over after and visits each task once.
An id that names no task is printed as <id> missing; only a hand-edited file that was adopted can hold one, because add task and the MCP add_task tool refuse an after id that names no task.
A task in waits_on that can never become done.merged stops the chain for good.
That is a failed.* task, a task whose plan is cancelled and that is not merged, or an id that names no task.
show prints the first such task as never_runs: <id> <state>.<phase> (or never_runs: <id> missing).
An after cycle, possible only through an adopted hand edit, is printed once as cycle: <id> -> … -> <id>, and the walk stops there instead of looping.
A plan's show has none of these fields.
The JSON object of a task holds waits_on and blocks too: arrays of {"id": string, "state": string|null, "phase": string|null}, nearest first, with null state and phase for an id that names no task.
It also holds never_runs (that object, or null) and cycle (an array of ids that starts and ends with the same id, or null).
The MCP show tool returns the same object.
radian arc adopt <ID>#
Every file arc writes is recorded with a content hash in the ledger. When a plan or task file on disk is not the one arc last wrote, arc does not act on it: the change may have come from an editor, a pull, or a checkout.
adopt records the current content as arc's own, so the item can be used again.
It accepts a change to the body, the title, after, supersedes, allow_noop, extra_stages, the per-task limits, and a plan's limits and tasks list.
| Argument | Value | What it does |
|---|---|---|
<ID> |
a plan or task id | Required. The item whose current file becomes arc's own. |
It refuses with exit 3 when the id names both a plan and a task (see Plan and task files), or when the outside change touched a field only arc (for a task) or the owner (for a plan) sets:
- a task:
state,phase,attempt,attempt_id,token,cost_usd,total_cost_usd,cost_unknown,auto_rework,retry_count,followup_pending,retry_at,base_sha,head_sha,verified_sha,verdict_set_digest; - a plan:
status,auto,priority.
A file that already matches the ledger is a no-op and prints so.
Without a recorded version to compare against, adopt refuses: it cannot tell which fields changed.
radian arc confirm <ID>#
confirm is your action: it accepts a draft plan, or a task arc or the lead proposed.
A plan moves from draft to confirmed.
A confirmed plan gets no new todo.ready tasks, and its request text can only gain change records (see Plan and task files).
A plan's auto level may only be other than off once it is confirmed.
A task moves from todo.proposed to todo.ready.
The plan's status and auto do not change, and the plan does not return to draft.
A task waits in todo.proposed until you confirm it when arc proposed it as the plan's review follow-up (see The review's follow-up) or when the lead added it to a confirmed plan with the MCP add_task tool; the scheduling loop passes over it, and start refuses it.
A proposed task you do not want is ended with radian arc cancel (failed.cancelled).
| Argument | Value | What it does |
|---|---|---|
<ID> |
a plan id, or a task id | Required. An id that names both a plan and a task is refused (3), naming both files. |
A plan confirm refuses with exit 3 when the plan has no tasks, when it is already confirmed, when it is cancelled, when its file changed outside arc, and when the worker's radian exec line is rejected (see start and worker.exec_args).
A task confirm refuses with exit 3 for a task in any state other than todo.proposed, for a task whose plan is cancelled, for a file that changed outside arc, and for a rejected worker line.
A refusal writes nothing.
It prints <plan> confirmed or <task> todo.ready.
Trying it first: --dry-run#
| Flag | Value | What it does |
|---|---|---|
--dry-run |
— | Run every check the confirm runs, print what it would do, and write nothing. |
radian arc confirm <ID> --dry-run runs every check the real confirm runs, in the same order, and refuses exactly as confirm would: the same message and the same exit code.
It writes nothing: no plan or task file and no ledger entry.
It checks state.lock only to find out whether another process holds it, and releases it; it does not create the lock file when it is missing, so no lock is left behind.
On success it prints these lines and exits 0:
- first, the line the real confirm would print, prefixed
would::would: TK0002-two todo.readyorwould: PL0001-first confirmed; - one
<id> waits on: <chain>line per task the confirm would maketodo.ready— the task itself, or a plan'stodo.readytasks in the plan's order — with the same entries asshow'swaits_on, or<id> waits on: nothing; - a
never runs: <id> <state>.<phase>line for each such task whose chain cannot finish (the same test asshow'snever_runs), and acycle: <id> -> … -> <id>line for a cycle; - one
<id> queue: <n> of <m> todo.readyline per such task: its place among everytodo.readytask plus itself, in claim order (the plan'spriority, the plan id, the task's order in the plan). Atodo.readytask no plan lists, possible only through an adopted plan edit, sorts last.
When the next pass would not claim the task, the queue line ends with the reason in parentheses, in the words radian arc run's summary uses: (waiting for <id> to merge), (waiting for a free concurrency slot), or (waiting for you to start it (`auto` is `off`)).
The place is where the task would stand at that moment, not a promise: another task may be claimed or merged before it.
would: TK0002-two todo.ready
TK0002-two waits on: TK0001-one todo.ready
TK0002-two queue: 2 of 2 todo.ready (waiting for TK0001-one to merge)
--dry-run exits 1 for an id that names nothing, 2 for a malformed id, 3 for a state that does not allow the confirm (an empty plan, an already confirmed plan, a task that is not todo.proposed, a file changed outside arc, a cancelled plan, a rejected worker line), and 4 when another process holds state.lock.
A real confirm prints the same never runs: and cycle: lines on stderr after its line when they apply.
They are warnings: the confirm still happens and still exits 0.
radian arc auto <PLAN> <LEVEL>#
auto sets a plan's auto level.
radian arc auto <PLAN> <LEVEL>
| Argument | Value | What it does |
|---|---|---|
<PLAN> |
ID |
The plan id. Required. |
<LEVEL> |
off, run, merge |
The level. Required. |
The levels#
| Level | What arc does | Where it stops |
|---|---|---|
off |
Nothing by itself. You start and merge each task. | Each step |
run |
Starts each task when its after tasks are merged, and runs the gate. |
review.awaiting_human |
merge |
run, and merges when every required stage and the integration passed. |
Failure, budget, or conflict |
radian arc run makes the transitions of these levels; it does not run in the background.
off is accepted on any plan; run and merge need a confirmed plan and otherwise exit 3.
A cancelled plan is final: any level, off included, is refused with exit 3.
A value outside the set is a usage error (2).
Automatic reworks#
A failed stage or a failed integration sends a task to review.rework_pending.
While the task's plan has an auto level other than off, arc counts that rework in the task's auto_rework field.
A failed reverify does not count, whether the loop or you started it.
When the count would go past [limits] max_auto_rework, arc pauses the whole plan: its auto becomes off, the task stays in review.rework_pending, and both radian arc run and show (as part of rework_reason) say why.
arc never resets a task's count.
A plan budget pausing the plan also sets auto to off; run starts and merges nothing for a plan at off.
radian arc priority <PLAN> <PRIORITY>#
priority sets a plan's priority: the plan the scheduling loop serves first.
radian arc priority <PLAN> <PRIORITY>
| Argument | Value | What it does |
|---|---|---|
<PLAN> |
ID |
The plan id. Required. Any plan, draft or confirmed. |
<PRIORITY> |
P0, P1, P2, P3, P4, P5 |
The priority. Required. P0 is the highest and P3 the default. |
| Priority | Meaning |
|---|---|
P0 |
The highest: its ready tasks get the next free slot before any lower priority. |
P1 |
Above the default. |
P2 |
Above the default. |
P3 |
The default; a plan whose file has no priority field reads as P3. |
P4 |
Below the default. |
P5 |
The lowest. |
A task has no priority of its own: it follows its plan, in its plan's task order and after list.
priority only decides which plan gets the next free [limits] concurrency slot; it never preempts an attempt that is already running, so a change takes effect at the next pass that has a free slot (see run).
The plan's state does not matter: a draft plan can be set too.
list, show, and the MCP list and show tools print the plan's priority.
A value outside the set (P6, p1, high) is refused with exit 3, and the message lists every value.
A cancelled plan is refused with exit 3; a refusal writes nothing.
The command prints <plan> priority <PRIORITY>, for example PL0001-first priority P1.
It exits 0 on success, 3 for a value outside the set or a plan file that changed outside arc, and 4 when another process holds state.lock.
radian arc pause <PLAN>#
pause sets the plan's auto level to off.
| Argument | Value | What it does |
|---|---|---|
<PLAN> |
ID |
The plan id. Required. |
A task that is already started keeps its state; the next radian arc run reads the level before it starts or merges anything.
It exits 0 on a plan that is already off.
radian arc start <TASK>#
start runs one attempt of one task and waits until it ends.
It is the owner's way to run one task: radian arc run runs every task the auto levels allow.
A task already in review.verifying (one a moved branch or a changed digest sent back) resumes verification instead: start runs the gate, prints its line, claims no new attempt, and exits 3 if verification cannot run.
When a scheduling loop is already running that task's gate, start waits for it and reuses its verdicts.
radian arc start <TASK>
| Argument | Value | What it does |
|---|---|---|
<TASK> |
ID |
The task id. Required. |
Before it reconciles, claims or verifies anything, start checks the worker line (see the worker.exec_args and worker.command rows).
Without [worker] command, it parses the line the worker would get: the wrapper's fixed radian exec arguments plus [worker] exec_args.
A line radian exec would reject, or one that repeats an argument the wrapper sets, refuses the command with exit 3 and the parser's own message, names worker.exec_args, claims nothing, and changes no file.
With [worker] command set, arc checks instead that the program resolves (see Another agent as the worker); a program that does not resolve refuses the command the same way, naming worker.command and the program.
Before it claims anything it reconciles every task in doing (see Reconcile) and moves a todo.retry_wait task whose retry_at has passed back to todo.ready.
start also claims the task you name when it is todo.retry_wait, even while its retry_at is still in the future: your start is a run-now, and the backoff exists for the scheduling loop. The claim is the same transaction as a todo.ready one, with the same concurrency check and the same printed line, and it clears retry_at.
A claim is refused with exit 3 and changes nothing when:
- the task is not
todo.readyortodo.retry_wait; atodo.proposedtask names its phase and waits forradian arc confirm, andtodo.paused_budgetand everyfailed.*phase are refused; - its plan is not
confirmed; acancelledplan is refused, naming its status; - an
aftertask is notdone; [limits] concurrencytasks are already indoing.
The claim is one transaction: the task's attempt goes up by one, attempt_id becomes <task>-<attempt>, a random token is written, and the task moves to doing.starting.
arc then runs the hidden radian arc work wrapper as a child, with the brief on its stdin; the wrapper moves the task to doing.running.
The worker itself — the session it runs in, the brief it receives, the steps before an attempt, the limits the wrapper enforces, and the checks after it exits — is in the worker.
When worker.command is set, another agent runs in its place: see Another agent as the worker.
When the attempt ends, arc applies the result in one transaction (the result table) and prints one line:
<attempt id> <outcome> -> <state>.<phase>
<attempt id> <outcome> -> <state>.<phase> (retry at <UTC time>)
<attempt id> <outcome> -> <state>.<phase> (<reason>)
For example TK0001-work-1 completed -> review.verifying.
A result whose token is not the task's current attempt prints <attempt id> ignored: <why> and changes nothing.
Before its own line, start prints reconciled: <line> for each task the reconcile step changed.
A pushed completed attempt continues into the gate in the same process, so one start takes a task from todo.ready (or todo.retry_wait) to review.awaiting_human or review.rework_pending.
The verification prints its own line:
<attempt id> verify -> <state>.<phase>
<attempt id> verify -> <state>.<phase> (<stage>: <reason> (verdict <path>))
For example TK0001-work-1 verify -> review.awaiting_human, or TK0001-work-1 verify -> review.rework_pending (commands: command_failed (verdict /…/verdicts/commands.json)).
When verification cannot run (a moved branch, a stage this build does not have), the task stays in review.verifying, start prints the attempt's own line and then the reason on stderr, and exits 3.
start exits 0 when the task left doing, 1 when it is still in doing after the attempt (or on an internal error), 3 when the claim is refused or verification refuses, and 4 when another process holds state.lock.
start waits for the wrapper, so it exits only after the attempt ends; radian arc run does not wait.
radian arc cancel <ID>#
cancel ends one task, or ends every task it accepts in a plan and the plan itself.
It is your action (there is no MCP tool for it), and it reconciles doing tasks first, as start does.
| Argument | Value | What it does |
|---|---|---|
<ID> |
a plan or task id | Required. An id that names both a plan and a task is refused (3), naming both files; an id that names neither exits 1. |
Task form#
| The task is | What cancel does |
|---|---|
todo (any phase) |
moves it to failed.cancelled |
doing, starting or running |
SIGTERM to the wrapper, then SIGKILL after [limits] cancel_grace_secs; when the wrapper's AttemptFinished arrives the task goes to failed.cancelled; when it never arrives the task goes to doing.reconciling |
doing.reconciling |
checks the wrapper's pid and start time; signals it if it is still that process; moves the task to failed.cancelled |
review (any phase) |
stops a gate that is running for the task, then moves the task to failed.cancelled |
failed.exhausted, failed.irrecoverable |
moves the task to failed.cancelled: you are saying "seen, given up". A stale gate record or lock, if one exists, is checked and stopped the way a review task's gate is; no other process runs for the task. |
done (any phase), failed.cancelled |
refused with exit 3, with a message that lists the phases cancel accepts |
A task that ran out of attempts (failed.exhausted) or cannot continue (failed.irrecoverable) has no worker, so cancel only moves it to failed.cancelled.
The task's attempt history, its attempt id, its verdicts and its total_cost_usd stay as they are; only the phase changes.
A done.merged task and a task already at failed.cancelled are refused.
When a gate is running for a review task (its runs/<task>/gate.json record is started or running), cancel signals the gate process the same way a scheduling-loop stop does: SIGTERM, [limits] cancel_grace_secs, then SIGKILL, and the gate's record becomes interrupted.
A failed.exhausted or failed.irrecoverable task has no gate of its own, but a stale record or lock from an earlier attempt is handled by the same process check: cancel signals only the process the record names, and only while it is still that process.
The task moves only after the gate is stopped.
The wrapper's pid and its start time both come from the attempt record, so a pid the operating system reused is never signalled.
cancel prints <task> <state>.<phase>, for example TK0001-work failed.cancelled.
Plan form#
A plan id runs these steps in this order:
- the plan's
autobecomesoff; - every task of the plan in
todo,doingorreview, and every task atfailed.exhaustedorfailed.irrecoverable, is cancelled by the task form's rules; - a task in
doneorfailed.cancelledis left as it is; - the plan's
statusbecomescancelled.
A plan cancelled this way has every task it could act on at done.merged or failed.cancelled — a task file that changed outside arc is left as it is — so a plan whose only non-merged task was exhausted reads finished (✓) after the cancel, when at least one task merged.
A task a gate is running for is stopped as the task form stops it.
If a task's cancel leaves it doing.reconciling, the plan still becomes cancelled; the next radian arc run pass ends the task at failed.cancelled, after the same process check the task form makes (see Reconcile).
That pass waits no second grace: the plan form already sent the wrapper its SIGTERM, so the pass checks the wrapper's pid and start time and kills it at once when it is still there.
A task the lead adds to the plan while these steps run is cancelled too: the plan's tasks are read once more after the status becomes cancelled, and a task added after the first read is ended by the task form's rules.
A plan that is already cancelled — by its status, or by all its tasks being failed.cancelled — is refused with exit 3.
cancel prints one <task> <state>.<phase> line for each task it changed, in the plan's task order, then <plan> cancelled:
TK0002-b failed.cancelled
TK0003-c failed.cancelled
PL0001-plan cancelled
A cancelled plan is final.
cancel, confirm, auto, priority, start, reverify, reset-budget, add task, and the MCP add_task, update_task, update_plan and add_rework tools refuse it with exit 3 (the MCP tools: an error naming the status).
radian arc run never claims, gates or merges its tasks, and the summary does not list it as waiting.
list hides it unless --all, and show <plan> and show <task> still read it and its tasks.
cancel exits 0 when it ended the task or plan or left a task doing.reconciling, 1 when the id names nothing, 3 for a refused state or an id that names both a plan and a task, and 4 when another process holds state.lock.
radian arc reset-budget <ID>#
reset-budget returns budget-paused tasks to todo.ready and resets the counted cost, cost_usd, to zero from now on. The task's total_cost_usd does not change: it is what the task has spent in total, not the budget counter.
The id names one plan or task; an id that names both is refused with exit 3 (see Plan and task files).
| Argument | Value | What it does |
|---|---|---|
<ID> |
a plan or task id | Required. A plan id resets every paused task of that plan. |
- Task form: the task must be
todo.paused_budget; it moves totodo.ready, itscost_usdbecomes0, and itstotal_cost_usdstays. Any other state is refused (3). - Plan form: every task of the plan that is
todo.paused_budgetmoves totodo.ready; every task of the plan getscost_usd0, because the plan's budget counts all of them.total_cost_usdstays on every task. The plan'sautolevel is not changed.
A cancelled plan is refused with exit 3, in the plan form and in the task form.
It prints <id>: <n> task(s) ready and exits 0.
An id that names no plan or task exits 1.
radian arc reverify <TASK>#
reverify re-runs the gate for a task's already pushed commit without running a worker.
It is your action (there is no MCP tool for it), and it exists for a gate that was wrong: a missing build step, a flaky test, or a gate command fixed after the verdict was written.
radian arc reverify <TASK>
| Argument | Value | What it does |
|---|---|---|
<TASK> |
ID |
The task id. Required. It must be review.rework_pending, review.awaiting_human, or review.verifying with a stuck gate. |
It applies only when the task's rework_reason, if there is one, is one a re-run can fix:
- a failed verification stage:
commands,mutation,review, orci, with any reason; - the integration stage failed with reason
command_failedortimeout, the same stage and reason pair the automatic reverify of a moved main accepts (see A command failure and a moved main); main does not need to have moved.
A reject note, a failed delivery check, an integration failure with any other reason (a conflict has its own rework path, in A conflict with main), and every other state are refused with exit 3.
A task in review.verifying is accepted when its gate is stuck: its runs/<task>/gate.json record names the task's current attempt with status no_record or died (see the gate run), and no gate process holds the task's gate lock.
Any other review.verifying task, including one whose gate is still running, is refused with exit 3.
A plan paused by the automatic rework limit does not block reverify when the task's own reason is one of the failures above.
A task whose commands or integration stage failed with command_failed or timeout is also re-verified by radian arc run once main moved (see A command failure and a moved main).
A task of a cancelled plan is refused with exit 3.
reverify ignores the automatic path's two guards: it runs whether or not the (head_sha, main) pair was re-verified before, and whether or not limits.max_auto_reverify is used up; it does not use up the count.
A reverify claims a new attempt without a worker:
attemptgoes up by one, andattempt_idandtokenare new;head_shaandbase_shastay the previous attempt's, and nothing is pushed;- the attempt result's class is
reverify; - the task moves to
review.verifyingand is verified at once in the same command, asstartdoes after a pushed attempt.
The new attempt's verdicts are its own: an earlier attempt's verdict is not reused and no verdict is ever rewritten.
The remote work/<task> must still equal head_sha; when it moved, reverify exits 3 with the moved-branch reason and the task stays in review.verifying, as verification does.
A reverify attempt is not a retry: it changes neither retry_count nor max_attempts and is not an automatic rework (auto_rework does not change).
It runs no worker, so no worker cost is added, but the review stage it runs counts: its cost is added to the task's cost_usd and total_cost_usd when the gate's verdicts are applied, and it can pause the task in todo.paused_budget (see limits.max_cost_usd).
It prints the claim's line and then the verification's:
<attempt id> reverify -> review.verifying
<attempt id> verify -> <state>.<phase>
For example TK0001-work-2 reverify -> review.verifying, then TK0001-work-2 verify -> review.awaiting_human.
A gate stage that fails again is a normal result: the task moves to review.rework_pending, the verification line carries the stage and its verdict, and reverify exits 0.
| Code | Meaning | When |
|---|---|---|
0 |
success | The verification ran, whether it passed or a stage failed. |
1 |
an error | A git or file error. |
3 |
the state does not allow the operation | The task is in another state, its rework reason is not one of the failures reverify accepts, it has no head_sha, the branch moved, or a listed stage is not built in this build. |
4 |
a lock is held by another process | Another process holds state.lock. |
radian arc merge <TASK>#
merge takes your approval, builds the integration candidate, pushes it to the remote main, and finishes the task.
It is the owner's action (there is no MCP tool for it).
At auto level merge, radian arc run takes the same approval and starts the integration in the background; a later pass reads its verdict and pushes.
radian arc merge <TASK>
| Argument | Value | What it does |
|---|---|---|
<TASK> |
ID |
The task id. Required. It must be review.awaiting_human (or resume a review.integrating/review.ready_to_merge task left by an earlier merge). |
Before it acts, merge:
- fetches
[repository] remoteand requireswork/<task>to still beverified_sha. When the branch moved, every verdict and the approval of the old commit become invalid: the task returns toreview.verifying, the reason is recorded, andmergeexits3. - recomputes the verification digest from the task's verdicts and requires it to equal the recorded
verdict_set_digest. A changed or new verdict for the same commit makes the approval invalid: the task returns toreview.verifyingwith the reason recorded, andmergeexits3.radian arc runthen verifies it again.
Then it:
- writes your
humanpass verdict forverified_sha(runs/<task>/attempts/<attempt id>/verdicts/human.json) and moves the task toreview.integrating. The verdict has noapproverwhen you ranmerge, andapproverautowhenradian arc runmerged it at auto levelmerge. - runs the integration stage against the current remote main. The candidate is
head_shawhen main is still the task'sbase_sha; otherwise it is a merge commit ofhead_shaonto main (arc never rebases). The profile'skey_commandsrun on the candidate. A conflict or a failing key command moves the task toreview.rework_pendingwith the reason, andmergeexits0. A passing integration moves the task toreview.ready_to_merge. - pushes the candidate to the remote main without force:
git push <remote> <candidate>:refs/heads/<main>. When main moved after integration (or the push is rejected as non-fast-forward), the task returns toreview.integratingandmergeintegrates again, at most 3 times in one command. After that it exits3with the task inreview.integrating; a latermergecontinues from there. - checks with
git ls-remotethat the remote main is the candidate and that the candidate containshead_sha, moves the task todone.merged, and removes the task's worktree (.radian/arc/worktrees/<task>). The task's branch stays on the remote.
merge never checks out or updates the main checkout's working tree: it fetches, pushes, and reads refs only.
On done.merged it prints <task> done.merged (<candidate sha>), followed by <new task id> proposed (follow-ups from <plan id>) when the merge left the plan with no open task and it has review items to collect (see The review's follow-up). A failed worktree removal is a warning: line after it and does not undo the merge, and so is a follow-up task that could not be added.
The exit codes are the command's own:
Merge exit codes#
| Code | Meaning | When |
|---|---|---|
0 |
success | The task reached done.merged, or the integration sent it to review.rework_pending. |
1 |
an error | A git or file error. |
3 |
the state does not allow the operation | The state does not allow the merge, the branch moved, the digest changed (the task returns to review.verifying), or 3 integration runs did not settle (the task stays in review.integrating). |
4 |
a lock is held by another process | Another process holds state.lock. |
radian arc reject <TASK> --note <TEXT>#
reject refuses a task that passed the gate, with a note for the lead.
It is the owner's action (there is no MCP tool for it).
radian arc reject <TASK> --note <TEXT>
| Argument | Value | What it does |
|---|---|---|
<TASK> |
ID |
The task id. Required. It must be review.awaiting_human. |
--note |
TEXT |
Why the task is refused. Required; an empty note is a usage error. |
It writes the note to runs/<task>/attempts/<attempt id>/reject.md and moves the task to review.rework_pending.
show prints the note as the task's rework_reason; list and show are how the lead reads it.
It prints <task> review.rework_pending.
It exits 0 on success, 1 on an error, 2 for an empty note, 3 when the task is not review.awaiting_human (or changed outside arc), and 4 when another process holds state.lock.
radian arc run#
run is one pass of the scheduling loop: what a pass does, its output, its exit codes and loop.log are in arc/loop.
radian arc serve#
serve is the scheduling loop: --interval, its output, the signal handling and the repeated-error stop are in arc/loop.
radian arc mcp [--claude-experimental]#
radian arc mcp is the stdio MCP server the lead writes plans and tasks through. Registering it, its tools and its tool errors are in arc/mcp.
| Flag | Value | Default | What it does |
|---|---|---|---|
--claude-experimental |
— | off | Push task transitions to a Claude Code channel. The details are in arc/mcp. |
Exit codes#
Every subcommand of this page uses these codes.
| Code | Meaning |
|---|---|
0 |
success |
1 |
an error |
2 |
a usage error |
3 |
the state does not allow the operation (this includes a file changed outside arc, and arc not being set up) |
4 |
a lock is held by another process |
Code 2 covers a bad flag or an argument value outside a closed set; a slug, an id, or a stage name that the rules refuse is also 2.
A closed output pipe (radian arc list | head) is a normal stop: exit 0.
The TUI#
The TUI never holds runner.lock and runs no pass. When it opens, it starts radian arc serve with the default interval as a detached process if no loop holds the lock; R starts one the same way while none runs.
The header shows loop ● running (pid <n>) for any holder (the pid appears when the holder recorded a readable one, otherwise it is plain loop ● running), loop ◌ starting until the loop it started records its own pid in runner.lock, and loop ○ stopped when none runs; a failed start adds one footer line with its reason and exit code.
The newest transition line loop.log gained since the TUI opened is the footer's ticker, and L opens a view of the last 500 lines.
q and SIGTERM close the TUI and nothing else: a loop it started keeps running, and an attempt it left running is reconciled by the next loop.
A running task's diff tab shows the worker's worktree against the attempt's base_sha, uncommitted files included (live · worktree), refreshed every 5 seconds.
A bare radian arc opens the TUI; the plans, board, task and merge pages, every mark and key, and the owner's actions are in arc/tui.
The plans page shows one of four views (active by default, then all, done, cancelled; f cycles them) and each plan draws one of seven marks — ✕ cancelled, ◌ draft, ◉ needs you, ● running, ◍ paused, ○ queued, ✓ done — in its own plan_* theme role, with P<n> by its id and in the frame label.
Its x, c, +/- and o cancel, confirm, step the priority and cycle the order (priority, state, id); the mark rules and the columns are in the plans page.
Not built yet#
The gate stages mutation and ci are not built: verification refuses a profile or task that lists one, and the task stays in review.verifying.
See also#
clifor every otherradiancommand.environmentfor the files radian reads, including this one.arc/workerfor the worker itself: its session, brief and limits, and another agent as the worker.