# Role pipeline: analyst → architect → decomposer → ⟲(claim → implement → review)⟲ → done # # The architect's design is broken into a task graph by the decomposer, then a single loop # drains those tasks one at a time. Each implementer entry deterministically CLAIMS the next # ready task (claim_task = true) — the kernel picks it, not the LLM — and injects that task's # acceptance criteria + blockers as L0 context. While a task is claimed, file writes are # auto-scoped to its affected_paths; the implementer widens scope only via propose_scope # (operator-approved). The loop is gated by the tasks_ready predicate, so it exits when the # board is drained. # # Loop control depends on transition ORDERING. The resolver evaluates a stage's outgoing # transitions sorted by transition id and takes the first whose condition holds, so the # reviewer edges are named review-1/2/3 to force this precedence: # 1. review-1-changes (verdict ≠ approved) → implementer (fix the current task) # 2. review-2-more (tasks_ready) → implementer (approved: claim next) # 3. review-3-done (always_true, fallthrough) → done (approved + board empty) # This avoids needing composite (all_of/not) conditions in the flat TOML schema. # # Requires these artifact kinds in ~/.config/correx/config.toml (schemas under docs/schemas/): # [[artifacts]] # id = "discovery"; schema_path = "schemas/discovery.json"; llm_emitted = true # [[artifacts]] # id = "analysis"; schema_path = "schemas/analysis.json"; llm_emitted = true # [[artifacts]] # id = "design"; schema_path = "schemas/design.json"; llm_emitted = true # [[artifacts]] # id = "review_report"; schema_path = "schemas/review_report.json"; llm_emitted = true # # Prompt files (prompts/*.md, relative to this workflow) must exist for a real run. id = "role_pipeline" start = "discovery" # 0. Vet the request against the real repo before any analysis: park on a batched list of # operator questions (underspecification or a claim the code contradicts) or clear it to # proceed. Read-only; owns clarification exclusively so the analyst always emits an analysis. [[stages]] id = "discovery" prompt = "prompts/discovery.md" produces = [{ name = "discovery", kind = "discovery" }] allowed_tools = ["file_read", "list_dir", "shell"] token_budget = 16384 max_retries = 2 # 1. Understand the request and the relevant code. Read-only. # ground_references: every workspace-relative file path the analysis names is checked # for existence; a hallucinated path fails the stage and retries with the misses fed back. [[stages]] id = "analyst" prompt = "prompts/analyst.md" produces = [{ name = "analysis", kind = "analysis" }] allowed_tools = ["file_read", "shell", "task_search", "task_context", "task_create"] ground_references = true token_budget = 16384 max_retries = 2 # 2. Decide the approach and component boundaries. [[stages]] id = "architect" prompt = "prompts/architect.md" needs = ["analysis"] produces = [{ name = "design", kind = "design" }] token_budget = 16384 max_retries = 2 # 3. Break the design into a task graph via task_decompose. require_task_decompose is a # deterministic kernel gate: stage_complete is blocked until task_decompose (or task_create # for a trivial single-task request) returns. Each child carries acceptance_criteria and # affected_paths — those drive the per-task L0 context and the per-task write scope below. [[stages]] id = "decomposer" prompt = "prompts/task_planner.md" needs = ["design"] allowed_tools = ["file_read", "shell", "task_search", "task_context", "task_decompose", "task_create"] require_task_decompose = true token_budget = 16384 max_retries = 2 # 4. Implement one task. claim_task = true: on entry the kernel claims the next ready task # (or re-surfaces the one already claimed on a review bounce) and injects its acceptance # criteria as L0. Writes are auto-jailed to the claimed task's affected_paths; propose_scope # is the operator-approved valve to widen that scope mid-task. [[stages]] id = "implementer" prompt = "prompts/implementer.md" produces = [{ name = "patch", kind = "file_written" }] claim_task = true allowed_tools = [ "file_read", "file_write", "file_edit", "shell", "propose_scope", "task_create", "task_update", "task_context", "task_search", ] token_budget = 32768 max_retries = 3 # 5. Review the patch against the claimed task's acceptance criteria and the analyst's brief. # On approval the reviewer marks the task done via task_update — that drops it from the # active claim so the next implementer entry claims a fresh task and the loop advances. [[stages]] id = "reviewer" prompt = "prompts/reviewer.md" needs = ["patch", "analysis"] produces = [{ name = "review_report", kind = "review_report" }] allowed_tools = ["task_context", "task_update"] token_budget = 32768 max_retries = 2 # --- forward edges --- [[transitions]] id = "discovery-to-analyst" from = "discovery" to = "analyst" condition_type = "artifact_validated" condition_artifact_id = "discovery" [[transitions]] id = "analyst-to-architect" from = "analyst" to = "architect" condition_type = "artifact_validated" condition_artifact_id = "analysis" [[transitions]] id = "architect-to-decomposer" from = "architect" to = "decomposer" condition_type = "artifact_validated" condition_artifact_id = "design" # Only proceed to implementation once the decomposer actually produced ready tasks. [[transitions]] id = "decomposer-to-implementer" from = "decomposer" to = "implementer" condition_type = "tasks_ready" [[transitions]] id = "implementer-to-reviewer" from = "implementer" to = "reviewer" condition_type = "artifact_validated" condition_artifact_id = "patch" # --- verdict + task-board gated loop (id order = evaluation precedence, see header) --- # 1. Changes requested → keep refining the current (still-claimed) task. [[transitions]] id = "review-1-changes" from = "reviewer" to = "implementer" condition_type = "artifact_field_equals" condition_artifact_id = "review_report" condition_field = "verdict" condition_value = "approved" condition_operator = "neq" # 2. Approved and tasks remain → claim the next one. [[transitions]] id = "review-2-more" from = "reviewer" to = "implementer" condition_type = "tasks_ready" # 3. Approved and the board is drained → done (fallthrough). [[transitions]] id = "review-3-done" from = "reviewer" to = "done" condition_type = "always_true"