🐝Swarm Tools
Decisions

EDR-001: Event-Sourced Beads

Hybrid CRUD + event audit trail using Effect-TS

Event-Sourced Beads with Effect-TS

Status: Research Complete
Date: December 2025
Recommendation: Build (Hybrid Approach)

Executive Summary

Question: How feasible is rebuilding steveyegge/beads using Effect-TS durable streams and event sourcing?

Answer: Highly feasible with 75% infrastructure reuse from swarm-mail.

AspectAssessment
Technical FeasibilityHigh - swarm-mail provides solid foundation
Effort Estimate2-3 weeks MVP, 4-6 weeks full parity
Risk LevelMedium - git sync proven, event sourcing adds complexity
RecommendationBUILD IT - hybrid CRUD + event audit trail

Problem Statement

We have two separate systems:

  1. steveyegge/beads (Go) - Battle-tested issue tracker with git sync
  2. swarm-mail (TypeScript/Effect) - Event sourcing primitives for agent coordination

The goal: A unified TypeScript implementation that maintains beads' proven git sync while leveraging swarm-mail's event sourcing infrastructure.

Architecture Comparison

steveyegge/beads (Current)

┌─────────────────────────────────────────┐
│           steveyegge/beads              │
├─────────────────────────────────────────┤
│  CLI (bd) - 50+ subcommands             │
├─────────────────────────────────────────┤
│  RPC Layer (daemon architecture)        │
├─────────────────────────────────────────┤
│  Storage (SQLite)                       │
│  ├── issues table (CRUD, mutable)       │
│  ├── dependencies table                 │
│  ├── events table (AUDIT ONLY)          │
│  └── blocked_issues_cache (derived)     │
├─────────────────────────────────────────┤
│  Git Sync                               │
│  ├── JSONL export (snapshots)           │
│  ├── 3-way merge driver                 │
│  └── Hash-based IDs                     │
└─────────────────────────────────────────┘

Key insight: beads is NOT event-sourced. It's hybrid CRUD + event audit trail. Events are for audit only, not replayed for state reconstruction.

Proposed Hybrid Architecture

┌─────────────────────────────────────────┐
│        Event-Sourced Beads              │
├─────────────────────────────────────────┤
│  Plugin Tools (beads_*)                 │
│  └── Existing API preserved             │
├─────────────────────────────────────────┤
│  Event Store (swarm-mail)               │
│  ├── 20 BeadEvent types                 │
│  ├── Append-only log (local audit)      │
│  └── NOT synced via git                 │
├─────────────────────────────────────────┤
│  Projections (swarm-mail pattern)       │
│  ├── beads table (current state)        │
│  ├── bead_dependencies table            │
│  ├── blocked_beads_cache (derived)      │
│  └── dirty_beads table (tracking)       │
├─────────────────────────────────────────┤
│  Git Sync (beads pattern)               │
│  ├── JSONL export FROM PROJECTIONS      │
│  ├── Reuse beads merge driver (MIT)     │
│  └── Hash-based IDs                     │
├─────────────────────────────────────────┤
│  Effect-TS Primitives                   │
│  ├── DurableCursor - Event replay       │
│  └── DurableLock - Concurrent safety    │
└─────────────────────────────────────────┘

Key design decisions:

  1. Events stay local - Not synced via git (too complex)
  2. JSONL exports projections - Same format as beads for merge driver compatibility
  3. Hybrid model - Events for audit/learning, projections for queries

Component Reuse Assessment

From swarm-mail (75% reusable)

ComponentReuseNotes
Event Store80%Add bead event types
Projection Pattern95%Add new cases
DatabaseAdapter100%Perfect as-is
DurableCursor90%For replay/sync
DurableLock90%Critical for concurrent updates
Migrations100%Add bead tables

From steveyegge/beads (vendor/port)

ComponentActionLicense
Merge DriverVendorMIT
Hash ID GeneratorPort to TSMIT
JSONL SchemaAdoptMIT
FlushManager PatternPort to TSMIT
Blocked Cache LogicPort to TSMIT

Event Schema

20 event types covering the full bead lifecycle:

type BeadEvent =
  // Lifecycle (6)
  | BeadCreatedEvent
  | BeadUpdatedEvent
  | BeadStatusChangedEvent
  | BeadClosedEvent
  | BeadReopenedEvent
  | BeadDeletedEvent
  
  // Dependencies (2)
  | BeadDependencyAddedEvent
  | BeadDependencyRemovedEvent
  
  // Labels (2)
  | BeadLabelAddedEvent
  | BeadLabelRemovedEvent
  
  // Comments (3)
  | BeadCommentAddedEvent
  | BeadCommentUpdatedEvent
  | BeadCommentDeletedEvent
  
  // Epic (3)
  | BeadEpicChildAddedEvent
  | BeadEpicChildRemovedEvent
  | BeadEpicClosureEligibleEvent
  
  // Swarm Integration (2)
  | BeadAssignedEvent
  | BeadWorkStartedEvent
  
  // Maintenance (1)
  | BeadCompactedEvent

Event schemas are already implemented in packages/opencode-swarm-plugin/src/schemas/bead-events.ts

Git Sync Strategy

Export Flow

Event Appended

updateMaterializedViews() [inline, same tx]

Mark bead dirty

FlushManager debounce (30s)

Export dirty beads to JSONL

Clear dirty flags

Git hooks (optional auto-commit)

Import Flow

Git pull / merge

Parse JSONL

For each issue:
  - Hash match? Skip
  - ID exists? Update projection
  - New ID? Insert projection

Emit "bead_imported" events (audit)

Rebuild blocked_beads_cache

Risk Assessment

RiskLikelihoodImpactMitigation
Event store performance at scaleLowMediumBatched replay, indexes
Merge conflicts in JSONLLowLowProven merge driver
Projection drift from eventsMediumHighChecksums, replay
Breaking existing beads_* toolsMediumHighAdapter layer

Implementation Plan

PhaseDurationDeliverables
FoundationWeek 1Projections, dirty tracking, basic export
Git SyncWeek 2Merge driver, import, FlushManager
Query LayerWeek 3Ready work, blocked cache, cycle detection
Plugin MigrationWeek 4Migrate beads_* tools, compat layer
PolishWeek 5-6Performance, error handling, migration tooling

Alternatives Considered

AlternativeWhy Rejected
Pure Event SourcingMerge conflicts nightmarish for events
Keep Separate SystemsDuplicated infrastructure, no learning
Fork steveyegge/beadsDifferent language, harder integration

Conclusion

Recommendation: BUILD IT

The hybrid approach is sound:

  • Low risk - Proven patterns from both systems
  • High value - Unified infrastructure, learning integration
  • Reasonable effort - 4-6 weeks with 75% reuse
  • Clear path - Phased implementation, backward compatible

Git Sync Deep Dive

Detailed analysis of how steveyegge/beads achieves distributed sync via git.

JSONL Format

One JSON object per line (snapshots, not events):

{"id":"bd-0134cc5a","title":"Fix auto-import","status":"closed","priority":0,...}
{"id":"bd-af78.1","title":"Add auth","status":"open","priority":1,...}

Why JSONL?

  • Git-friendly (line-based diffs)
  • Merge-friendly (conflicts are rare)
  • Human-readable
  • Streamable

Incremental Export

CREATE TABLE dirty_issues (
    issue_id TEXT PRIMARY KEY,
    marked_at TIMESTAMP,
    content_hash TEXT
);

Flow:

  1. Mutation → MarkIssueDirty(issueID)
  2. FlushManager debounces (30s)
  3. Export dirty issues only
  4. Clear dirty flags

3-Way Merge Driver

Field-level merge rules:

  • Timestamps: Max value wins
  • Dependencies: Union of both sides
  • Text fields: Side with latest updated_at wins
  • Tombstones: Always win (unless expired)

Hash-Based IDs

Content-based hash prevents collisions:

h := sha256.New()
h.Write([]byte(title))
h.Write([]byte(description))
h.Write([]byte(created.Format(time.RFC3339Nano)))
h.Write([]byte(workspaceID))

Progressive length scaling: 6 chars → 7 → 8 on collision.

DB Size6-char collision7-char8-char
1,0000.02%0.00%0.00%
10,0002.27%0.06%0.00%
100,00099.99%6.24%0.18%

Research Methodology

This research was conducted by a swarm of 5 parallel agents:

  1. Architecture Analysis - Deep dive into steveyegge/beads Go implementation
  2. Infrastructure Mapping - Assess swarm-mail reuse potential
  3. Event Schema Design - Draft Zod schemas for bead events
  4. Git Sync Analysis - Understand distributed coordination
  5. Synthesis - Consolidate findings into this EDR

Tools used:

  • repo-crawl_* - GitHub API exploration
  • repo-autopsy_* - Deep code analysis
  • semantic-memory_* - Past learnings
  • cass_search - Cross-agent session history

References

On this page