Task Graph (beads)

beads is godotz.ai’s persistent DAG task scheduler. It tracks work from high-level epics down to atomic steps, stores every state transition in a Dolt-versioned backend, and emits lifecycle hooks that agents can intercept. The CLI is bd.


Why a Task Graph

Plain in-context TODO lists vanish when a session ends. beads persists state across sessions, machines, and agent restarts. If a worker crashes mid-task, bd retry resumes from the last completed step — no work is duplicated.

The DAG model also prevents two agents from starting the same task concurrently. Dependency edges are the contract; beads enforces them.


Hierarchy

epic
└── task
    ├── step 1
    ├── step 2
    └── step 3
LevelDescriptionTypical owner
epicA shippable feature or milestoneHuman operator
taskA discrete unit of work within an epicOrchestrator agent
stepAn atomic action within a taskWorker agent

An epic completes only when all its tasks complete. A task completes only when all its steps complete or are explicitly skipped.


bd CLI Reference

# Create
bd epic create "ship auth v2"
bd task create --epic auth-v2 "implement JWT refresh"
bd step create --task jwt-refresh "write token rotation logic"

# Run
bd run --task jwt-refresh              # run all steps in dependency order
bd run --step token-rotation-logic     # run a single step

# Status
bd status                              # tree view of all active work
bd status --epic auth-v2              # subtree for one epic
bd log --task jwt-refresh             # step-by-step execution log

# Recovery
bd retry --task jwt-refresh           # retry failed steps only
bd skip --step token-rotation-logic   # mark step skipped, unblock dependents
bd cancel --epic auth-v2             # cancel all in-progress work for epic

# History
bd diff HEAD~1                         # diff last two graph states (Dolt)
bd log --graph                         # full commit history of graph state

DAG Execution

Steps within a task form a directed acyclic graph. beads resolves execution order by topological sort and runs independent steps in parallel up to the configured maxConcurrency.

step-a ──┐
          ├── step-c ──── step-e (terminal)
step-b ──┘
          └── step-d (terminal)

Given this graph:

  1. step-a and step-b start immediately (no deps)
  2. step-c starts when both step-a and step-b complete
  3. step-d starts when step-b completes
  4. step-e starts when step-c completes

Steps step-d and step-e may run concurrently.

# bd.config.yaml
execution:
  maxConcurrency: 4        # max parallel steps per task
  stepTimeoutSeconds: 300  # step killed after 5 min if no heartbeat
  retryPolicy:
    maxAttempts: 3
    backoffSeconds: [10, 30, 90]

Dolt Embedded Backend

beads stores its graph in a Dolt database — a MySQL-compatible relational database with git semantics. Every bd write operation creates a Dolt commit.

# bd under the hood
bd task create "implement JWT refresh"
# → INSERT INTO tasks (id, title, status, epic_id, ...) VALUES (...)
# → CALL dolt_commit('-Am', 'create task: implement JWT refresh')

This gives the task graph full version history:

bd diff HEAD~1          # what changed in the last operation
bd diff main..branch    # compare branches (useful for parallel epic work)
bd checkout <commit>    # restore graph to any historical state

Dolt runs embedded within the beads process — no separate server required for single-node deployments. Multi-node deployments use Dolt SQL Server mode with replication.


Prefix Conventions

Task and step IDs use a prefix convention to encode context at a glance:

PrefixMeaningExample
feat/Feature workfeat/auth-refresh
fix/Bug fixfix/token-expiry-edge-case
chore/Non-functional changechore/update-deps
doc/Documentationdoc/api-reference
test/Test-only changetest/jwt-integration
infra/Infrastructureinfra/redis-cluster
exp/Experiment, may not landexp/rl-optimizer

Agents use these prefixes to filter relevant tasks when scanning status:

bd status --filter feat/     # show only feature tasks
bd status --filter exp/      # show only experiments

Hooks

beads fires lifecycle hooks at state transitions. Hooks are shell commands or HTTP endpoints configured per-event:

# bd.config.yaml
hooks:
  on_task_start:
    - cmd: "ntfy publish omp-tasks 'Task started: {{task.title}}'"
  on_step_complete:
    - cmd: "bd log --step {{step.id}} >> .omc/logs/steps.log"
  on_task_complete:
    - http: "http://langfuse:3000/api/events"
      body: '{"event": "task_complete", "taskId": "{{task.id}}"}'
  on_step_fail:
    - cmd: "ntfy publish omp-alerts 'FAIL: {{step.id}} — {{step.error}}'"
    - cmd: "bd retry --step {{step.id}} --delay 30"
  on_epic_complete:
    - cmd: "gh pr create --title '{{epic.title}}' --body '{{epic.summary}}'"

Hook variables use {{field}} templating. Available variables per event:

VariableAvailable on
step.id, step.title, step.errorstep events
task.id, task.title, task.statustask events
epic.id, epic.title, epic.summaryepic events

Hooks run synchronously before the state transition is committed. A hook that exits non-zero blocks the transition and fires on_hook_fail (no infinite recursion — hook failures do not re-fire the failed hook).


Human Checkpoints

A step can be marked type: checkpoint to pause execution and wait for human approval before continuing:

# task definition
steps:
  - id: implement
    type: exec
    cmd: "claude-code run implement.md"

  - id: human-review
    type: checkpoint
    message: "Review the implementation diff before deploy"
    deps: [implement]

  - id: deploy
    type: exec
    cmd: "bd run deploy.sh"
    deps: [human-review]
bd status           # shows "WAITING: human-review"
bd approve --step human-review
# → deploy step now starts

Checkpoints are stored in the Dolt graph and survive restarts — the wait is durable, not in-process.


Integration with the Agent Runtime

The OMC harness integrates with beads through the bd CLI available in every agent session. Orchestrators decompose epics and create tasks; workers claim steps and run them.

Orchestrator:
  bd task create --epic $EPIC "implement feature"
  bd step create --task $TASK "write code"
  bd step create --task $TASK "write tests" --deps write-code
  bd run --task $TASK

Worker (claimed a step):
  # step is "in_progress" in the graph
  # ... do the work ...
  bd step complete --step $STEP_ID

bd run is idempotent: running it on an already-completed task is a no-op. Running it on a partially-complete task resumes from where it stopped.