Private
Public Access
0
0
Files
manual_slop/docs/guide_beads.md
T

15 KiB

Beads Mode (Dolt-Backed Issue Tracking)

Top | MMA | Tools & IPC | Simulations


Overview

Beads is a Dolt-backed issue tracking system used as a first-class, project-specific alternative to Manual Slop's markdown-based ticket tracking. Beads stores the task graph in a local .beads/ Dolt repository, allowing tracks and tickets to be versioned alongside the code in git.

This guide covers:

  1. Architecture — Where Beads fits in the manual_slop stack
  2. ComponentsBeadsClient, the Dolt repository, the bead data model
  3. MCP Tool Integrationbd_create, bd_list, bd_ready, bd_update
  4. MMA Integration (Roadmap) — How Beads mode would integrate with the ConductorEngine
  5. Testing — Current state and limitations

Current state (as of 2026-06-02): The BeadsClient is a mock implementation that uses a .beads_mock/ directory with a JSON file. The real Dolt-backed Beads integration is roadmap, not yet implemented. The MCP tools, however, are fully wired and functional against the mock. When the real Beads is integrated, the MCP tool signatures and the BeadsClient public API will remain stable; only the backing storage changes.


Architecture

Beads is a per-project issue tracking layer that lives alongside Manual Slop's other per-project state (conductor directory, log directory, project TOML).

manual_slop_project/
├── .beads/                   # Real Beads: Dolt repository (planned)
│   ├── .dolt/                # Dolt internal storage (when implemented)
│   └── config.json           # Beads configuration
├── .beads_mock/              # Current mock: flat JSON storage
│   └── beads.json            # List of bead objects
├── conductor/                # Manual Slop's own track storage
│   └── tracks/
├── manual_slop.toml          # Project configuration
└── ... (source files)

Lifecycle:

  • A project may opt into Beads mode by setting [beads] enabled = true in manual_slop.toml.
  • When enabled, BeadsClient.init_repo() is called on project load to ensure the .beads/ (or .beads_mock/) directory exists.
  • All track/ticket state for that project flows through BeadsClient instead of the conductor's conductor/tracks/<id>/ markdown files.
  • The MMA dashboard's Visual DAG reads from BeadsClient.list_beads() instead of in-memory TrackDAG.

Why Beads? The Markdown-based track storage (in conductor/tracks/) works well for human-authored planning but is awkward for AI-driven workflows. Beads provides:

  • Programmatic CRUD — agents can bd_create, bd_update, bd_ready without parsing markdown.
  • Dependency graphs — beads have explicit depends_on relationships with cycle detection.
  • Versioning — the Dolt repo is git-trackable, so the task graph has the same history guarantees as the code.
  • External tooling — the bd CLI works outside Manual Slop for human review.

Components

Bead (Data Model)

@dataclass
class Bead:
    id: str
    title: str
    description: str
    status: str = "active"
Field Type Purpose
id str Unique identifier. Format: bead-<N> (sequential, mock) or Dolt-generated hash (real).
title str Short, human-readable title.
description str Full description. May include multi-line text, file references, and acceptance criteria.
status str One of active, in_progress, closed, blocked.

Future fields (planned for the real Dolt integration):

  • depends_on: List[str] — explicit dependency graph
  • assignee: Optional[str] — Tier or human owner
  • priority: int — 0-9 ranking
  • created_at: datetime, updated_at: datetime
  • tags: List[str] — for filtering
  • acceptance_criteria: List[str] — structured completion conditions

BeadsClient (src/beads_client.py)

The Python client for the Beads backend.

class BeadsClient:
    def __init__(self, working_dir: Path):
        self.working_dir = Path(working_dir)
        self.repo_dir = self.working_dir / ".beads_mock"
        self.beads_file = self.repo_dir / "beads.json"

Construction: Takes a working_dir (typically the project base directory). The mock client derives the storage path as <working_dir>/.beads_mock/beads.json. The real client will derive <working_dir>/.beads/ for Dolt.

Public Methods:

def init_repo(self) -> None:
    """Create the storage directory and an empty beads.json if not present."""

def is_initialized(self) -> bool:
    """Check whether the storage directory exists."""

def create_bead(self, title: str, description: str) -> str:
    """Create a new bead. Returns the new bead's ID."""

def update_bead(self, bead_id: str, status: str) -> bool:
    """Update the status of an existing bead. Returns True if found, False otherwise."""

def list_beads(self) -> List[Bead]:
    """Return all beads as a list of Bead objects."""

Internal Storage (mock):

def _read_beads(self) -> List[dict]:
    if not self.beads_file.exists():
        return []
    return json.loads(self.beads_file.read_text(encoding="utf-8"))

def _write_beads(self, beads: List[dict]) -> None:
    self.beads_file.write_text(json.dumps(beads, indent=1), encoding="utf-8")

The mock uses a single JSON file with a list of bead objects. The real client will use Dolt SQL tables (beads, dependencies).

Thread Safety: The mock client's _read_beads / _write_beads are not internally synchronized. The caller (typically mcp_client.dispatch from a worker thread) is expected to serialize access. The real Dolt-backed client will use Dolt's transaction system.

Beads CLI (bd)

When the real integration is in place, the bd CLI (provided by the Beads project) is the primary user-facing tool for managing beads outside Manual Slop. The Python client is a thin wrapper around bd subprocess calls or a direct Dolt connection.

CLI usage (planned):

bd create "Fix RAG race condition" "Vector store concurrent writes need a lock"
bd list
bd ready       # List beads with no unresolved dependencies
bd update bead-3 in_progress

The Python BeadsClient is intended to be a programmatic equivalent of the bd CLI for use by agents.


MCP Tool Integration

The four Beads operations are exposed as MCP tools in src/mcp_client.py:1474-1494. The dispatch checks tool_name.startswith("bd_") and routes to BeadsClient methods.

Tool Inventory

Tool Parameters Maps to Returns
bd_create title: str, description: str BeadsClient.create_bead The new bead's ID
bd_list (none) BeadsClient.list_beads Formatted list: ID: <id>, Status: <status>, Title: <title>
bd_update bead_id: str, status: str BeadsClient.update_bead Success/failure indicator
bd_ready (none) (filtered list_beads) Beads with no unresolved dependencies (mock: returns all active beads)

Dispatch Flow

# In src/mcp_client.py:dispatch
if tool_name.startswith("bd_"):
    if not _primary_base_dir:
        return "ERROR: no active workspace to run beads tools."
    bclient = BeadsClient(_primary_base_dir)
    if tool_name == "bd_list":
        beads = bclient.list_beads()
        if not beads:
            return "No beads found."
        return "\n".join([f"ID: {b.id}, Status: {b.status}, Title: {b.title}" for b in beads])
    elif tool_name == "bd_create":
        bead_id = bclient.create_bead(title=tool_input["title"], description=tool_input["description"])
        return f"Created bead {bead_id}"
    elif tool_name == "bd_update":
        success = bclient.update_bead(tool_input["bead_id"], tool_input["status"])
        return f"Updated {tool_input['bead_id']}" if success else f"Bead {tool_input['bead_id']} not found"
    elif tool_name == "bd_ready":
        # Mock: return all active beads. Real: filter by resolved dependencies.
        beads = [b for b in bclient.list_beads() if b.status == "active"]
        ...

Tool Schemas (registered in get_tool_schemas)

{
    "name": "bd_create",
    "description": "Create a new bead in the active Beads repository",
    "parameters": {
        "type": "object",
        "properties": {
            "title": {"type": "string"},
            "description": {"type": "string"}
        },
        "required": ["title", "description"]
    }
}
# Similar for bd_update, bd_list, bd_ready

Required Base Directory

The dispatch requires _primary_base_dir to be set (i.e., a project is loaded). If not, the tools return "ERROR: no active workspace to run beads tools." This prevents agents from accidentally reading/writing to a global Beads repo.


MMA Integration (Roadmap)

Current state: The ConductorEngine does not yet consult or write to Beads. Tracks and tickets are still stored in conductor/tracks/<id>/ markdown files.

Planned Integration:

1. ConductorEngine.parse_json_tickets → Beads

When Beads mode is active, the JSON tickets ingested from Tier 2's output would be forwarded to BeadsClient.create_bead:

def parse_json_tickets(self, json_str: str) -> None:
    tickets = json.loads(json_str)
    if self.beads_enabled:
        for ticket in tickets:
            self.beads_client.create_bead(
                title=ticket["description"],
                description=json.dumps(ticket, indent=2)  # Full ticket as description
            )
    else:
        # Existing markdown-based path
        ...

2. ConductorEngine.save_track_state → Beads

Track state persistence would write to Beads instead of state.toml:

def save_track_state(self, track_id: str, state: TrackState) -> None:
    if self.beads_enabled:
        # Update the corresponding bead's status
        for ticket in state.tickets:
            self.beads_client.update_bead(ticket.id, ticket.status)
    else:
        # Existing markdown path
        ...

3. Visual DAG → Beads

The MMA dashboard's Visual DAG would query BeadsClient.list_beads() instead of the in-memory TrackDAG:

def get_dag_tickets(self) -> List[Ticket]:
    if self.beads_enabled:
        beads = self.beads_client.list_beads()
        return [self._bead_to_ticket(b) for b in beads]
    else:
        return self.track.tickets

4. Context Compaction for Beads

Completed beads would be summarized and archived to free context window space (see test_aggregate_beads.py:test_build_beads_compaction for the test pattern).

Configuration

[beads]
enabled = true
backend = "mock"  # or "dolt" when implemented
auto_archive_completed = true

When backend = "dolt", the real Beads client is used (when implemented). When backend = "mock", the current JSON storage is used. This allows projects to migrate incrementally.


Activation

To enable Beads mode for a project:

  1. Add to manual_slop.toml:

    [beads]
    enabled = true
    backend = "mock"  # "dolt" once available
    
  2. Restart Manual Slop (or reload the project).

  3. The GUI's MMA Dashboard gains a "Beads" tab showing the current beads.

  4. Agents can now use the bd_* MCP tools.

Note: As of 2026-06-02, the conductor's track/ticket storage does not yet auto-migrate to Beads. Tracks created before Beads activation remain in conductor/tracks/. Tracks created after Beads activation may be written to Beads (depending on which integration milestone is shipped).


Testing

Unit Tests

  • tests/test_beads_client.pyBeadsClient lifecycle (init, create, update, list) against a temp directory
  • tests/test_mcp_client_beads.py — End-to-end MCP dispatch for bd_create, bd_list, bd_update, bd_ready

Integration Tests

  • tests/test_gui_dag_beads.py — Loads beads into the Visual DAG, verifies nodes and edges
  • tests/test_aggregate_beads.py — Context compaction for completed beads

Test Pattern

def test_bd_create_dispatch(tmp_path, live_gui):
    # Set up a project with Beads enabled
    project_dir = tmp_path / "test_project"
    project_dir.mkdir()
    (project_dir / "manual_slop.toml").write_text("[beads]\nenabled = true\nbackend = \"mock\"\n")
    
    # Trigger dispatch via the MCP client
    response = live_gui[1].call_tool("bd_create", {
        "title": "Test Bead",
        "description": "Created in test"
    })
    
    # Verify the bead was created
    assert "Created bead bead-" in response

The mock backend's filesystem-based storage makes tests fast and deterministic. Tests use tmp_path for isolation, satisfying the Artifact Isolation rule in the Structural Testing Contract.


Limitations

  1. Mock Backend: The current BeadsClient uses JSON files, not Dolt. There's no SQL query support, no transaction isolation, no proper concurrent write handling.

  2. No Dependency Graph: The mock's list_beads() returns all beads. The bd_ready tool is a placeholder that returns all active beads regardless of dependencies. The real Dolt integration will use a dependencies table to implement true ready-queue semantics.

  3. No Versioning: The mock has no history. Beads created today are the same as beads created yesterday. The real Dolt integration will provide point-in-time queries and diffs.

  4. No External Sync: The mock doesn't sync with the bd CLI. Changes via bd CLI on the same .beads_mock/ directory would be lost on next write from the Python client (and vice versa).

  5. Conductor Coexistence: Until the planned integration lands, beads and conductor tracks are two parallel systems. Users must choose one or the other per project (or accept that some data lives in both).

  6. No Schema Migration: When the real Dolt integration lands, a migration tool will be needed to convert existing .beads_mock/beads.json files to Dolt SQL tables. This tool is not yet built.


Future Work

  • Dolt Integration — Replace the mock with a real Dolt-backed client. Use Dolt's Python client or shell out to the dolt CLI.
  • Dependency Graph — Add a dependencies table; implement bd_ready with proper dependency resolution.
  • Conductor Coexistence — Allow tracks to be either markdown-based or Beads-based per project.
  • External CLI Sync — Detect bd CLI invocations and reconcile state.
  • Migration Tool — Convert .beads_mock/beads.json to Dolt.
  • Real-Time Updates — Beads created by external bd invocations appear in the GUI in real time via Dolt's event log.

See guide_mma.md#beads-integration-roadmap for the broader MMA integration plan.