Back to Beads

Molecules: Work Graphs in Beads

docs/MOLECULES.md

1.0.38.9 KB
Original Source

Molecules: Work Graphs in Beads

This doc explains how beads structures and executes work. Start here if you're building workflows.

TL;DR

  1. Work = issues with dependencies. That's it. No special types needed.
  2. Dependencies control execution. blocks = sequential. No dep = parallel.
  3. Molecules are just epics. Any epic with children is a molecule. Templates are optional.
  4. Bonding = adding dependencies. bd mol bond A B creates a dependency between work graphs.
  5. Agents execute until blocked. When all ready work is done, the workflow is complete.

The Execution Model

How Work Flows

An agent picks up a molecule (epic with children). They execute ready children in parallel until everything closes:

epic-root (assigned to agent)
├── child.1 (no deps → ready)      ← execute in parallel
├── child.2 (no deps → ready)      ← execute in parallel
├── child.3 (needs child.1) → blocked until child.1 closes
└── child.4 (needs child.2, child.3) → blocked until both close

Ready work: bd ready shows issues with no open blockers. Blocked work: bd blocked shows what's waiting.

Dependency Types That Block

TypeSemanticsUse Case
blocksB can't start until A closesSequencing work
parent-childIf parent blocked, children blockedHierarchy (children parallel by default)
conditional-blocksB runs only if A failsError handling paths
waits-forB waits for all of A's childrenFanout gates

Non-blocking types: related, discovered-from, replies-to - these link issues without affecting execution.

Default Parallelism

Children are parallel by default. Only explicit dependencies create sequence:

bash
# These three tasks run in PARALLEL (no deps between them)
bd create "Task A" -t task
bd create "Task B" -t task
bd create "Task C" -t task

# Add dependency to make B wait for A
bd dep add <B-id> <A-id>   # B depends on A (B needs A)

Multi-Day Execution

An agent works through a molecule by:

  1. Getting ready work (bd ready)
  2. Claiming it (bd update <id> --claim)
  3. Doing the work
  4. Closing it (bd close <id>)
  5. Repeat until molecule is done

If the molecule is blocked by another molecule:

  • Agent either waits, or
  • Agent continues into the blocking molecule (compound execution)

Bonding enables compound execution: When you bond molecule A to molecule B, the agent can traverse both as one logical unit of work.

Molecules vs Epics

They're the same thing. A molecule is just an epic (parent + children) with workflow semantics.

TermMeaningWhen to Use
EpicParent issue with childrenGeneral term for hierarchical work
MoleculeEpic with execution intentWhen discussing workflow traversal
ProtoEpic with template labelReusable pattern (optional)

You can create molecules without protos - just create an epic and add children:

bash
bd create "Feature X" -t epic
bd create "Design" -t task --parent <epic-id>
bd create "Implement" -t task --parent <epic-id>
bd create "Test" -t task --parent <epic-id>
bd dep add <implement-id> <design-id>   # implement needs design
bd dep add <test-id> <implement-id>      # test needs implement

Bonding: Connecting Work Graphs

Bond = create a dependency between two work graphs.

bash
bd mol bond A B                    # B depends on A (sequential by default)
bd mol bond A B --type parallel    # Organizational link, no blocking
bd mol bond A B --type conditional # B runs only if A fails

What Bonding Does

OperandsWhat Happens
epic + epicCreates dependency edge between them
proto + epicSpawns proto as new issues, attaches to epic
proto + protoCreates compound template

The Key Insight

Bonding lets agents traverse compound workflows. When A blocks B:

  • Completing A unblocks B
  • Agent can continue from A into B seamlessly
  • The compound work graph can span days

This is how orchestrators run autonomous workflows - agents follow the dependency graph, handing off between sessions, until all work closes.

Ordinary epics stay open when the last child closes. They become close-eligible work that can be closed explicitly once the parent outcome is actually done.

Phase Metaphor (Templates)

For reusable workflows, beads uses a chemistry metaphor:

PhaseNameStorageSyncedPurpose
SolidProto.beads/YesFrozen template
LiquidMol.beads/YesActive persistent work
VaporWisp.beads/ (Wisp=true)NoEphemeral operations

Phase Commands

bash
bd mol pour <proto>              # Proto → Mol (persistent instance)
bd mol wisp <proto>              # Proto → Wisp (ephemeral instance)
bd mol squash <id>               # Mol/Wisp → Digest (permanent record)
bd mol burn <id>                 # Wisp → nothing (discard)

When to Use Each Phase

Use CasePhaseWhy
Feature workMol (pour)Persists across sessions, audit trail
Patrol cyclesWispRoutine, no audit value
One-shot opsWispScaffolding, not the work itself
Important discovery during wispMol (--pour)"This matters, save it"

Common Patterns

Sequential Pipeline

bash
bd create "Pipeline" -t epic
bd create "Step 1" -t task --parent <pipeline>
bd create "Step 2" -t task --parent <pipeline>
bd create "Step 3" -t task --parent <pipeline>
bd dep add <step2> <step1>
bd dep add <step3> <step2>

Parallel Fanout with Gate

bash
bd create "Process files" -t epic
bd create "File A" -t task --parent <epic>
bd create "File B" -t task --parent <epic>
bd create "File C" -t task --parent <epic>
bd create "Aggregate" -t task --parent <epic>
# Aggregate needs all three (waits-for gate)
bd dep add <aggregate> <fileA> --type waits-for

Dynamic Bonding (Christmas Ornament)

When the number of children isn't known until runtime:

bash
# In a survey step, discover workers and bond arms dynamically
for worker in $(bd agent list); do
  bd mol bond mol-worker-arm $PATROL_ID --ref arm-$worker --var name=$worker
done

Creates:

patrol-x7k (wisp)
├── preflight
├── survey-workers
│   ├── patrol-x7k.arm-ace (dynamically bonded)
│   ├── patrol-x7k.arm-nux (dynamically bonded)
│   └── patrol-x7k.arm-toast (dynamically bonded)
└── aggregate (waits for all arms)

Agent Pitfalls

1. Temporal Language Inverts Dependencies

Wrong: "Phase 1 comes before Phase 2" → bd dep add phase1 phase2 Right: "Phase 2 needs Phase 1" → bd dep add phase2 phase1

Use requirement language. Verify with bd blocked.

2. Assuming Order = Sequence

Numbered steps don't create sequence. Dependencies do:

bash
# These run in PARALLEL despite names
bd create "Step 1" ...
bd create "Step 2" ...
bd create "Step 3" ...

# Add deps to sequence them
bd dep add step2 step1
bd dep add step3 step2

3. Forgetting to Close Work

Blocked issues stay blocked forever if their blockers aren't closed. Always close completed work:

bash
bd close <id> --reason "Done"

4. Orphaned Wisps

Wisps accumulate if not squashed/burned:

bash
bd mol wisp list        # Check for orphans
bd mol squash <id>      # Create digest
bd mol burn <id>        # Or discard
bd mol wisp gc          # Garbage collect old wisps
bd mol wisp gc --closed --force  # Purge all closed wisps

Layer Cake Architecture

For reference, here's how the layers stack:

Formulas (JSON compile-time macros)      ← optional, for complex composition
    ↓
Protos (template issues)                  ← optional, for reusable patterns
    ↓
Molecules (bond, squash, burn)            ← workflow operations
    ↓
Epics (parent-child, dependencies)        ← DATA PLANE (the core)
    ↓
Issues (Dolt, version-controlled)          ← STORAGE

Most users only need the bottom two layers. Protos and formulas are for reusable patterns and complex composition.

Commands Quick Reference

Execution

bash
bd ready                         # What's ready to work
bd blocked                       # What's blocked
bd update <id> --claim
bd close <id>

Dependencies

bash
bd dep add <issue> <depends-on>  # issue needs depends-on
bd dep tree <id>                 # Show dependency tree

Molecules

bash
bd mol pour <proto> --var k=v    # Template → persistent mol
bd mol wisp <proto>              # Template → ephemeral wisp
bd mol bond A B                  # Connect work graphs
bd mol squash <id>               # Compress to digest
bd mol burn <id>                 # Discard without record