316 lines
15 KiB
Markdown
316 lines
15 KiB
Markdown
# 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: <clone>/.beads/ │ │ EPHEMERAL: <rig>/.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** (`<clone>/.beads/`) - project issues, molecules, hooks
|
|
- **Rig ephemeral** (`<rig>/.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
|