Skip to content

Bursts & Waves

TermDefinition
QuantumA unit of work tracked by Quanta.Keeper (SQLite). Has an ID, status, type, priority, labels, dependencies.
PipelineAn ordered sequence of psychonaut stages that process a quantum. Stages can be sequential or fan-out.
QuantumRunOne quantum + its pipeline execution. Tracks current stage, agent statuses, results, and assumptions.
BurstA set of QuantumRuns kicked off from ready quanta. All execute concurrently.
WaveRepeated bursts until no ready quanta remain. Closing quanta 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 quanta)

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

:collecting — Queries Keeper.ready/1 for unblocked quanta. For each quantum:

  1. Fetches full quantum 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 QuantumRun struct
  5. Marks quantum as :in_progress

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

:running — Spawns agents for the first stage of each QuantumRun. 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 QuantumRuns have reached terminal status. For each:

  • :done quanta: calls Keeper.close_quantum/3
  • :error quanta: 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 the ready-quanta query returns empty:

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

A QuantumRun is a passive data structure tracking a quantum through its pipeline:

%Annihilation.Burst.QuantumRun{
quantum_id: 42,
quantum_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 QuantumRun 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(quantum_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(quantum_run, stage_config)
# Returns: %{agent_id: "42_s0_coder", ...}
StageExecutor.prepare_sequential_next(quantum_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 quantum 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_quantum/3
  4. Stage fails -> quantum stays open, failure logged as comment