diff --git a/docs/beads-data-plane.md b/docs/beads-data-plane.md new file mode 100644 index 00000000..642126c2 --- /dev/null +++ b/docs/beads-data-plane.md @@ -0,0 +1,275 @@ +# Beads: The Universal Data Plane + +> **Status**: Canonical Architecture Documentation +> **See also**: [pinned-beads-design.md](pinned-beads-design.md), [propulsion-principle.md](propulsion-principle.md) + +## Overview + +Gas Town agents coordinate through a single, unified data store: **Beads**. Every +piece of agent state - work assignments, mail messages, molecules, hooks - lives +in beads as issues with consistent field semantics. + +This document explains the universal data model that powers agent coordination. + +## The Core Insight + +Beads is not just an issue tracker. It's a **universal data plane** where: + +- **Work molecules** are issues with steps as child issues +- **Mail messages** are issues with sender/recipient encoded in fields +- **Hooks** are queries over issues (`assignee = me AND pinned = true`) +- **Inboxes** are queries over issues (`assignee = me AND status = open AND has from: label`) + +Everything is an issue. The semantics come from how fields are used. + +## The Unified Data Model + +Every beads issue has these core fields: + +| Field | Type | Purpose | +|-------|------|---------| +| `id` | string | Unique identifier (e.g., `gt-xxxx`, `hq-yyyy`) | +| `title` | string | Brief summary | +| `description` | string | Full content | +| `status` | enum | `open`, `in_progress`, `closed`, `pinned` | +| `assignee` | string | Who this is assigned to | +| `priority` | int | 0=critical, 1=high, 2=normal, 3=low, 4=backlog | +| `type` | string | `task`, `bug`, `feature`, `epic` | +| `labels` | []string | Metadata tags | +| `pinned` | bool | Whether this is pinned to assignee's hook | +| `parent` | string | Parent issue ID (for hierarchies) | +| `created_at` | timestamp | Creation time | + +## Field Reuse Patterns + +### Work Molecules + +A molecule is an issue that represents executable work: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Issue: gt-abc1 │ +│ ───────────────────────────────────────────────────────── │ +│ title: "Implement user authentication" │ +│ type: epic │ +│ assignee: "gastown/crew/max" │ +│ pinned: true ← ON MY HOOK │ +│ status: in_progress │ +│ description: "Full auth flow with OAuth..." │ +│ children: [gt-abc2, gt-abc3, gt-abc4] ← STEPS │ +└─────────────────────────────────────────────────────────────┘ +``` + +The hook query: `WHERE assignee = me AND pinned = true` + +### Mail Messages + +Mail reuses the same fields with different semantics: + +``` +MAIL FIELD → BEADS FIELD +═══════════════════════════════════════════════════════════ + +To (recipient) → assignee +Subject → title +Body → description +From (sender) → labels: ["from:mayor/"] +Thread ID → labels: ["thread:thread-xxx"] +Reply-To → labels: ["reply-to:hq-yyy"] +Message Type → labels: ["msg-type:task"] +Unread → status: open +Read → status: closed +``` + +Example mail message as a bead: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Issue: hq-def2 │ +│ ───────────────────────────────────────────────────────── │ +│ title: "Fix the auth bug" ← SUBJECT │ +│ assignee: "gastown/crew/max" ← TO │ +│ status: open ← UNREAD │ +│ labels: ["from:mayor/", ← FROM │ +│ "thread:thread-abc", ← THREAD │ +│ "msg-type:task"] ← TYPE │ +│ description: "The OAuth flow is broken..." ← BODY │ +└─────────────────────────────────────────────────────────────┘ +``` + +The inbox query: `WHERE assignee = me AND status = open AND has_label("from:*")` + +### Distinguishing Mail from Work + +How does the system know if an issue is mail or work? + +| Indicator | Mail | Work | +|-----------|------|------| +| Has `from:` label | Yes | No | +| Has `pinned: true` | Rarely | Yes (when on hook) | +| Parent is molecule | No | Often | +| ID prefix | `hq-*` (town beads) | `gt-*` (rig beads) | + +The `from:` label is the canonical discriminator. Regular issues don't have senders. + +## Two-Level Beads Architecture + +Gas Town uses beads at two levels: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TOWN LEVEL: ~/gt/.beads/ │ +│ ───────────────────────────────────────────────────────── │ +│ Prefix: hq-* │ +│ Contains: │ +│ - All mail (cross-agent communication) │ +│ - Mayor coordination issues │ +│ - Cross-rig work items │ +│ Sync: Direct commit to main (single location) │ +└─────────────────────────────────────────────────────────────┘ + │ + │ gt mail commands auto-route here + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ RIG LEVEL: ~/gt/gastown/crew/max/.beads/ │ +│ ───────────────────────────────────────────────────────── │ +│ Prefix: gt-* │ +│ Contains: │ +│ - Project issues (bugs, features, tasks) │ +│ - Molecules (work patterns) │ +│ - Agent hook states │ +│ Sync: Via beads-sync branch (multiple clones) │ +└─────────────────────────────────────────────────────────────┘ +``` + +Key points: +- **Mail always uses town beads** (`~/gt/.beads/`) - `gt mail` routes automatically +- **Project work uses rig beads** (in your clone) - `bd` commands use local `.beads/` +- **Different sync strategies**: Town is single-writer, rig uses branch-based sync + +## The Query Model + +Both hooks and inboxes are **views** (queries) over the flat beads collection: + +``` +BEADS DATABASE (flat collection) +══════════════════════════════════════════════════════════════ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ hq-aaa │ │ hq-bbb │ │ gt-xxx │ │ gt-yyy │ +│ mail │ │ mail │ │ task │ │ molecule │ +│ to: max │ │ to: joe │ │ assign: │ │ assign: │ +│ open │ │ closed │ │ max │ │ max │ +│ │ │ │ │ pinned: │ │ │ +│ ↓ INBOX │ │ │ │ true │ │ │ +│ │ │ │ │ ↓ HOOK │ │ │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + + +INBOX QUERY (for max): + SELECT * FROM beads + WHERE assignee = 'gastown/crew/max' + AND status = 'open' + AND labels CONTAINS 'from:*' + → Returns: [hq-aaa] + + +HOOK QUERY (for max): + SELECT * FROM beads + WHERE assignee = 'gastown/crew/max' + AND pinned = true + → Returns: [gt-xxx] +``` + +There is no container. No inbox bead. No hook bead. Just queries over issues. + +## Session Cycling Through the Data Lens + +When an agent cycles (hands off to fresh session), the data model ensures continuity: + +### What Persists (in beads) + +| Data | Location | Survives Restart | +|------|----------|------------------| +| Pinned molecule | Rig beads | Yes | +| Handoff mail | Town beads | Yes | +| Issue state | Rig beads | Yes | +| Git commits | Git | Yes | + +### What Doesn't Persist + +| Data | Why Not | +|------|---------| +| Claude context | Cleared on restart | +| In-memory state | Process dies | +| Uncommitted changes | Not in git | +| Unflushed beads | Not synced | + +### The Cycle + +``` +END OF SESSION START OF SESSION +══════════════════ ══════════════════ + +1. Commit & push code 1. gt prime (loads context) + +2. bd sync (flush beads) 2. gt mol status + └─ Molecule state saved └─ Query: pinned = true + ↓ +3. gt handoff -s "..." -m "..." 3. Hook has molecule? + └─ Creates mail in town beads ├─ YES → Execute it + (assignee = self) └─ NO → Query inbox + ↓ +4. Session dies 4. Inbox has handoff mail? + ├─ YES → Read context + └─ NO → Wait for work +``` + +The **molecule is the source of truth** for what you're working on. +The **handoff mail is supplementary context** (optional but helpful). + +## Command to Data Mapping + +| Command | Data Operation | +|---------|----------------| +| `gt mol status` | Query: `assignee = me AND pinned = true` | +| `gt mail inbox` | Query: `assignee = me AND status = open AND from:*` | +| `gt mail read X` | Read issue X, no status change | +| `gt mail delete X` | Close issue X (status → closed) | +| `gt sling X to Y` | Update X: `assignee = Y, pinned = true` | +| `bd pin X` | Update X: `pinned = true` | +| `bd close X` | Update X: `status = closed` | + +## Why This Design? + +### 1. Single Source of Truth + +All agent state lives in beads. No separate databases, no config files, no +hidden state. If you can read beads, you can understand the entire system. + +### 2. Queryable Everything + +Hooks, inboxes, work queues - all are just queries. Want to find all blocked +work? Query. Want to see what's assigned to a role? Query. The data model +supports arbitrary views. + +### 3. Git-Native Persistence + +Beads syncs through git. This gives you: +- Version history +- Branch-based isolation +- Merge conflict resolution +- Distributed replication + +### 4. No Schema Lock-in + +New use cases emerge by convention, not schema changes. Mail was added by +reusing `assignee` for recipient and adding `from:` labels. No database +migration needed. + +## Related Documents + +- [pinned-beads-design.md](pinned-beads-design.md) - Hook semantics per role +- [propulsion-principle.md](propulsion-principle.md) - The "RUN IT" protocol +- [sling-design.md](sling-design.md) - Work assignment mechanics +- [molecular-chemistry.md](molecular-chemistry.md) - Molecule patterns