Theme
ARCHIVED — Original Agent Task Orchestrator concept
Historical input preserved verbatim below. This file is not current architecture. Use
docs/product/,docs/architecture/,docs/design/, anddocs/context.mdfor current truth. This archive exists so earlier ideas and research are never lost.
Agent Task Orchestrator — Spec & Build Plan
Stack: Node + TypeScript + Effect-TS + SQLite (via @effect/sql-sqlite-node, wrapping better-sqlite3) Purpose: primarily a vehicle for learning Effect-TS in a real, if modest, concurrency/process-supervision context.
1. The full lifecycle
Two linked loops: an optional Plan loop (grill it until it's right, then split into tasks), and a Task loop (work it, review it twice, done). Both loops share the same shape: draft → review → (reject → back to work, repeat) → (approve → advance).
PLAN LOOP (optional)
┌─────────────────────────────────────────────────────────────┐
│ │
▼ │
draft ──submit──► in_review ──annotate/grill──┬─ approved ──► decomposed
└─ rejected ───────┘
(back to draft, round++)
decomposed emits N tasks (+ dependency edges)
│
▼
TASK LOOP (per task)
backlog ──[optional planning sub-loop, same shape as above]──► todo
│
▼
in_progress ◄────────────────┐
(agent "doing/working") │
│ │
▼ │
agent_review │
┌───────┴────────┐ │
approved rejected ──────────┤
│ (round++, back to in_progress)
▼
human_review │
┌───────┴────────┐ │
approved rejected ────────────────┘
│ (round++, back to in_progress)
▼
done ──(optional, later)──► archivedKey rule, both loops: rejection never sends work to a "revise the review" step — it sends it straight back to the doing state (draft for plans, in_progress for tasks), carrying the annotations as context. Rinse and repeat until approved. Every round is counted, not just timestamped, so you can see at a glance how many iterations something took.
2. Data model (SQLite)
sql
CREATE TABLE projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
repo_path TEXT NOT NULL,
default_branch TEXT NOT NULL DEFAULT 'main',
created_at TEXT NOT NULL
);
CREATE TABLE agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
role TEXT NOT NULL, -- 'worker' | 'reviewer' | 'planner'
command_template TEXT NOT NULL
);
-- PLAN LOOP -----------------------------------------------------------
CREATE TABLE plans (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
title TEXT NOT NULL,
content TEXT NOT NULL, -- markdown, the plan document
status TEXT NOT NULL DEFAULT 'draft', -- draft | in_review | approved | decomposed
round INTEGER NOT NULL DEFAULT 0, -- increments on every rejection
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE plan_reviews (
id TEXT PRIMARY KEY,
plan_id TEXT NOT NULL REFERENCES plans(id),
round INTEGER NOT NULL,
reviewer_kind TEXT NOT NULL, -- 'agent' | 'human'
verdict TEXT NOT NULL, -- 'approved' | 'rejected'
annotations TEXT, -- JSON: [{lineRange, comment, suggestedReplacement}]
created_at TEXT NOT NULL
);
-- TASK LOOP -------------------------------------------------------------
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
plan_id TEXT REFERENCES plans(id), -- nullable: not every task comes from a Plan
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'backlog',
-- backlog | planning | todo | in_progress | agent_review | human_review | done | archived
requires_planning INTEGER NOT NULL DEFAULT 0, -- per-task execution-plan sub-loop, optional
execution_plan TEXT, -- approved per-task plan content, if any
planning_round INTEGER NOT NULL DEFAULT 0,
review_round INTEGER NOT NULL DEFAULT 0, -- increments on every agent_review/human_review rejection
assigned_agent_id TEXT REFERENCES agents(id),
reviewer_agent_id TEXT REFERENCES agents(id),
worktree_path TEXT,
branch_name TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE task_dependencies (
task_id TEXT NOT NULL REFERENCES tasks(id),
depends_on_task_id TEXT NOT NULL REFERENCES tasks(id),
PRIMARY KEY (task_id, depends_on_task_id)
);
CREATE TABLE task_events (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL REFERENCES tasks(id),
event_type TEXT NOT NULL, -- 'status_change' | 'agent_started' | 'agent_finished' | 'review_submitted' | ...
payload TEXT, -- JSON
created_at TEXT NOT NULL
);
CREATE TABLE reviews (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL REFERENCES tasks(id),
stage TEXT NOT NULL, -- 'agent_review' | 'human_review'
round INTEGER NOT NULL,
reviewer_kind TEXT NOT NULL, -- 'agent' | 'human'
verdict TEXT NOT NULL, -- 'approved' | 'rejected'
annotations TEXT, -- JSON, Plannotator diff-mode output
created_at TEXT NOT NULL
);3. Architecture (Effect-TS layers)
AppLayer =
SqlLive
.pipe(Layer.provideMerge(PlanRepoLive))
.pipe(Layer.provideMerge(TaskRepoLive))
.pipe(Layer.provideMerge(WorktreeManagerLive))
.pipe(Layer.provideMerge(AgentRunnerLive))
.pipe(Layer.provideMerge(ReviewServiceLive)) // Plannotator: annotate-mode & diff-mode
.pipe(Layer.provideMerge(PlanServiceLive)) // draft ⇄ review loop → decompose
.pipe(Layer.provideMerge(TaskOrchestratorLive)) // state machine, both review loops, dependency-aware queue
.pipe(Layer.provideMerge(HttpApiLive))- PlanService
submitForReview(planId)→ Plannotator annotate-mode oncontent.recordReview(planId, verdict, annotations)→ ifrejected:round++, status back todraft, annotations attached as context for the next draft pass. Ifapproved: status →approved.decompose(planId)→ planner agent proposes{ title, description, dependsOnIndices, requiresPlanning }[]; caller reviews/edits; on confirm, persists as realtasks+task_dependenciesand flips plan todecomposed.
- TaskOrchestrator — the shared "loop" logic is one function reused for both review stages and the optional per-task planning sub-loop:tsSame shape handles
const handleReviewOutcome = (task, stage, verdict, annotations) => verdict === "rejected" ? TaskRepo.update(task.id, { status: "in_progress", review_round: task.review_round + 1 }) .pipe(Effect.zipRight(recordAnnotationsAsContext(task.id, annotations))) : stage === "agent_review" ? TaskRepo.update(task.id, { status: "human_review" }) : TaskRepo.update(task.id, { status: "done" })planningsub-loop rejections back tobacklog/re-draft ofexecution_plan. - Queue watcher only assigns a
todotask once everytask_dependenciesrow for it points to adonetask (unchanged from before). - ReviewService —
reviewPlan(plan)(annotate-mode) andreviewDiff(task, stage)(diff-mode foragent_review/human_review, orExitPlanModemode for the per-task planning sub-loop). - WorktreeManager / AgentRunner / HttpApi unchanged.
4. Per-task planning sub-loop vs. the Plan screen
| Plan screen Per-task planning flag | ||
|---|---|---|
| Scope | Epic/feature, produces many tasks | One task's own implementation approach |
| Trigger | Started manually | requires_planning = 1 |
| Loop shape | draft ⇄ in_review (annotate), rinse-repeat | same shape, scoped to execution_plan field |
| Output | N new tasks + dependency edges | One approved plan attached to that task |
A Plan's decomposition can set requires_planning = 1 on any task it creates that's still underspecified — that task then runs its own mini plan-loop before an agent starts touching code.
5. Dependencies & parallelism
- Tasks with no
task_dependenciesrows are eligible for assignment immediately — independent worktrees, true parallelism. - A dependent task is assigned only once every dependency is
done. - Branch dependent tasks off the default branch after the dependency merges — not off an unmerged sibling branch. Simpler, avoids conflicting concurrent edits. Branching off an unmerged sibling (to start earlier) is a real optimization — Phase 4+ item, not part of the initial build.
- Validate the dependency graph is acyclic (DFS) on every edge insert; reject cycles at write time.
6. Which codebase to start from
Backend: fresh, in Effect-TS (§3). Frontend: build your own — none of the surveyed boards are worth forking wholesale:
- Vibe Kanban (Rust/Axum backend + React frontend, Apache-2.0): backend not reusable (Rust); frontend passed over too now — own UI opinions win over saving scaffold time here.
- Nimbalyst/Crystal: reference only, for the diff-review pane UX.
- Cline's kanban: confirms the "board as a layer over git state" pattern; not a base to fork.
- Orca (AO): not worth untangling.
- Plannotator: run as a self-hosted local sidecar process for both annotate-mode (plans) and diff-mode (task reviews) — this one you do keep, it's a backend integration, not a frontend fork.
Since the board is now greenfield too, sketch the actual screens (board view, plan view, task detail/review pane) before writing components — worth doing as its own short pass rather than improvising column-by-column.
7. Agent Runner interface
ts
interface Agent {
id: string
role: "worker" | "reviewer" | "planner"
commandTemplate: string
}
interface AgentRunner {
run(agent: Agent, worktreePath: string, prompt: string): Effect.Effect<AgentResult, AgentError>
}
interface AgentResult {
exitCode: number
stdout: string
diffSummary: { filesChanged: number; insertions: number; deletions: number }
}CLI-agnostic by design — Claude Code, Codex, or anything else is just a row in agents with a different command_template.
8. Build order
Phase 0 — skeleton
- Full schema (plans, plan_reviews, tasks, task_dependencies, reviews) from day one.
TaskRepo+PlanRepoCRUD, bare HTTP API.
Phase 1 — one agent, one task, manual
WorktreeManager,AgentRunnerwith a single hardcoded agent.- Manually drive a task to a diff. No review loop yet.
Phase 2 — task review loop (the core of the whole system)
- Plannotator diff-mode as
ReviewService. - Wire
agent_review ⇄ in_progressandhuman_review ⇄ in_progresswith round-counting. - This is the point where "rinse and repeat until approved" actually exists end-to-end.
Phase 3 — Plan loop
PlanServicedraft ⇄ review loop (annotate-mode).decomposewith confirm-before-commit UI.
Phase 4 — dependencies & parallelism
task_dependenciesenforcement in the queue watcher, cycle guard.- (Later) branch-off-unmerged-sibling optimization.
Phase 5 — board UI (greenfield)
- Screen sketch first: board view (columns per status), plan view (draft/review/decompose), task detail (diff + annotation pane, round history).
- Build against the HTTP API that's already stable by this point — backend contracts shouldn't need to change to fit the UI.
- Activity feed from
task_events; round counters visible on cards.
Phase 6 — polish
- Multi-agent pools, retries, timeouts,
done → archivedlifecycle, cross-project dashboard.
9. Open questions to settle before Phase 0
- Single-user only, or auth/multi-user from the start?
- One agent CLI to start with, or multi-agent from day one?
- Rejection re-run: same agent session with feedback appended, or fresh session with feedback injected into the prompt? (Fresh is simpler/more robust; recommend starting there.)
- Does the "planner" agent role use the same CLI/model as workers, or a separate, more deliberate model call?
- Any cap on rounds before a task/plan gets flagged for direct human intervention instead of another automated pass? (Worth adding early — an infinite rinse-repeat loop between two agents is a real failure mode.)