┌──────────────────────────────────────────────────┐
│ │
▼ │
┌────────┐ ┌─────────────┐ ┌─────────────┐ │
│ todo │────►│ in_progress │────►│ in_review │ │
└────┬────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ ┌───────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────┐ │
│ │ │ in_approval │─────────────┘
│ │ └──────┬──────┘ (reject)
│ │ │
│ │ ▼
│ │ ┌─────────────┐
│ │ │ merging │─────────────┐
│ │ └──────┬──────┘ (merge fail)│
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────┐ │
│ │ │ done │ │
│ │ └─────────────┘ │
│ │ │
▼ ▼ │
┌─────────────────────────┐ │
│ cancelled │ │
└─────────────────────────┘ │
| From | Allowed transitions |
|---|---|
todo |
in_progress, cancelled |
in_progress |
in_review, todo, cancelled |
in_review |
in_approval, in_progress, cancelled |
in_approval |
merging, in_progress, cancelled |
merging |
done, in_progress |
done |
(terminal — no transitions) |
cancelled |
(terminal — no transitions) |
Any transition not in this table returns HTTP 409 Conflict.
1. Manager creates task → status: todo
2. Manager assigns to engineer → assignee set
3. Engineer starts work → status: in_progress
4. Engineer creates worktree → git branch isolated
5. Engineer finishes, submits → status: in_review
6. Review requested → review.created event
7. Reviewer approves code → status: in_approval
8. Manager approves for merge → status: merging
9. Merge worker merges the branch → status: done
Tasks can move backward:
- Review rejection:
in_review→in_progress(reviewer found issues) - Approval rejection:
in_approval→in_progress(manager wants changes) - Merge failure:
merging→in_progress(conflicts or CI failure)
When a reviewer gives a request_changes verdict, the system automatically:
- Formats all review comments into structured feedback (file paths, line numbers, content)
- Transitions the task back to
in_progress - Sends the formatted feedback as a message to the assignee agent
- PG NOTIFY fires → Dispatcher re-runs the agent
The agent's prompt instructs it to check its inbox for review feedback first using the get_review_feedback tool. This creates a fully automated review→fix→resubmit cycle without manual intervention.
Reviewer gives request_changes
→ ReviewService formats comments
→ Task: in_review → in_progress
→ Message sent to assignee agent
→ PG NOTIFY → Dispatcher → Agent re-runs
→ Agent reads feedback, fixes code
→ Agent requests review again
→ Cycle repeats until approved
Tasks can declare dependencies via the depends_on integer array.
Task A: id=1, status=todo, depends_on=[]
Task B: id=2, status=todo, depends_on=[1]
Task C: id=3, status=todo, depends_on=[1, 2]
Rules:
- A task with
depends_oncannot move toin_progressuntil ALL dependencies have statusdone - Missing dependency IDs (referencing non-existent tasks) also block the transition
- Dependencies are only checked when transitioning TO
in_progress - No circular dependency detection yet (enforced by convention)
Example:
POST /api/v1/tasks/2/status {"status": "in_progress"}
→ 409 Conflict: "Blocked by unresolved dependencies: task 1 (todo)"
# Complete task 1 first...
POST /api/v1/tasks/1/status {"status": "in_progress"}
POST /api/v1/tasks/1/status {"status": "in_review"}
POST /api/v1/tasks/1/status {"status": "in_approval"}
POST /api/v1/tasks/1/status {"status": "merging"}
POST /api/v1/tasks/1/status {"status": "done"}
# Now task 2 can start
POST /api/v1/tasks/2/status {"status": "in_progress"}
→ 200 OK
At any point during a task, an agent can pause to request human input:
Agent working on task → ask_human("Should I refactor the auth module?")
→ HumanRequest created (status: pending)
→ Agent waits for response
→ Human responds via dashboard or API
→ PG NOTIFY fires → Dispatcher resumes agent
Request types:
- question — Free-text answer
- approval — Yes/no decision
- review — Code/work review
When an engineer moves a task to in_review, the system automatically:
- Pushes the branch to the remote (via
GitService.push_branch) - Creates a GitHub PR (via
PRServiceusingghCLI) - Stores the PR URL and number in
task_metadata
This is best-effort — if push or PR creation fails, the review flow continues uninterrupted.
If the team has a reviewer agent (role=reviewer, status=idle), reviews are automatically assigned to it for a first-pass AI code review:
1. Engineer completes work → request_review(task_id)
2. Branch pushed + PR created → auto-push + gh pr create
3. Reviewer agent auto-assigned → _find_reviewer_agent(team_id)
4. Reviewer agent dispatched → message sent → PG NOTIFY → agent runs
5. Reviewer reads diff, leaves comments → add_review_comment (file + line)
6. Reviewer renders verdict:
6a. approve → task stays in_review for human final review
6b. request_changes → feedback loop (see below)
7. Human reviews the pre-vetted code → final approve → in_approval → merge
Agent approval is a first-pass check — it does not auto-merge. The task stays in in_review so a human can do the final review.
When any reviewer (agent or human) gives request_changes:
Reviewer gives request_changes
→ ReviewService formats comments (file paths, line numbers)
→ Task: in_review → in_progress
→ Formatted feedback sent as message to assignee agent
→ PG NOTIFY → Dispatcher → Agent re-runs
→ Agent reads feedback via get_review_feedback
→ Agent fixes code and re-submits for review
→ Cycle repeats until approved
Multiple review cycles are tracked via the attempt field. Each cycle is a fresh review. The feedback delivery is automatic — no manual intervention needed.
Agents can persist discoveries between runs using save_context / get_context:
Run 1: Agent investigates bug
→ save_context(key="root_cause", value="Regex in password.py")
→ save_context(key="key_files", value="auth/password.py:42")
Run 2: Agent re-dispatched after review feedback
→ Context automatically injected into prompt under PREVIOUS CONTEXT
→ Agent already knows root cause + key files
→ Jumps straight to fixing review comments
Context is stored in task_metadata.context JSONB and injected into the agent prompt at the start of each run.
Every task state change is recorded as an immutable event in the events table.
stream_id type data
────────── ──────────────────── ──────────────────────────────────
task:1 task.created {"title": "Fix login", "priority": "high"}
task:1 task.assigned {"from": null, "to": "<agent-uuid>"}
task:1 task.status_changed {"from": "todo", "to": "in_progress"}
task:1 task.updated {"priority": "critical"}
task:1 task.status_changed {"from": "in_progress", "to": "in_review"}
task:1 review.created {"review_id": 1, "attempt": 1}
task:1 review.verdict {"verdict": "approve", "reviewer_id": "..."}
task:1 task.status_changed {"from": "in_review", "to": "in_approval"}
task:1 task.status_changed {"from": "in_approval", "to": "merging"}
task:1 merge.completed {"merge_commit": "abc123"}
task:1 task.status_changed {"from": "merging", "to": "done"}
Query the full history: GET /api/v1/tasks/1/events
| Type | When | Data |
|---|---|---|
task.created |
Task created | title, priority, team_id, assignee_id, depends_on |
task.updated |
Fields changed (not status) | Changed fields only |
task.status_changed |
Status transition | from, to, actor_id |
task.assigned |
Assignee changed | from, to |
task.comment_added |
Comment added | content, author |
message.sent |
Message sent | sender_id, recipient_id, task_id |
| Type | When | Data |
|---|---|---|
session.started |
Agent session begins | agent_id, task_id, model |
session.ended |
Agent session ends | cost_usd, tokens, error |
session.usage_recorded |
Token usage recorded | tokens_in, tokens_out |
agent.budget_exceeded |
Budget limit hit | agent_id, limit |
| Type | When | Data |
|---|---|---|
human_request.created |
Agent requests human input | kind, question |
human_request.resolved |
Human responds | response, responded_by |
human_request.expired |
Request timed out |
| Type | When | Data |
|---|---|---|
review.created |
Review requested | task_id, attempt |
review.verdict |
Verdict rendered | verdict, reviewer_id |
review.comment_added |
Comment on review | file_path, line_number |
review.feedback_sent |
Review feedback delivered to agent | review_id, assignee_id, comment_count |
merge.queued |
Merge job created | task_id, strategy |
merge.started |
Merge worker picks up job | |
merge.completed |
Merge succeeded | merge_commit |
merge.failed |
Merge failed | error |
| Type | When | Data |
|---|---|---|
webhook.created |
Webhook configured | org_id, provider |
webhook.delivery_received |
Incoming payload | event_type |
webhook.delivery_processed |
Successfully processed | actions |
settings.updated |
Team settings changed | changes |
| Priority | Use case |
|---|---|
low |
Nice to have, no deadline |
medium |
Default. Standard work items |
high |
Important, should be done soon |
critical |
Blocking other work, do immediately |
The POST /api/v1/teams/{team_id}/tasks/batch endpoint (and the create_tasks_batch MCP tool) creates multiple tasks in a single request with support for intra-batch dependencies.
{
"tasks": [
{"title": "Set up database models", "assignee_id": "agent-uuid-1"},
{"title": "Build API endpoints", "assignee_id": "agent-uuid-2", "depends_on_indices": [0]},
{"title": "Write integration tests", "depends_on_indices": [0, 1]}
]
}- Each task in the array can reference other tasks in the same batch by their 0-based array index
- After all tasks are created and assigned real IDs, the indices are resolved to actual
depends_ontask IDs - Task at index 1 with
depends_on_indices: [0]means it depends on the task at index 0 in the batch - Standard DAG dependency enforcement applies — the dependent task cannot move to
in_progressuntil its dependencies reachdone
This is the primary mechanism for a manager agent to plan and dispatch a multi-step project in one call.
The wait_for_task_completion MCP tool enables blocking coordination between agents. A manager agent can assign work to engineer agents and then block until the work is done before proceeding with the next step.
Manager creates batch of tasks with dependencies
→ Assigns task A to engineer-1
→ Assigns task B to engineer-2
→ Calls wait_for_task_completion(task_id=A)
→ Blocked until engineer-1 finishes (task A reaches "done" or "cancelled")
→ Calls wait_for_task_completion(task_id=B)
→ Blocked until engineer-2 finishes
→ Manager continues with follow-up work
| Parameter | Default | Description |
|---|---|---|
task_id |
(required) | The task to wait on |
timeout_seconds |
3600 | Max wait time before the call errors |
terminal_statuses |
["done", "cancelled"] |
Which statuses count as "complete" |
The tool polls the task status internally and returns the full task object once a terminal status is reached. If the timeout expires, it returns an error so the manager can decide how to proceed (retry, escalate, cancel).
Use list_team_agents to discover available agents and their current status (idle, working, paused) before assigning work. This lets a manager agent make informed assignment decisions based on agent availability.