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>
276 lines
12 KiB
Markdown
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
|