# Beads as Data Plane (CLAW) > **Status**: Design documentation > **See also**: [pinned-beads-design.md](pinned-beads-design.md), [propulsion-principle.md](propulsion-principle.md) ## Overview Gas Town agents coordinate through **Beads** - a git-backed issue tracker. **CLAW**: **C**ommits as **L**edger of **A**ll **W**ork. Git commits are the persistence mechanism. Every state change becomes a commit, giving us atomic updates, full history, and distributed sync for free. We store agent state (work assignments, mail, molecules, hooks) as beads issues. ## How We Use It We're treating beads as more than an issue tracker: - **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 the general abstraction for executable work in Gas Town. Molecules can take various **shapes** - structural patterns that encode different workflow semantics. The data plane stores all shapes the same way; the semantics come from how the structure is interpreted. **Common molecular shapes:** | Shape | Structure | Use Case | |-------|-----------|----------| | **Epic** | Parent + flat children (TODO list) | Simple feature work, linear decomposition | | **Christmas Ornament** | Trunk + dynamic arms + base | Patrol cycles with variable fanout | | **Compound** | Multiple bonded molecules | Complex multi-phase workflows | | **Polymer** | Chained compound molecules | Large-scale autonomous operation | > **All epics are molecules. Not all molecules are epics.** > For the full taxonomy of shapes, see [molecular-chemistry.md](molecular-chemistry.md). Here's an **epic** (the simplest molecular shape) stored as beads data: ``` ┌─────────────────────────────────────────────────────────────┐ │ Issue: gt-abc1 │ │ ───────────────────────────────────────────────────────── │ │ title: "Implement user authentication" │ │ type: epic ← SHAPE: TODO LIST │ │ 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] ← FLAT CHILDREN │ └─────────────────────────────────────────────────────────────┘ ``` The hook query: `WHERE assignee = me AND pinned = true` **Why the data plane doesn't distinguish shapes:** The shape lives in the structure (parent/child relationships, dependency edges), not in a separate field. An epic is recognized by its flat parent-children structure. A Christmas Ornament is recognized by dynamic arms bonded during execution. The beads database just stores issues with relationships - the chemistry layer interprets the shape. ### 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-Tier Beads Architecture Gas Town uses beads at two levels, each with persistent and ephemeral components: ``` ══════════════════════════════════════════════════════════════════════════ TOWN LEVEL ══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ PERSISTENT: ~/gt/.beads/ │ │ EPHEMERAL: ~/gt/.beads-wisp/ │ │ ───────────────────────────── │ │ ───────────────────────────── │ │ Prefix: hq-* │ │ Git tracked: NO │ │ Git tracked: Yes │ │ Contains: │ │ Contains: │ │ - Deacon patrol cycles │ │ - All mail │ │ - Town-level ephemeral work │ │ - Mayor coordination │ │ Lifecycle: │ │ - Cross-rig work items │ │ Created → Executed → │ │ Sync: Direct commit to main │ │ Squashed to digest → Deleted │ └─────────────────────────────────┘ └─────────────────────────────────┘ ══════════════════════════════════════════════════════════════════════════ RIG LEVEL ══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ PERSISTENT: /.beads/ │ │ EPHEMERAL: /.beads-wisp/ │ │ ───────────────────────────── │ │ ───────────────────────────── │ │ Prefix: gt-* (rig-specific) │ │ Git tracked: NO │ │ Git tracked: Yes │ │ Contains: │ │ Contains: │ │ - Witness patrol cycles │ │ - Project issues │ │ - Refinery patrol cycles │ │ - Molecules (work patterns) │ │ - Rig-level ephemeral work │ │ - Agent hook states │ │ Lifecycle: │ │ Sync: Via beads-sync branch │ │ Created → Executed → │ │ │ │ Squashed to digest → Deleted │ └─────────────────────────────────┘ └─────────────────────────────────┘ ``` Key points: - **Each level has persistent + ephemeral storage** - **Town persistent** (`~/gt/.beads/`) - mail, mayor work, cross-rig coordination - **Town ephemeral** (`~/gt/.beads-wisp/`) - deacon patrols - **Rig persistent** (`/.beads/`) - project issues, molecules, hooks - **Rig ephemeral** (`/.beads-wisp/`) - witness/refinery patrols - **Sync strategies**: Persistent beads sync via git; ephemeral wisps never 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 [Y]` | Hook X to Y (or self), inject start prompt | | `gt hook X` | Update X: `pinned = true` (assign without action) | | `gt handoff X` | Hook X, restart session (GUPP kicks in) | | `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) - Full taxonomy of molecular shapes, phases, and operators - [session-lifecycle.md](session-lifecycle.md) - The Single-Bond Principle and context cycling