Task state machine
Module: packages/core/src/task/state.ts Repository: packages/core/src/task/repo.ts (the only thing that writes to tasks) Schema enum: task_state in packages/db/migrations/0005_tasks.sql
States
| state | terminal | meaning |
|---|---|---|
blocked | no | At least one hard dependency is not yet done/cancelled. |
ready | no | All hard dependencies satisfied; available to claim. |
claimed | no | An agent acquired the lease but hasn't started signalling work. |
in_progress | no | Agent heartbeats while doing the work. |
needs_review | no | Agent called completeTask; verifier pending/failed or kind=manual. |
done | yes | Verified-passed (or skipped). No further transitions. |
cancelled | yes | Aborted from any non-terminal state. No further transitions. |
Allowed transitions
deps met
┌────────┐──────────▶┌───────┐──atomic──▶┌─────────┐──heartbeat──▶┌─────────────┐
│ blocked│ │ ready │ │ claimed │ │ in_progress │
└───┬────┘◀─new dep──└──┬────┘◀lease─exp─└────┬────┘◀lease─exp────└──────┬──────┘
│ │ │ │ complete
│ │ │ ▼
│ │ │ ┌──────────────┐
│ │ │ reopen ◀─────│ needs_review │
│ │◀──────────────────────────────── └──────┬───────┘
│ │ │ approve /
│ │ │ pass/skip
│ │ ▼
│ │ ┌──────────────┐
│ │ │ done │ (terminal)
│ │ └──────────────┘
▼ ▼
┌─────────┐ ◀───── (cancel from any non-terminal)
│cancelled│ (terminal)
└─────────┘Full transition table — from → to is legal iff to ∈ TASK_TRANSITIONS[from]:
from \ to | blocked | ready | claimed | in_progress | needs_review | done | cancelled |
|---|---|---|---|---|---|---|---|
| blocked | — | ✓ | — | — | — | — | ✓ |
| ready | ✓ | — | ✓ | — | — | — | ✓ |
| claimed | — | ✓ | — | ✓ | — | — | ✓ |
| in_progress | — | ✓ | — | — | ✓ | ✓ | ✓ |
| needs_review | — | ✓ | — | — | — | ✓ | ✓ |
| done | — | — | — | — | — | — | — |
| cancelled | — | — | — | — | — | — | — |
Illegal transitions throw InvalidTransitionError (code task.invariant_violated).
Invariants (DB-level CHECKs, schema 0005)
| constraint | rule |
|---|---|
tasks_claim_invariant | state ∈ {claimed, in_progress} ⇒ claimed_by_agent_id IS NOT NULL AND lease_expires_at IS NOT NULL |
tasks_done_invariant | state = 'done' ⇒ completed_at IS NOT NULL AND verification_status IN ('passed','skipped') |
The repo layer is built to honour both — every UPDATE that flips state also touches the dependent columns in the same statement.
completeTask outcomes by verification kind
verification_contract.kind | next state | verification_status | side effect |
|---|---|---|---|
none | done | skipped | none |
manual | needs_review | pending | waits for resolveReview('approve') |
github_pr / gitlab_mr | needs_review | pending | BullMQ verification job enqueued (id verify-<id>) |
commands | needs_review | pending | same |
markNeedsReview is idempotent when the task is already in needs_review — it just flips the verification status (e.g. pending → failed after the verifier reports back).
Side-effect ordering
Inside db.transaction():
- The state UPDATE.
agent_auditrow for the action (task.created,task.claim,task.completed, …).- Any dependent rows (
task_dependencies,context.output).
After commit (out-of-band):
redis.publish('pubsub:org:{orgId}:tasks', JSON.stringify(TaskEvent))— task 33 SSE bridge consumes this.- BullMQ
verificationenqueue (for automated kinds only).
A failed redis.publish or queue add does not roll back the state change — it only logs. The verification worker (task 32) sweeps verification_status='pending' tasks on startup to recover from missed enqueues.
What's NOT in this module
- Lease / heartbeat hot path lives next door in
lease.ts+reaper.ts(task 17).
The repo writes the durable columns; lease.ts mirrors them into Redis (claim:{org}:{task}) for fast verifyClaim; the reaper sweeps expired rows back to ready every 30 s.
- Cycle detection in
task_dependencies(task 18 — a generic DAG validator). - Knowledge edge linking when an agent completes a task with
learningsRef(task 28). - Verification orchestration — the engine consumes the BullMQ jobs we enqueue (task 32).
- `isBlockedByReview` is a stub returning
falseuntil task 29a (typed comments + amendments).