summaryrefslogtreecommitdiff
path: root/docs/workflow-and-entry-points.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/workflow-and-entry-points.md')
-rw-r--r--docs/workflow-and-entry-points.md273
1 files changed, 273 insertions, 0 deletions
diff --git a/docs/workflow-and-entry-points.md b/docs/workflow-and-entry-points.md
new file mode 100644
index 0000000..d9312c5
--- /dev/null
+++ b/docs/workflow-and-entry-points.md
@@ -0,0 +1,273 @@
+# Claudomator: Workflow & Entry Points
+
+This document diagrams Claudomator's overall workflow, all entry points into the
+task-execution pipeline, the task state machine, and where the recursive
+arbitrated-review mechanism sits relative to that state machine.
+
+---
+
+## 1. Entry Points Overview
+
+Every arrow below eventually reaches `executor.Pool`, the single choke-point
+that bounds concurrency and gates spend.
+
+```mermaid
+flowchart TD
+ subgraph CLI["CLI (claudomator run)"]
+ CLI_RUN["claudomator run <file.yaml>\n(internal/cli/run.go)"]
+ end
+
+ subgraph REST["REST API"]
+ API_CREATE["POST /api/tasks\n(creates PENDING task)"]
+ API_RUN["POST /api/tasks/{id}/run\n(submits PENDING → QUEUED)"]
+ API_CREATE --> API_RUN
+ end
+
+ subgraph CHATBOT["Chatbot MCP (/chatbot/mcp)"]
+ CB_SUBMIT["submit_task tool\n(internal/api/chatbotmcp.go)"]
+ CB_STORY["create_story tool\n(creates Story row)"]
+ CB_ACCEPT["accept_story tool\n(REVIEW_READY → DONE)"]
+ CB_LIST["list_stories / get_story tools\n(read-only)"]
+ end
+
+ subgraph WEBHOOK["GitHub Webhook"]
+ WH["POST /api/webhooks/github\ncheck_run / workflow_run failure\n(internal/api/webhook.go)"]
+ end
+
+ subgraph STORY_API["Story REST API"]
+ ST_CREATE["POST /api/stories\n(creates Story row)"]
+ ST_ACCEPT["POST /api/stories/{id}/accept\n(REVIEW_READY → DONE)"]
+ end
+
+ subgraph AGENT_TOOLS["Agent Back-channel Tools\n(MCP /mcp or native tool-use loop)"]
+ AG_SPAWN["spawn_subtask\n(no role= → child with parent_task_id)\n(role= → sibling with depends_on)"]
+ AG_EPIC["propose_epic\n(creates/updates Epic, groups Stories)"]
+ AG_RC["propose_role_config\n(creates draft role_configs version)"]
+ end
+
+ subgraph ORCHESTRATOR["StoryOrchestrator (poll, 15 s)\n(internal/scheduler/story_orchestrator.go)"]
+ SO_EVAL["ensureEvaluators\n(4 Evaluator tasks per builder node)"]
+ SO_ARB["ensureArbitration\n(1 Arbitration/planner task)"]
+ SO_FIX["ensureFixAttempt / spawnNestedFixAttempt\n(new builder task after rejection)"]
+ SO_RETRO["processRetro\n(retro task after story DONE)"]
+ end
+
+ subgraph SCHEDULER["Scheduler (poll, ~10 s)\n(internal/scheduler/scheduler.go)"]
+ SC_FAIL["processTask\n(retry / escalate FAILED role-typed tasks)"]
+ SC_TIMEOUT["tickAskUserTimeouts\n(auto-answer + resume BLOCKED tasks\noutstanding > ask_user_timeout)"]
+ end
+
+ POOL(["executor.Pool\n(bounded dispatcher)"])
+
+ %% CLI path
+ CLI_RUN -->|"Pool.Submit() directly"| POOL
+
+ %% REST path
+ API_RUN -->|"Pool.Submit()"| POOL
+
+ %% Chatbot MCP paths
+ CB_SUBMIT -->|"creates task + auto-submits"| POOL
+ CB_STORY -->|"story row only;\nStoryOrchestrator spawns Builder"| ORCHESTRATOR
+ CB_ACCEPT -->|"story REVIEW_READY → DONE;\ntriggers processRetro next tick"| ORCHESTRATOR
+
+ %% Webhook path
+ WH -->|"creates task + auto-runs"| POOL
+
+ %% Story REST paths
+ ST_CREATE -->|"story row only"| ORCHESTRATOR
+ ST_ACCEPT -->|"REVIEW_READY → DONE"| ORCHESTRATOR
+
+ %% Agent tool paths
+ AG_SPAWN -->|"Pool.Submit() for new child/sibling"| POOL
+ AG_EPIC -->|"storage only\n(no task spawned)"| POOL
+ AG_RC -->|"storage only\n(no task spawned)"| POOL
+
+ %% StoryOrchestrator → Pool
+ SO_EVAL -->|"Store.CreateTask + Pool.Submit()"| POOL
+ SO_ARB -->|"Store.CreateTask + Pool.Submit()"| POOL
+ SO_FIX -->|"Store.CreateTask + Pool.Submit()"| POOL
+ SO_RETRO -->|"Store.CreateTask + Pool.Submit()"| POOL
+
+ %% Scheduler → Pool
+ SC_FAIL -->|"Pool.Submit() or Pool.SubmitResume()"| POOL
+ SC_TIMEOUT -->|"Pool.SubmitResume()"| POOL
+
+ %% Orchestrator watches Pool results
+ POOL -->|"task reaches READY/COMPLETED/FAILED;\norchestrator polls"| ORCHESTRATOR
+ POOL -->|"task reaches FAILED;\nscheduler polls"| SCHEDULER
+ POOL -->|"task stays BLOCKED;\nscheduler polls"| SCHEDULER
+```
+
+---
+
+## 2. Detailed Entry Point Reference
+
+| Entry Point | Code Location | What It Creates | Who Submits to Pool |
+|---|---|---|---|
+| `claudomator run <file>` | `internal/cli/run.go` | Task(s) from YAML | `cli` directly via `Pool.Submit` |
+| `POST /api/tasks` + `POST /api/tasks/{id}/run` | `internal/api/taskops.go` | PENDING task, then QUEUED | `api.Server` via `Pool.Submit` |
+| Chatbot MCP `submit_task` | `internal/api/chatbotmcp.go` | Task, immediately submitted | `chatbotmcp` via `Pool.Submit` |
+| Chatbot MCP `create_story` | `internal/api/chatbotmcp.go` | Story row | `StoryOrchestrator` (next tick) |
+| Chatbot MCP `accept_story` | `internal/api/chatbotmcp.go` | Story status DONE | `StoryOrchestrator` (triggers retro) |
+| `POST /api/stories` | `internal/api/stories.go` | Story row | `StoryOrchestrator` (next tick) |
+| `POST /api/stories/{id}/accept` | `internal/api/stories.go` | Story status DONE | `StoryOrchestrator` (triggers retro) |
+| `POST /api/webhooks/github` | `internal/api/webhook.go` | Task auto-tagged `["ci","auto"]` | `api.Server` via `Pool.Submit` |
+| Agent `spawn_subtask` (no `role=`) | `internal/executor/agentmcp.go`, `internal/agentloop/tools.go` | Child task with `parent_task_id` | `Pool.Submit` |
+| Agent `spawn_subtask` (with `role=`) | same | Sibling task with `depends_on`, `Agent.Role` set, no Type/Model | `Pool.Submit`; role resolved at dispatch |
+| Agent `propose_epic` | same | Epic row; groups existing stories | storage only, no task |
+| Agent `propose_role_config` | same | Draft `role_configs` version | storage only, no task |
+| `StoryOrchestrator.ensureEvaluators` | `internal/scheduler/story_orchestrator.go` | 4 Evaluator tasks per builder node | `Store.CreateTask` + `Pool.Submit` |
+| `StoryOrchestrator.ensureArbitration` | same | 1 `planner`-role Arbitration task | same |
+| `StoryOrchestrator.ensureFixAttempt` | same | New root builder task after rejection | same |
+| `StoryOrchestrator.spawnNestedFixAttempt` | same | New nested builder task after rejection | same |
+| `StoryOrchestrator.processRetro` | same | `retro`-role task after story DONE | same |
+| `Scheduler.processTask` | `internal/scheduler/scheduler.go` | Re-queues FAILED role-typed task at same or next tier | `Pool.Submit` / `Pool.SubmitResume` |
+| `Scheduler.tickAskUserTimeouts` | same | System-authored fallback answer; resumes BLOCKED task | `Pool.SubmitResume` |
+
+---
+
+## 3. Task State Machine
+
+```mermaid
+stateDiagram-v2
+ [*] --> PENDING : task created
+
+ PENDING --> QUEUED : /run submitted\n(Pool.Submit)
+
+ QUEUED --> RUNNING : dispatcher picks slot
+
+ RUNNING --> READY : execution succeeded\n(exit 0)
+ RUNNING --> BLOCKED : agent called ask_user\nor subtasks outstanding
+ RUNNING --> FAILED : execution failed\n(non-zero exit / error)
+ RUNNING --> TIMED_OUT : timeout exceeded
+ RUNNING --> CANCELLED : /cancel called
+ RUNNING --> BUDGET_EXCEEDED : spend cap hit
+
+ BLOCKED --> QUEUED : answer provided\n(POST /answer or\nscheduler timeout)
+ BLOCKED --> READY : all subtasks COMPLETED\n(maybeUnblockParent)
+
+ READY --> COMPLETED : human/chatbot accept\n(non-builder-role)\nOR\nfinalizeArbitration approves\n(builder-role)
+ READY --> PENDING : human/chatbot reject
+
+ FAILED --> QUEUED : /resume or\nScheduler retry/escalate
+ TIMED_OUT --> QUEUED : /resume
+ CANCELLED --> QUEUED : /resume
+ BUDGET_EXCEEDED --> QUEUED : /resume
+
+ COMPLETED --> [*]
+```
+
+### Key: READY does not mean "done" for builder-role tasks
+
+For **non**-builder-role tasks (the common case), `READY → COMPLETED` is a
+simple human or chatbot accept via `POST /api/tasks/{id}/accept`.
+
+For **builder**-role tasks inside a story pipeline, `READY` means
+**"awaiting arbitration"** — the `StoryOrchestrator` intercepts every `READY`
+builder node and drives it through the review cycle before ever promoting it to
+`COMPLETED`. A builder task at `READY` without a completed arbitration is
+deliberately left there until the review is done.
+
+```
+builder READY ──► ensureEvaluators spawns 4 tasks
+ │
+ ▼
+ 4 Evaluators → COMPLETED (auto-accepted)
+ │
+ ▼
+ ensureArbitration spawns 1 planner task
+ │
+ ▼
+ Arbitration → COMPLETED (auto-accepted)
+ │
+ ┌───────────┴───────────┐
+ │ APPROVED │ REJECTED
+ ▼ ▼
+ builder READY builder stays READY (superseded)
+ → COMPLETED new fix-attempt task spawned
+ (story → REVIEW_READY (story → NEEDS_FIX → IN_PROGRESS)
+ if root node)
+```
+
+---
+
+## 4. Recursive Arbitrated-Review Cycle
+
+The review mechanism is **depth-agnostic**: it applies identically at the root
+and at any nested depth. `processStory` walks the entire task tree every tick
+and applies `processBuilderNode` to every `READY` builder-role node found.
+
+```mermaid
+flowchart TD
+ subgraph Story["Story (IN_PROGRESS)"]
+ ROOT["root_task_id\n(Builder, READY)"]
+ end
+
+ subgraph ReviewCycle["Review Cycle (per builder node, any depth)"]
+ EVAL["4 Evaluator tasks\nevaluator_quality\nevaluator_security\nevaluator_correctness\nevaluator_performance\n\n(depends_on: builder node)"]
+ ARB["1 Arbitration task\nrole: planner\n\n(depends_on: all 4 evaluators)"]
+ APPROVE{"Approved?"}
+ FIX["Fix-attempt task\n(same role, new builder;\ndepends_on: rejected node)"]
+ end
+
+ subgraph Terminal["Terminal states"]
+ COMP["builder node → COMPLETED\n(if root: story → REVIEW_READY)"]
+ HUMAN["POST /api/stories/{id}/accept\n(REVIEW_READY → DONE)"]
+ RETRO["processRetro: retro-role task\nreflects, proposes role_configs drafts"]
+ DONE["story → DONE"]
+ end
+
+ ROOT -->|"StoryOrchestrator\nensureEvaluators"| EVAL
+ EVAL -->|"all COMPLETED\nensureArbitration"| ARB
+ ARB -->|"COMPLETED\nfinalizeArbitration reads\nreport_verdict event"| APPROVE
+ APPROVE -->|"Yes"| COMP
+ APPROVE -->|"No"| FIX
+ FIX -->|"next tick: new node at READY\nrecursive review restarts here"| EVAL
+ COMP --> HUMAN
+ HUMAN --> DONE
+ DONE -->|"processRetro"| RETRO
+
+ NOTE["Nested builder nodes (parent_task_id != ''):\nsame cycle, spawnNestedFixAttempt\ninstead of ensureFixAttempt;\nno story-status change at nested level"]
+ style NOTE fill:#f5f5f5,stroke:#999,color:#333
+```
+
+---
+
+## 5. executor.Pool Internal Flow
+
+Once work reaches the pool, the internal path is:
+
+```mermaid
+flowchart LR
+ SUBMIT["Pool.Submit(task)\nor Pool.SubmitResume(task, exec)"]
+ QUEUE["workCh buffered channel\n(FIFO, no priority ordering)"]
+ DISPATCH["dispatch() goroutine\nwaits for semaphore slot\nchecks budget gate\nchecks depends_on satisfied"]
+ RUNNER["ContainerRunner\n(Docker; git clone; claude -p)\nor LocalRunner (OpenAI-compatible HTTP)"]
+ RESULT["handleRunResult\nwrite execution row\nupdate task state\nbroadcast WebSocket event\ncascade-fail dependents if terminal-fail"]
+
+ SUBMIT --> QUEUE --> DISPATCH --> RUNNER --> RESULT
+ RESULT -->|"READY or COMPLETED"| DONE2(["task observable\nvia WS / REST"])
+ RESULT -->|"BLOCKED"| BLOCKED2(["workspace preserved\nin sandbox_dir;\nawait answer"])
+ RESULT -->|"FAILED etc."| FAILED2(["Scheduler picks up\non next poll tick"])
+```
+
+---
+
+## Notes
+
+- **`propose_epic` / `propose_role_config`** are agent tools that write to
+ storage only — they do not create tasks or trigger any further scheduling.
+ They exist to let a running agent record planning-layer artifacts (epic
+ groupings; improved role system prompts for human review) as a side-effect of
+ its execution.
+
+- **`story.RootTaskID` is immutable** after story creation. When a builder node
+ is rejected and replaced, `task.CurrentAttempt(store, anchorID)` walks the
+ `depends_on` chain forward to find the current live node. Every orchestrator
+ caller resolves through this before acting.
+
+- **Scheduler vs. StoryOrchestrator**: the `Scheduler` handles generic
+ role-typed task lifecycle (FAILED → retry/escalate; BLOCKED → timeout
+ resume). The `StoryOrchestrator` handles story-specific pipeline orchestration
+ (Builder → Evaluators → Arbitration). They run in the same process but poll
+ independently and write to separate concerns.