Files
gastown/docs/beads-data-plane.md
Steve Yegge 54c82694d0 Add Beads Universal Data Plane documentation
Documents how beads serves as the universal data store for all agent
coordination: work molecules, mail messages, hooks, and inboxes are
all issues with consistent field semantics.

Key insights:
- Mail reuses assignee (to), title (subject), labels (from:)
- Hooks and inboxes are queries, not containers
- Two-level architecture: town beads (mail) vs rig beads (work)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 14:14:59 -08:00

276 lines
12 KiB
Markdown

# 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