Skip to content

feat(orchestration): token-free recipe director — launchRecipeDirector + implement_then_review (#9) - #23

Merged
zaridan merged 4 commits into
mainfrom
zaridan/feat-orch-9-recipe-director-backend
Jun 24, 2026
Merged

feat(orchestration): token-free recipe director — launchRecipeDirector + implement_then_review (#9)#23
zaridan merged 4 commits into
mainfrom
zaridan/feat-orch-9-recipe-director-backend

Conversation

@zaridan

@zaridan zaridan commented Jun 24, 2026

Copy link
Copy Markdown
Owner

#9 — RecipeDirectorBackend: the token-free recipe director

A director that runs a fixed recipe with ZERO director LLM tokens. The "director" is deterministic dispatch — the worker agents do all the coding. Built entirely on the merged bridge runtime (F1–F4, O1/O2); no worktree/dispatch logic is forked.

Flow — launchRecipeDirector(project, recipe)

  1. Create the no-LLM director shell. The hidden director worktree-shell creation (prefix ORCASTRATOR · , 'skip' setup) is extracted from launchOrchestratorForProject into a shared createDirectorWorktreeShell. The recipe director creates that same shell but activates it with no startup payload — a blank terminal, no agent, no /orcastrate.
  2. Compile the recipe → tasks. compileRecipe topo-sorts by deps, prepends the coordinator's track: spec hint, and fails fast on unknown/self/cyclic deps. The launch path then calls window.api.orchestration.taskCreate per task, resolving each dependsOn key to the previously-created task id and stamping every task to the shell worktree (see plumbing below).
  3. Start the run. window.api.orchestration.run({ worktree: id:<shell>, worktreeBacked: true, workerAgent: <default coding agent> }). The merged coordinator creates per-track child worktrees (lineage parent = the director shell) and launches the real worker agents. The live Control Panel renders the DAG automatically.

The director shell runs no agent → no director LLM tokens. The only LLM cost is the worker agent(s) doing implement/review.

The one recipe this PR ships — implement_then_review

Two tasks, implement and review, on the same track → one worktree, one branch, one PR. review.dependsOn = [implement] so review runs after implement finishes — a real handoff that also satisfies the coordinator's same-track safe-ordering guard (it refuses a worktree-backed run whose same-track tasks aren't ordered by deps).

Plumbing — taskCreate targetWorktree

For run-start adoptUnownedTasks to claim a pre-created task, the task's target_key must equal the run's. The coordinator stamps target_key from the creating terminal's worktree, but the renderer recipe director has a worktree id, not a live terminal handle. So orchestration.taskCreate gains an optional targetWorktree selector that stamps target_key through the same resolveOrchestrationTargetKey that run uses for worktree. It takes precedence over callerTerminalHandle and fails closed when the selector doesn't resolve. The run's from is bound to the shell's terminal handle (best-effort, via terminal.resolveActive) so the Control Panel keys the DAG on the director pane; on a miss the run still starts.

Tests

  • Compilationimplement_then_review → 2 tasks, same track, review depends on implement; topo ordering; rejects unknown/cyclic/self/duplicate deps.
  • Token-free invariant — the shell is activated with no agent startup payload (no agent command, no /orcastrate).
  • Launch calls — issues the right ordered taskCreate (deps wired, targetWorktree stamped) + a worktree-backed run({ worktreeBacked, workerAgent }); tasks created before the run starts; blank-agent → claude fallback; graceful shell-create / handle-resolve failure.
  • RPCtaskCreate --target-worktree stamps target_key via the shared resolver and fails closed.

Full vitest + typecheck + electron-vite/web build + oxlint green.

Gating & scope

Behind experimentalOrchestrators. Per-run isolation (F1) intact; reuses the merged worktree-backed coordinator + F4 run binding.

Deferred (not this PR): the full starter-recipe set (#10), the DirectorBackend abstraction (#8), the director-type picker UI (#11), hybrid mode. Wiring the recipe name into the run-DAG recipe field (currently null) needs a coordinator-run column and is left to the abstraction work in #8/#10.

Do not merge.

🤖 Generated with Claude Code

zaridan and others added 4 commits June 23, 2026 20:16
Add the built-in Recipe/RecipeTask types and the one canonical recipe this
PR ships, `implement_then_review` — implement + review on the SAME track
(one worktree → one PR) with review.dependsOn=[implement] so review runs
after implement finishes (a real handoff that also satisfies the
coordinator's same-track safe-ordering guard).

`compileRecipe` lowers a recipe into dependency-ordered taskCreate inputs:
it topo-sorts by deps, prepends the coordinator's `track:` spec hint, and
fails fast on unknown/self/cyclic deps. Pure + deterministic so the launch
path can walk it, calling taskCreate per task and resolving dependsOn keys
to created ids as it goes. No director LLM tokens involved.

Tests assert the implement_then_review compilation (2 tasks, same track,
review after implement) against the coordinator's real parseTrackFromSpec.

Deferred: full recipe set (#10), DirectorBackend abstraction (#8), picker
UI (#11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ors (#9)

orchestration.taskCreate stamps a task's target_key from the creating
terminal's worktree, but the renderer recipe director has the director
worktree id, not a live terminal handle. Add an optional `targetWorktree`
selector that stamps target_key through the SAME resolveOrchestrationTargetKey
that orchestration.run uses for `worktree` — so a pre-created task's key
matches the run's target and run-start adoptUnownedTasks claims it.

Precedence: an explicit targetWorktree wins over callerTerminalHandle; it
fails closed (refuses the task) when the selector doesn't resolve, mirroring
run's guard. Plumbed through the shared preload binding type; preload/web
forward params verbatim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#9)

Extract the hidden director worktree-shell creation shared by both director
kinds into createDirectorWorktreeShell (no agent, no prompt → token-free by
construction); launchOrchestratorForProject now layers the coordinator agent
+ /orcastrate on top of it.

Add launchRecipeDirector(project, recipe): create the no-LLM shell, activate
it with NO startup payload (blank terminal — the token-free invariant), then
compile the recipe and create one task per recipe task (track hint in the
spec, deps wired key→created id, targetWorktree stamping each to the shell so
run-start adoption claims them), then start a worktree-backed coordinator run
anchored on the shell with the user's default coding agent as the worker. The
run's `from` is bound to the shell's terminal handle (best-effort) so the live
Control Panel keys the DAG on the director pane; on a miss the run still
starts. The only LLM cost is the worker agent(s) doing implement/review.

Tests: recipe→taskCreate compilation (ordered, same-track, review deps
implement), token-free invariant (activation gets no agent startup), the
launch issues the right taskCreate + worktree-backed run calls, blank-agent
fallback to claude, and graceful shell-create/handle-resolve failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ee guard (#9)

Pre-merge hardenings from the PR #23 review panel (no blockers; both
load-bearing properties confirmed). Tighten the seams that have clash history.

End-to-end target_key adoption test (the F1 clash seam): prove the task path
(taskCreate --target-worktree) and the run path (run --worktree) resolve the
SAME key through the REAL resolveOrchestrationTargetKey + REAL adoptUnownedTasks
— a real in-memory OrchestrationDb, only the filesystem worktree lookup stubbed
(id:X→worktree X). A task stamped to id:W is adopted by a run on id:W; a
negative control (task on id:OTHER) is NOT adopted, so the test fails if the
two paths ever diverge.

Token-free invariant guard: comment at createDirectorWorktreeShell's
createWorktree call documenting that it MUST NOT pass createdWithAgent (else
activation's buildCreatedAgentReopenStartup fallback relaunches an LLM in the
director pane), plus a test exercising the REAL shell helper that asserts the
createdWithAgent arg stays undefined and setup is 'skip'.

Nits: drop the dead per-task agent plumbing from Recipe/CompiledRecipeTask
(re-add when #10/#11 need heterogeneous agents); throw on an unmapped
dependsOn key in the launch loop instead of silently dropping it; document
that the #11 picker MUST gate launchRecipeDirector on experimentalOrchestrators;
document the shell-handle anchoring as opportunistic (no readiness await,
fail-safe to a derived coordinator handle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zaridan

zaridan commented Jun 24, 2026

Copy link
Copy Markdown
Owner Author

Hardening round — panel pre-merge items closed

Pushed 2a1d2b4. No behavior change to the launch flow; this tightens the seams the panel flagged.

MUST 🟠 — e2e target_key adoption (the F1 clash seam). Added an integration test that proves the binding through the real resolveOrchestrationTargetKey + real db.adoptUnownedTasks on a real in-memory OrchestrationDb — no mocked resolver returning a matched literal. Only the lowest-level filesystem worktree lookup is stubbed (id:X → worktree X). A task stamped via taskCreate({ targetWorktree: 'id:W' }) is adopted by a run on id:W (coordinator_run_id set); a negative control (task on id:OTHER) is not adopted — so the test fails the instant the two paths resolve different keys.

MUST 🟡 — token-free invariant guarded. Comment at createDirectorWorktreeShell's createWorktree call spelling out that it MUST NOT pass createdWithAgent (else activation's buildCreatedAgentReopenStartup fallback relaunches an LLM in the director pane). Plus a test on the real shell helper asserting the createdWithAgent arg stays undefined and setup is 'skip'. (The launch test already covers the other half: activation gets no startup payload.)

Nits

Green: typecheck (3 projects), oxlint, electron-vite build, and vitest (new e2e + token-free tests; full orchestration + renderer-lib sweep 2152). Not merging.

@zaridan
zaridan merged commit 19085cd into main Jun 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant