Skip to content

Bursts & Waves

TermDefinition
BeadA unit of work tracked by Beads.Keeper (SQLite). Has an ID, status, type, priority, labels, dependencies.
PipelineAn ordered sequence of psychonaut stages that process a bead. Stages can be sequential or fan-out.
BeadRunOne bead + its pipeline execution. Tracks current stage, agent statuses, results, and assumptions.
BurstA set of BeadRuns kicked off from ready beads. All execute concurrently.
WaveRepeated bursts until no ready beads remain. Closing beads can unblock others.

The Burst.Manager is a deterministic GenServer (not an LLM psychonaut) with a 5-phase state machine:

:idle --> :collecting --> :running --> :draining --> :collecting (next burst)
\-> :done --> :idle (no more beads)

:idle — Waiting for start_wave/1. Opens a session for JSONL logging.

:collecting — Queries Keeper.ready/1 for unblocked beads. For each bead:

  1. Fetches full bead data via Keeper.show/2
  2. Matches against pipeline recipes via Keeper.match_pipeline/2
  3. Falls back to default pipeline if no match
  4. Creates a BeadRun struct
  5. Marks bead as :in_progress

Safety: max 100 bursts per wave to prevent infinite loops.

:running — Spawns agents for the first stage of each BeadRun. Monitors all spawned processes. Handles:

  • :agent_done messages with results
  • :DOWN messages for crashes
  • Stage advancement logic (sequential chaining or fan-out completion)

:draining — All BeadRuns have reached terminal status. For each:

  • :done beads: calls Keeper.close_bead/3
  • :error beads: adds failure comment via Keeper.add_comment/4, resets status to :open

Computes wave stats, broadcasts {:burst_complete, info}, transitions to :collecting for the next burst.

:doneKeeper.ready/1 returned empty. Logs wave summary, broadcasts {:wave_complete, stats}, closes session, returns to :idle.

Waves keep running until br ready returns empty:

Wave 1:
Burst 1: [bead-42 (frontend), bead-43 (backend)] --> both complete
Burst 2: [bead-44 (was blocked by 42)] --> completes
Burst 3: [br ready returns empty] --> wave done

A BeadRun is a passive data structure tracking a bead through its pipeline:

%Annihilation.Burst.BeadRun{
bead_id: 42,
bead_data: %{title: "...", labels: ["frontend"]},
pipeline: %Pipeline{name: "default", stages: [...]},
current_stage: 0,
stage_agents: %{"42_s0_coder" => :running}, # agent_id => status
stage_results: %{"42_s0_coder" => "..."}, # agent_id => result text
completed_stages: [%{stage_index: 0, results: %{...}}],
status: :running, # :pending | :running | :done | :error
assumptions: [], # Recorded drifts during this run
started_at: ~U[...],
completed_at: nil,
error_reason: nil
}

When an agent completes, StageExecutor.stage_status/1 determines what happens:

  • :in_progress — agents still running, wait
  • :all_succeeded — advance to next stage or mark done
  • :has_failures — mark BeadRun as error
  • :sequential_blocked — a sequential agent failed, chain broken

For fan-out stages, all agents must complete before the stage is evaluated. For sequential stages, agents run one at a time — each receives the prior agent’s output threaded into its context.

StageExecutor prepares spawn configs without actually spawning processes:

StageExecutor.prepare_fan_out(bead_run, stage_config)
# Returns: [%{agent_id: "42_s0_security", agent_template: ..., context: "...", ...}, ...]

All agents receive identical accumulated context from previous stages and run concurrently.

StageExecutor.prepare_sequential_first(bead_run, stage_config)
# Returns: %{agent_id: "42_s0_coder", ...}
StageExecutor.prepare_sequential_next(bead_run, stage_config, prior_result)
# Returns: %{agent_id: "42_s0_reviewer", context: "...prior output...", ...}

Each sequential agent receives the prior agent’s result appended to the base context.

Agents receive accumulated output from all previous stages:

## Stage 0 Results
### Agent: 42_s0_orchestrator
[orchestrator's plan]
## Stage 1 Results
### Agent: 42_s1_coder
[coder's implementation]

Results are truncated at 10,000 characters per agent to prevent context overflow.

Only Burst.Manager modifies bead status. Individual psychonauts never call Keeper.close or Keeper.update directly:

  1. Psychonauts do their work (code, test, review)
  2. Agent completes -> result sent to Burst.Manager via :agent_done message
  3. All pipeline stages pass -> Burst.Manager calls Keeper.close_bead/3
  4. Stage fails -> bead stays open, failure logged as comment