|
| 1 | +# AI Orchestrator |
| 2 | + |
| 3 | +The AI orchestrator drives a multi-issue implementation plan from [plan.json](plan.json): it plans dependencies, implements AFK (away-from-keyboard) issues in isolated git worktrees, reviews changes, merges successful branches into the main repo, and cleans up implementation branches when all AFK work is done. |
| 4 | + |
| 5 | +Skills to run in order to produce the `plan.json` |
| 6 | + |
| 7 | +- grill-with-docs |
| 8 | +- to-prd |
| 9 | +- to-issues-as-json |
| 10 | + |
| 11 | +## Running |
| 12 | + |
| 13 | +Entry point: [main.ts](main.ts) — resolves the repository root (`..`), `plan.json`, and `logs/`. |
| 14 | + |
| 15 | +```bash |
| 16 | +npm run ai:run:plan |
| 17 | +``` |
| 18 | + |
| 19 | +This runs `npx tsx ai-orchestrator/main.ts`. |
| 20 | + |
| 21 | +## Plan file (`plan.json`) |
| 22 | + |
| 23 | +Each issue in `plan.json` follows the [Issue](utils/orchestrator.types.ts) shape: |
| 24 | + |
| 25 | +| Field | Role | |
| 26 | +| -------------------------------------------------- | ---------------------------------------------------------------------------- | |
| 27 | +| `id`, `title`, `whatToBuild`, `acceptanceCriteria` | Fed to the implement agent | |
| 28 | +| `type` | `AFK` (automated) or `HITL` (human-in-the-loop; skipped by the orchestrator) | |
| 29 | +| `isPlanned`, `blockedBy` | Set by the planning phase | |
| 30 | +| `passes` | Set to `true` after a successful merge | |
| 31 | + |
| 32 | +An issue is **unblocked** when it is `isPlanned`, not yet `passes`, has `type === "AFK"`, and every id in `blockedBy` has `passes: true` (see [plan.ts](utils/plan.ts) `getUnblocked()`). |
| 33 | + |
| 34 | +The plan file is reloaded at the start of each iteration and saved after planning and merge phases. |
| 35 | + |
| 36 | +## Workflow |
| 37 | + |
| 38 | +`Orchestrator.run()` in [orchestrator.ts](orchestrator.ts) runs up to `maxIterations` (default **20**). Each iteration: |
| 39 | + |
| 40 | +1. **Reload plan** — `plan.load()` re-reads `plan.json` from disk. |
| 41 | +2. **Planning** — If any issue has `isPlanned: false`, the **planner** agent runs (`prompts/plan.md`) with the full plan JSON. It returns `{ id, blockedBy }[]`; the orchestrator calls `markPlanned` and saves the plan. |
| 42 | +3. **Select work** — `getUnblocked()`. If there are no unblocked issues, the loop exits (logs distinguish: all AFK complete vs. blocked/circular dependency vs. remaining HITL). |
| 43 | +4. **Implementation** — Unblocked issues run in parallel via `Promise.allSettled`: |
| 44 | + - Branch name: `orchestrator/implementation-task-{issueId}` |
| 45 | + - [withWorktree](utils/worktree.utils.ts) creates a worktree under `.orchestrator-worktrees/`, symlinks `node_modules`, runs the callback, then removes the worktree in `finally` |
| 46 | + - **Implement** agent (`implement.md`, cwd = worktree) |
| 47 | + - If the branch has commits not on `HEAD` (`hasCommits`), run the **Review** agent (`review.md`); otherwise skip review |
| 48 | + - Collect issues that produced commits; failures are logged and the iteration continues |
| 49 | +5. **Merge** — If any issue produced commits this iteration, the **merger** agent (`merge.md`) runs in the main repo. Structured output `{ merged, failed }`; merged issue ids get `markPassed` and the plan is saved. |
| 50 | +6. If no issue produced commits, skip merge and start the next iteration. |
| 51 | + |
| 52 | +After the loop, if there are **no remaining AFK issues** (`remainingAfkIssues.length === 0`), **cleanup** deletes `orchestrator/implementation-task-*` branches for passed AFK issues. |
| 53 | + |
| 54 | +## Agents |
| 55 | + |
| 56 | +| Phase | Prompt | Model | Working directory | |
| 57 | +| --------- | ------------------------------------ | ----------------- | ----------------- | |
| 58 | +| Plan | [plan.md](prompts/plan.md) | claude-opus-4-7 | repo root | |
| 59 | +| Implement | [implement.md](prompts/implement.md) | claude-opus-4-7 | worktree | |
| 60 | +| Review | [review.md](prompts/review.md) | claude-sonnet-4-6 | worktree | |
| 61 | +| Merge | [merge.md](prompts/merge.md) | claude-sonnet-4-6 | repo root | |
| 62 | + |
| 63 | +Agent execution uses the Claude Agent SDK in [agent.utils.ts](utils/agent.utils.ts). Per-agent logs are written under `logs/` via [agent-logger.utils.ts](utils/agent-logger.utils.ts). |
| 64 | + |
| 65 | +## Worktrees and branches |
| 66 | + |
| 67 | +- Implementation branches: `orchestrator/implementation-task-{issueId}` |
| 68 | +- Worktrees live under `.orchestrator-worktrees/` (branch slashes become `--` in the directory name) |
| 69 | +- The worktree is removed after each issue’s implement/review callback; the branch may remain until global cleanup |
| 70 | +- Cleanup runs only when all AFK issues have `passes: true` |
| 71 | + |
| 72 | +## Project layout |
| 73 | + |
| 74 | +``` |
| 75 | +ai-orchestrator/ |
| 76 | + main.ts # CLI entry |
| 77 | + orchestrator.ts # Orchestrator class |
| 78 | + plan.json # Issue backlog (mutable at runtime) |
| 79 | + prompts/ # Agent prompt templates |
| 80 | + utils/ # plan, worktree, agent helpers |
| 81 | + logs/ # Per-agent run logs (created at runtime) |
| 82 | +``` |
| 83 | + |
| 84 | +## Workflow diagram |
| 85 | + |
| 86 | +Main iteration loop: |
| 87 | + |
| 88 | +```mermaid |
| 89 | +flowchart TD |
| 90 | + start([run]) --> iter{iteration le maxIterations} |
| 91 | + iter --> load[load plan.json] |
| 92 | + load --> planPhase{unplanned issues?} |
| 93 | + planPhase -->|yes| planner[Planner agent: set blockedBy] |
| 94 | + planPhase -->|no| unblocked |
| 95 | + planner --> savePlan1[save plan] |
| 96 | + savePlan1 --> unblocked{unblocked AFK issues?} |
| 97 | + unblocked -->|no| exitLoop[break loop] |
| 98 | + unblocked -->|yes| implParallel[Parallel: worktree per issue] |
| 99 | + implParallel --> implement[Implement agent] |
| 100 | + implement --> hasCommits{commits on branch?} |
| 101 | + hasCommits -->|yes| review[Review agent] |
| 102 | + hasCommits -->|no| skipReview[skip review] |
| 103 | + review --> collect[collect issues with commits] |
| 104 | + skipReview --> collect |
| 105 | + collect --> anyDone{any completed?} |
| 106 | + anyDone -->|no| iter |
| 107 | + anyDone -->|yes| merger[Merger agent] |
| 108 | + merger --> markPassed[markPassed + save plan] |
| 109 | + markPassed --> iter |
| 110 | + exitLoop --> cleanup{all AFK done?} |
| 111 | + cleanup -->|yes| deleteBranches[delete implementation branches] |
| 112 | + cleanup -->|no| done([All done]) |
| 113 | + deleteBranches --> done |
| 114 | +``` |
| 115 | + |
| 116 | +Per-issue worktree lifecycle: |
| 117 | + |
| 118 | +```mermaid |
| 119 | +flowchart LR |
| 120 | + create[create worktree + symlink node_modules] --> implement[Implement agent] |
| 121 | + implement --> check{hasCommits?} |
| 122 | + check -->|yes| review[Review agent] |
| 123 | + check -->|no| remove[remove worktree] |
| 124 | + review --> remove |
| 125 | + remove --> branchRemains[branch kept until cleanup] |
| 126 | +``` |
0 commit comments