| name | openstation-orchestrate |
| description | Drive a DAG of Open Station tasks to done — plan waves from dependencies, dispatch with sane defaults, watch via tmux, retry, advance on verification, and escalate blockers. Use deliberately when asked to "run the epic", "drive these tasks", "orchestrate the milestones", or to advance a multi-task plan. |
| user-invocable | false |
Open Station Orchestrator
Drive an epic (or any DAG of tasks) from ready to done. This is
the acting half of the supervise/orchestrate split: it mutates
state (status transitions, openstation run, possibly commits).
Diagnosis is always delegated — never re-implement it here.
Act-vs-diagnose boundary (see
[[t0478-supervision-and-orchestration-skill-split]]):
| Concern | Skill | Mutates state? |
|---|
| Sense — list, health, recommend | openstation-supervisor / openstation.check | Never |
| Act — dispatch, retry, advance, escalate | openstation-orchestrate (this) | Yes |
The orchestrator calls the supervisor's diagnosis as its inspect
step; the supervisor never calls back.
Invoke deliberately — this skill spends budget. Default budgets and
turn caps are tuned for implementation work (see §2).
Procedure
1. Plan from the DAG
Build the wave plan before dispatching anything.
-
Resolve the epic (parent task) — read its subtasks frontmatter
and the body's ## Subtasks section to enumerate children.
openstation show <epic> --json
-
For each child, read its depends_on frontmatter:
openstation show <child> --json
-
Compute waves by topological order:
- Wave 0 (entry) — children with no
depends_on, or whose
deps are all done.
- Wave N — children whose deps are all satisfied by waves
0..N-1.
-
Promote backlog → ready only when deps are satisfied.
Use /openstation.ready <task> (never edit frontmatter
directly). Tasks with unsatisfied deps stay in backlog.
-
Record the plan before dispatching — print the wave table
to the user so the plan is auditable:
| Wave | Task | Deps | Files (modules touched) |
|---|---|---|---|
| 0 | t0479 | — | .openstation/skills/openstation-supervisor/ |
| 0 | t0481 | — | src/foo/ |
| 1 | t0480 | t0479 | .openstation/skills/openstation-orchestrate/ |
2. Dispatch with sane defaults
Use --turns 150 --budget 15 for implementation milestones.
The shipped defaults (50→100 turns, $5 budget) caused ~⅓ of the
Phase 2 runs to be turn-cap continuations and one milestone hit
the $10 budget cap.
openstation run --task <id> -d --turns 150 --budget 15
Sizing guidance:
| Task shape | Suggested --turns | Suggested --budget |
|---|
| Implementation milestone (multi-file, tests) | 150 | 15 |
| Spec / skill / docs edit | 80 | 5 |
| Research-only | 60 | 5 |
| Single-file refactor | 80 | 5 |
| Verification (auto-verify hook) | leave default | leave default |
Always dispatch detached (-d) for orchestrated runs so the loop
can supervise multiple tasks. Use --worktree per §6.
3. Monitor via tmux — delegate the diagnosis
The loop's monitoring step is tmux-based liveness + supervisor
diagnosis. Never re-implement diagnosis in this skill.
For each dispatched task:
-
Liveness check (tmux, CLI-independent):
tmux list-windows -a -F "#{window_name}" 2>/dev/null | grep <task-id>
- Match → RUNNING (wait; do not act).
- No match,
tmux exit 0 → NOT RUNNING (proceed to
diagnosis).
tmux not installed / non-zero exit → UNKNOWN —
escalate to the user; do not assume done.
-
Diagnosis (delegate): for any NOT RUNNING / UNKNOWN /
ambiguous task, invoke:
/openstation.check <task-id>
…or follow the openstation-supervisor skill end-to-end. Read
the diagnosis it returns. Do not duplicate its log analysis
prose in this skill.
-
Trust ordering (per supervisor §4b):
task status > Verification Report > pytest spot-check > agent
self-narration. Act on the artifact, not the chat.
-
Async auto-commit caveat (per supervisor §4a): a *→done
may not yet have committed. Before flagging a missing commit,
re-check or read openstation events list --task <id>.
4. Retry policy
Map the diagnosis from §3 to one of these actions. Work persists
on disk across re-runs (turn-capped runs resume cleanly).
Diagnosis (from supervisor / openstation.check) | Action |
|---|
error_max_turns / hit turn cap | Re-dispatch with --turns 200 (or +50 over the prior cap) |
error_max_budget_usd / hit budget cap | Re-dispatch with --budget 25 (or +10 over the prior cap) |
| Process killed / tmux disappeared mid-run, no completion event | Re-dispatch at current caps |
failed status with hard blocker (per the failure report) | Stop. Escalate per §8 — do not re-dispatch blindly |
review → in-progress (verifier bounced; rework) | See "Verifier-bounce loop" below |
verified → ready to advance | See §5 |
| UNKNOWN / CLI-down | Stop the loop. Escalate per §8 |
Verifier-bounce loop (review → in-progress):
- Read the task's
## Verification Report → "What Needs Fixing".
- Confirm the failure is real before re-dispatching the dev:
run
pytest <relevant-path> (or the targeted check named in
the report). This avoids re-running the dev on a phantom
failure caused by a verifier mistake.
- If the failure is real → re-dispatch the dev with the fix
list. If the failure is not real (verifier was wrong) →
escalate per §8; do not auto-flip the task back to
review.
Retry budget. Cap re-runs of a single task at 3 consecutive
attempts at the same step (dev or verifier). On the 4th attempt
need, escalate per §8 — that's a structural problem, not a
persistence problem.
5. Advance on verification (auto-verify aware)
A *→review hook may auto-dispatch the owner / verifier. Do
not double-dispatch. Before launching a verifier:
tmux list-windows -a -F "#{window_name}" 2>/dev/null | grep -E "verify-<task-id>|<task-id>"
…and check openstation sessions <task-id> for an active verify
run. If one is in flight, wait.
Lifecycle the orchestrator drives (using the canonical commands —
never edit frontmatter):
| Transition | Command | When |
|---|
backlog → ready | /openstation.ready <task> | Deps satisfied |
ready → in-progress | openstation run --task <id> -d ... | Dispatch (§2) |
in-progress → review | (the dev does this) | Don't drive it |
review → verified | (the verifier / /openstation.verify) | Don't drive it |
verified → done | /openstation.done <task> | After human/owner sign-off where required |
failed → ready | /openstation.ready <task> (after the fix) | Only after the underlying blocker is fixed |
On verified → done, recompute waves (§1) — newly unblocked
children promote to ready and become candidates for dispatch.
6. Serial-vs-parallel rule
Never run tasks in parallel that touch overlapping files on the
same branch. Concurrent writes on main were the source of the
Phase 2 M4/M5/M6 entanglement (uncommitted work intermixed, had
to hold closes and untangle).
Decision tree per wave:
- For every pair of tasks in the wave, intersect the file/module
sets declared in their
## Scope / ## File sections (the
plan table in §1 should capture this).
- Disjoint modules + same branch → may run in parallel.
- Overlapping files + same branch → serialize. Run one,
wait for
done, then the next.
- Overlap unavoidable but parallelism wanted → dispatch each
with
--worktree so they run on separate branches; merge
sequentially afterward.
When in doubt: serialize. The cost of a slower wave is much lower
than the cost of untangling concurrent writes.
7. External-surface handoff
Sandboxed agents cannot run privileged external commands —
notably gh (PR ops) and git push. When a child task requires
an external surface (publish a repo, push a branch, open a PR,
create a release, post to an external API):
- Pause the task at
in-progress. Do not transition it to
review or done; the work is incomplete.
- Escalate to the user / PM with a precise handoff:
- the exact privileged command(s) to run,
- the branch / artifact involved,
- the expected post-state (e.g. "PR open at …").
- Wait for confirmation that the external step succeeded
before resuming the loop.
- Resume. Re-dispatch the task (or simply advance to
review) once the external state is in place.
Never attempt the privileged command from inside the orchestrator
or a dispatched agent — the failure is structural (sandbox
policy), not retryable.
8. Blocker escalation
Surface to the human rather than auto-deciding when:
- Convergence / design call — two tasks disagree on shape;
one must be rejected or rescoped (e.g. Phase 2's
t0456
reject, t0455/t0457 pause). The orchestrator does not
decide reject vs rescope.
- Ambiguous verification — verifier and dev disagree and the
pytest spot-check is also ambiguous, or the verification
criteria themselves are unclear.
- Irreversible action — anything that mutates external state
(publish, push, release, send) or destructive local state
(force-push, history rewrite, branch delete).
- Retry budget exceeded — 3 consecutive failures at the same
step (§4).
- CLI-down / UNKNOWN liveness (§3) — the control plane is
broken; stop the loop until the user fixes it.
- Hard blocker reported via
/openstation.fail — read the
failure report; relay it; do not retry blindly.
Escalation format — present clearly so the user can decide
without re-reading logs:
## Orchestrator: Decision Needed
**Task**: <id> — <slug>
**State**: <current status>
**Reason**: <one-sentence why this needs the human>
**Evidence**:
- <link / quote from Verification Report / pytest output / event log>
- <relevant artifact paths>
**Options**:
1. <option A>
2. <option B>
3. <option C — usually "stop the epic">
After escalating, pause the loop for the affected sub-DAG.
Other waves on independent branches may continue if they don't
depend on the blocked task.
Constraints
- Acts; does not diagnose. Status transitions,
openstation run, and commits (where authorized) are this skill's job.
Per-task health analysis and log-reading must be delegated
to openstation-supervisor / /openstation.check.
- Use canonical commands for transitions —
/openstation.ready,
/openstation.done, /openstation.fail, /openstation.suspend,
/openstation.reject. Never edit task frontmatter directly to
flip status.
- No external-surface commands from this skill.
gh pr *,
git push, releases — escalate per §7.
- Default to serialize, not parallelize, when file overlap is
ambiguous (§6).
- Respect the retry budget — 3 strikes per step, then
escalate (§4).
- Stop on CLI-down / UNKNOWN. Do not advance the plan when
the control plane is unreliable (§3, §8).
- Auto-verify aware — never dispatch a duplicate verifier
when the
*→review hook already launched one (§5).
Out of scope
- Re-implementing diagnosis prose — see
openstation-supervisor / /openstation.check.
- Migrating the watcher / retry / dispatch-defaults mechanics
into
openstation run code + config (the deferred future
"Option D" from
[[t0478-supervision-and-orchestration-skill-split]]).
- Deciding scope, priorities, or rejections — escalate (§8); the
operator decides.