Execution
Running an approved plan as parallel workstreams — the graph schema, how targeting is pinned, how write contracts are enforced, and the three ways reality is allowed to diverge.
/ivar-execute <plan-path>Execution takes an approved plan and runs it as several workstreams at once, each with its own agent and its own slice of the filesystem.
Before it will run
- A feature session —
IVAR_FEATUREandIVAR_SESSION_IDboth set, and$IVAR_SESSION_PATH/state.jsonreadable. - The feature exists in the hall.
- Every planning gate passed: Requirements, Analysis and Plan approved.
The graph schema
{
"workstreams": [
{
"id": "api-contract",
"title": "Define API contracts",
"operations": ["OP-API-CONTRACT", "OP-AUTH"],
"depends_on": [],
"write_contract": ["ecbert/apps/ecbert/src/**"],
"provider": "opencode"
},
{
"id": "frontend",
"title": "Update frontend components",
"operations": ["OP-UI"],
"depends_on": ["api-contract"],
"write_contract": ["lagertha/src/**"]
}
]
}| Field | Meaning |
|---|---|
id | the workstream id — matches ### <id> in the plan byte for byte |
title | what it is doing |
operations | the OP-* ids from the plan it owns |
depends_on | workstream ids that must finish first |
write_contract | the paths it may write, relative to the view directory |
provider · model · agent | optional targeting overrides |
That is the whole schema
The parser denies unknown fields. version, plan_path, repos and prompt
are all refused — there is no graph schema version, and no per-workstream
prompt to author. The executor's prompt is rendered from the plan itself, the
workstream's operations and its write contract.
Write contracts
The write contract is what keeps two parallel workstreams off the same file. Paths are relative to the session view directory, where repos sit at the root:
api/src/** ✓
repos/api/src/** ✗ matches nothingA repos/ prefix silently matches nothing
There is no repos/ level in the view directory. A glob written that way does
not error — it never matches, and the guard then refuses every write the
workstream tries to make.
Enforcement happens at two layers:
| Layer | Catches |
|---|---|
| Provider guard | a Write / Edit outside the contract, as it happens |
| Post-run audit | writes a shell command made without asking the guard |
The audit compares the worktrees against the commit the run started from, so
committing does not hide a stray write. It reads the difference both ways, which
also catches a run that threw away an uncommitted edit it inherited — a
git checkout -- or git reset --hard is as much a contract violation as a
write.
A run, start to finish
Decompose. With no board yet, the plan is broken into a candidate graph — workstreams, their operations, their dependencies and their contracts.
Confirm who runs what. The session's provider is the default every
untargeted workstream inherits. The candidate's targeting is shown one line
per workstream, and you are asked whether to change any of it — every time,
even when everything is defaulted.
api-contract provider=opencode model=— agent=—
frontend provider=opencode model=— agent=—Prepare. The resolved provider, model and agent are written into
each ### <workstream> block in plan.md so the plan and the board agree,
then the board is created.
ivar feature execute prepare <feature> --graph-json <path> --session "$IVAR_SESSION_ID"Approve. The board sits at AwaitingApproval with the graph — including
each workstream's provider, model and agent — for you to read.
ivar feature execute approve <feature>Tick. Launches every workstream that is pending and whose dependencies have all succeeded, then blocks until the ones it launched terminate. When it returns, a wave has landed; if that made others ready, tick again.
ivar feature execute tick <feature>Reply. When a workstream blocks on a question, answer it.
ivar feature execute reply <answer> --feature <feature> --session <session>tick is the wait, not a poll
There is nothing to poll while tick runs — the call itself blocks. It is
also idempotent: run it as many times as you like, it only ever launches
workstreams whose dependencies have already succeeded.
Targeting is pinned at prepare
There is no command that changes a prepared workstream's provider, model or
agent. Retargeting means deleting board.json and re-authoring the graph.
That is why the question comes before prepare and why the answer belongs in
the candidate JSON — a question skipped at the cheap moment costs the whole
setup later. By approval time the resolved provider is explicit in both
plan.md and board.json, so no approved workstream is left relying on the
hall default.
When reality diverges
Three situations, three paths, and exactly one applies:
| Situation | Path |
|---|---|
| Outside the approved scope, and isolatable | ivar feature create <child> --parent <current> |
| A structural correction to the approved plan | ivar feature execute replan <feature> --plan <plan-path> |
| Implementation-only local divergence inside an operation | ivar feature execute reconcile <feature> --workstream <id> --description <text> |
A replan folds the revised plan into a live board. Behaviour-changing revisions pause the affected workstreams until each acknowledges:
ivar feature execute ack-revision <feature> --workstream <id>Execution resumes only once every affected workstream has acknowledged.
A reconcile records the deviation in the execution journal and leaves the plan standing — it is for the case where the implementation drifted, not the design.
Coordinator and executor
The two roles have different authority, and that split is what keeps a parallel run from corrupting shared state.
The coordinator owns the feature and its tree. It is the one that creates a child when an isolatable out-of-scope request arrives — without asking permission, because the alternative is a queue of blocked workstreams waiting on a question that has one right answer.
The executor never mutates shared feature state: it does not create, reparent, promote, integrate, close or delete anything. It stops and reports an isolatable request, and the coordinator creates the child.
Never hand-edit the board
graph.json, status.json and the journal are written by the CLI. Editing
them by hand desynchronises the board from the runs it is tracking.
Planning
The SPDD cycle and the identifiers that hold it together — R-*, N-* and OP-* ids, what declares each one, and the exact shape the execution graph is parsed from.
Review and delivery
Read the change across repos in one workspace, then push it — preview first, apply against the fingerprint you reviewed.