docs: Radically condense documentation (10k → 548 lines)
Replace 28 sprawling docs with 2 focused ones: - README.md: User-focused, top-down intro (268 lines) - docs/reference.md: Technical reference (280 lines) Key changes: - Top-down structure (formulas first, not beads) - Bullets/tables over prose - Human commands (start/shutdown/attach) vs agent commands - All 6 roles documented (Overseer through Polecat) - Ice-9/protomolecule easter eggs for the Expanse/Vonnegut fans - Prefix-based beads routing explanation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
306
README.md
306
README.md
@@ -1,106 +1,268 @@
|
||||
# Gas Town
|
||||
|
||||
> **Status**: Experimental (v0.1) - We're exploring these ideas and invite you to explore with us.
|
||||
Multi-agent orchestrator for Claude Code. Sling work to agents; they run it.
|
||||
|
||||
Gas Town is an experiment in multi-agent coordination for Claude Code. It provides infrastructure for spawning workers, tracking work via molecules, and coordinating merges.
|
||||
## Why Gas Town?
|
||||
|
||||
We think of it using steam-age metaphors:
|
||||
|
||||
```
|
||||
Claude = Fire (the energy source)
|
||||
Claude Code = Steam Engine (harnesses the fire)
|
||||
Gas Town = Steam Train (coordinates engines on tracks)
|
||||
Beads = Railroad Tracks (the persistent ledger)
|
||||
```
|
||||
|
||||
The goal is a "village" architecture - not rigid hierarchy, but distributed awareness where agents can help neighbors when something is stuck. Whether this actually works at scale is something we're still discovering.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.23+** - For building from source
|
||||
- **Git** - For rig management and beads sync
|
||||
- **tmux** - Required for agent sessions (all workers run in tmux panes)
|
||||
- **Claude Code CLI** - Required for agents (`claude` command must be available)
|
||||
|
||||
## Install
|
||||
|
||||
**From source (recommended for now):**
|
||||
|
||||
```bash
|
||||
go install github.com/steveyegge/gastown/cmd/gt@latest
|
||||
```
|
||||
|
||||
**Package managers (coming soon):**
|
||||
|
||||
```bash
|
||||
# Homebrew (macOS/Linux)
|
||||
brew install gastown
|
||||
|
||||
# npm (cross-platform)
|
||||
npm install -g @anthropic/gastown
|
||||
```
|
||||
| Without | With Gas Town |
|
||||
|---------|---------------|
|
||||
| Agents forget work after restart | Work persists on hooks - survives crashes, compaction, restarts |
|
||||
| Manual coordination | Agents have mailboxes, identities, and structured handoffs |
|
||||
| 4-10 agents is chaotic | Comfortably scale to 20-30 agents |
|
||||
| Work state in agent memory | Work state in Beads (git-backed ledger) |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Create a town (workspace)
|
||||
# Install
|
||||
go install github.com/steveyegge/gastown/cmd/gt@latest
|
||||
|
||||
# Create workspace
|
||||
gt install ~/gt
|
||||
|
||||
# Add a project rig
|
||||
gt rig add myproject --remote=https://github.com/you/myproject.git
|
||||
# Add a project
|
||||
gt rig add myproject --remote=https://github.com/you/repo.git
|
||||
|
||||
# Assign work to a polecat
|
||||
gt sling myproject-123 myproject
|
||||
# Sling work to a polecat (worker)
|
||||
gt sling issue-123 myproject
|
||||
```
|
||||
|
||||
## Architecture
|
||||
## Core Concepts
|
||||
|
||||
```
|
||||
Town (~/gt/)
|
||||
├── Mayor (global coordinator)
|
||||
└── Rig: myproject
|
||||
├── Witness (lifecycle manager)
|
||||
├── Refinery (merge queue)
|
||||
└── Polecats (workers)
|
||||
Town (~/gt/) Your workspace
|
||||
├── Rig (project) Container for a git project + its agents
|
||||
│ ├── Polecats Workers (ephemeral, spawn → work → disappear)
|
||||
│ ├── Witness Monitors workers, handles lifecycle
|
||||
│ └── Refinery Merge queue processor
|
||||
└── Mayor Global coordinator
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
**Hook**: Each agent has a hook where work hangs. On wake, run what's on your hook.
|
||||
|
||||
Ideas we're exploring:
|
||||
**Beads**: Git-backed issue tracker. All work state lives here. [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
|
||||
|
||||
- **Molecular Chemistry of Work**: Protos (templates) → Mols (flowing work) → Wisps (ephemeral) → Digests (outcomes)
|
||||
- **Beads**: Git-backed, human-readable ledger for tracking work ([github.com/steveyegge/beads](https://github.com/steveyegge/beads))
|
||||
- **Village Model**: Distributed awareness instead of centralized monitoring
|
||||
- **Propulsion Principle**: Agents pull work from molecules rather than waiting for commands
|
||||
- **Nondeterministic Idempotence**: The idea that any worker can continue any molecule after crashes
|
||||
## Workflows
|
||||
|
||||
Some of these are implemented; others are still aspirational. See docs for current status.
|
||||
### Minimal (No Tmux)
|
||||
Run individual Claude Code instances manually. Gas Town just tracks state.
|
||||
```bash
|
||||
gt sling issue-123 myproject # Creates work assignment
|
||||
claude --resume # Agent reads mail, runs work
|
||||
```
|
||||
|
||||
## Commands
|
||||
### Full Stack (Tmux)
|
||||
Agents run in tmux sessions. Daemon manages lifecycle.
|
||||
```bash
|
||||
gt daemon start # Start lifecycle manager
|
||||
gt sling issue-123 myproject # Spawns polecat automatically
|
||||
```
|
||||
|
||||
### Pick Your Roles
|
||||
Gas Town is modular. Run what you need:
|
||||
- **Polecats only**: Manual spawning, no monitoring
|
||||
- **+ Witness**: Automatic worker lifecycle, stuck detection
|
||||
- **+ Refinery**: Merge queue, code review
|
||||
- **+ Mayor**: Cross-project coordination
|
||||
|
||||
## Cooking Formulas
|
||||
|
||||
Formulas define structured workflows. Cook them, sling them to agents.
|
||||
|
||||
### Basic Example
|
||||
|
||||
```toml
|
||||
# .beads/formulas/shiny.formula.toml
|
||||
formula = "shiny"
|
||||
description = "Design before code, review before ship"
|
||||
|
||||
[[steps]]
|
||||
id = "design"
|
||||
description = "Think about architecture"
|
||||
|
||||
[[steps]]
|
||||
id = "implement"
|
||||
needs = ["design"]
|
||||
|
||||
[[steps]]
|
||||
id = "test"
|
||||
needs = ["implement"]
|
||||
|
||||
[[steps]]
|
||||
id = "submit"
|
||||
needs = ["test"]
|
||||
```
|
||||
|
||||
### Using Formulas
|
||||
|
||||
```bash
|
||||
gt status # Town status
|
||||
gt rig list # List rigs
|
||||
gt sling <bead> <rig> # Assign work to polecat
|
||||
gt mail inbox # Check messages
|
||||
gt peek <worker> # Check worker health
|
||||
gt nudge <worker> # Wake stuck worker
|
||||
bd formula list # See available formulas
|
||||
bd cook shiny # Cook into a protomolecule
|
||||
bd pour shiny --var feature=auth # Create runnable molecule
|
||||
gt sling gt-xyz myproject # Assign to worker
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### What Happens
|
||||
|
||||
- [Vision](docs/vision.md) - Core innovations and philosophy
|
||||
- [Architecture](docs/architecture.md) - System design
|
||||
- [Molecular Chemistry](docs/molecular-chemistry.md) - Work composition
|
||||
- [Molecules](docs/molecules.md) - Workflow templates
|
||||
1. **Cook** expands the formula into a protomolecule (frozen template)
|
||||
2. **Pour** creates a molecule (live workflow) with steps as beads
|
||||
3. **Worker executes** each step, closing beads as it goes
|
||||
4. **Crash recovery**: Worker restarts, reads molecule, continues from last step
|
||||
|
||||
## Development
|
||||
### Example: Beads Release Molecule
|
||||
|
||||
A real workflow for releasing a new beads version:
|
||||
|
||||
```toml
|
||||
formula = "beads-release"
|
||||
description = "Version bump and release workflow"
|
||||
|
||||
[[steps]]
|
||||
id = "bump-version"
|
||||
description = "Update version in version.go and CHANGELOG"
|
||||
|
||||
[[steps]]
|
||||
id = "update-deps"
|
||||
needs = ["bump-version"]
|
||||
description = "Run go mod tidy, update go.sum"
|
||||
|
||||
[[steps]]
|
||||
id = "run-tests"
|
||||
needs = ["update-deps"]
|
||||
description = "Full test suite, check for regressions"
|
||||
|
||||
[[steps]]
|
||||
id = "build-binaries"
|
||||
needs = ["run-tests"]
|
||||
description = "Cross-compile for all platforms"
|
||||
|
||||
[[steps]]
|
||||
id = "create-tag"
|
||||
needs = ["build-binaries"]
|
||||
description = "Git tag with version, push to origin"
|
||||
|
||||
[[steps]]
|
||||
id = "publish-release"
|
||||
needs = ["create-tag"]
|
||||
description = "Create GitHub release with binaries"
|
||||
```
|
||||
|
||||
Cook it, pour it, sling it. The polecat runs through each step, and if it crashes
|
||||
after `run-tests`, a new polecat picks up at `build-binaries`.
|
||||
|
||||
### Formula Composition
|
||||
|
||||
```toml
|
||||
# Extend an existing formula
|
||||
formula = "shiny-enterprise"
|
||||
extends = ["shiny"]
|
||||
|
||||
[compose]
|
||||
aspects = ["security-audit"] # Add cross-cutting concerns
|
||||
```
|
||||
|
||||
## Key Commands
|
||||
|
||||
### For Humans (Overseer)
|
||||
|
||||
```bash
|
||||
go build -o gt ./cmd/gt
|
||||
go test ./...
|
||||
gt start # Start Gas Town (daemon + agents)
|
||||
gt shutdown # Graceful shutdown
|
||||
gt status # Town overview
|
||||
gt <role> attach # Jump into any agent session
|
||||
# e.g., gt mayor attach, gt witness attach
|
||||
```
|
||||
|
||||
Most other work happens through agents - just ask them.
|
||||
|
||||
### For Agents
|
||||
|
||||
```bash
|
||||
# Work
|
||||
gt sling <bead> <rig> # Assign work to polecat
|
||||
bd ready # Show available work
|
||||
bd list --status=in_progress # Active work
|
||||
|
||||
# Communication
|
||||
gt mail inbox # Check messages
|
||||
gt mail send <addr> -s "..." -m "..."
|
||||
|
||||
# Lifecycle
|
||||
gt handoff # Request session cycle
|
||||
gt peek <agent> # Check agent health
|
||||
|
||||
# Diagnostics
|
||||
gt doctor # Health check
|
||||
gt doctor --fix # Auto-repair
|
||||
```
|
||||
|
||||
## Roles
|
||||
|
||||
| Role | Scope | Job |
|
||||
|------|-------|-----|
|
||||
| **Overseer** | Human | Sets strategy, reviews output, handles escalations |
|
||||
| **Mayor** | Town-wide | Cross-rig coordination, work dispatch |
|
||||
| **Deacon** | Town-wide | Daemon process, agent lifecycle, plugin execution |
|
||||
| **Witness** | Per-rig | Monitor polecats, nudge stuck workers |
|
||||
| **Refinery** | Per-rig | Merge queue, PR review, integration |
|
||||
| **Polecat** | Per-task | Execute work, file discovered issues, request shutdown |
|
||||
|
||||
## The Propulsion Principle
|
||||
|
||||
> If your hook has work, RUN IT.
|
||||
|
||||
Agents wake up, check their hook, execute the molecule. No waiting for commands.
|
||||
Molecules survive crashes - any agent can continue where another left off.
|
||||
|
||||
---
|
||||
|
||||
## Optional: MEOW Deep Dive
|
||||
|
||||
**M**olecular **E**xpression **O**f **W**ork - the full algebra.
|
||||
|
||||
### States of Matter
|
||||
|
||||
| Phase | Name | Storage | Behavior |
|
||||
|-------|------|---------|----------|
|
||||
| Ice-9 | Formula | `.beads/formulas/` | Source template, composable |
|
||||
| Solid | Protomolecule | `.beads/` | Frozen template, reusable |
|
||||
| Liquid | Mol | `.beads/` | Flowing work, persistent |
|
||||
| Vapor | Wisp | `.beads/` (ephemeral flag) | Transient, for patrols |
|
||||
|
||||
*(Protomolecules are an homage to The Expanse. Ice-9 is a nod to Vonnegut.)*
|
||||
|
||||
### Operators
|
||||
|
||||
| Operator | From → To | Effect |
|
||||
|----------|-----------|--------|
|
||||
| `cook` | Formula → Protomolecule | Expand macros, flatten |
|
||||
| `pour` | Proto → Mol | Instantiate as persistent |
|
||||
| `wisp` | Proto → Wisp | Instantiate as ephemeral |
|
||||
| `squash` | Mol/Wisp → Digest | Condense to permanent record |
|
||||
| `burn` | Wisp → ∅ | Discard without record |
|
||||
|
||||
### Dynamic Bonding
|
||||
|
||||
Patrol workflows can spawn children at runtime:
|
||||
|
||||
```bash
|
||||
# Witness discovers 3 polecats, bonds an arm for each
|
||||
for polecat in ace nux toast; do
|
||||
bd mol bond mol-polecat-arm $PATROL_ID --var name=$polecat
|
||||
done
|
||||
```
|
||||
|
||||
Creates a "Christmas ornament" shape - trunk with dynamic arms.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.23+**
|
||||
- **Git**
|
||||
- **tmux** (for full stack mode)
|
||||
- **Claude Code CLI**
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
2341
docs/architecture.md
2341
docs/architecture.md
File diff suppressed because it is too large
Load Diff
@@ -1,315 +0,0 @@
|
||||
# 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
|
||||
@@ -1,224 +0,0 @@
|
||||
# Bootstrapping Gas Town from an HQ
|
||||
|
||||
This guide documents how to bootstrap a full Gas Town installation from an HQ repository (e.g., `steveyegge/stevey-gt`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- macOS or Linux
|
||||
- Git configured with SSH access to GitHub
|
||||
- Homebrew (for macOS)
|
||||
|
||||
## Overview
|
||||
|
||||
A Gas Town HQ is a template repository containing:
|
||||
- Town-level configuration (`mayor/`, `.beads/`)
|
||||
- Rig configs (`gastown/config.json`, `beads/config.json`)
|
||||
- CLAUDE.md for Mayor context
|
||||
|
||||
The HQ does NOT contain:
|
||||
- The actual gt binary (must be built)
|
||||
- Full rig structures (must be populated)
|
||||
- Agent state files (must be created)
|
||||
|
||||
## Step 1: Clone the HQ
|
||||
|
||||
```bash
|
||||
git clone git@github.com:steveyegge/stevey-gt.git ~/gt
|
||||
cd ~/gt
|
||||
```
|
||||
|
||||
## Step 2: Install Go
|
||||
|
||||
Gas Town is written in Go. Install it via Homebrew:
|
||||
|
||||
```bash
|
||||
brew install go
|
||||
go version # Verify: should show go1.25+
|
||||
```
|
||||
|
||||
## Step 3: Clone Gastown into Mayor's Rig
|
||||
|
||||
The gt binary lives in the gastown repository. Clone it into the mayor's rig directory:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/gt/gastown/mayor
|
||||
git clone git@github.com:steveyegge/gastown.git ~/gt/gastown/mayor/rig
|
||||
```
|
||||
|
||||
## Step 4: Build the gt Binary
|
||||
|
||||
```bash
|
||||
cd ~/gt/gastown/mayor/rig
|
||||
go build -o gt ./cmd/gt
|
||||
```
|
||||
|
||||
Optionally install to PATH:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/bin
|
||||
cp gt ~/bin/gt
|
||||
# Ensure ~/bin is in your PATH
|
||||
```
|
||||
|
||||
## Step 5: Populate Rig Structures
|
||||
|
||||
For each rig in your HQ (gastown, beads, etc.), create the full agent structure:
|
||||
|
||||
### Gastown Rig
|
||||
|
||||
```bash
|
||||
cd ~/gt/gastown
|
||||
|
||||
# Create directories
|
||||
mkdir -p refinery witness polecats crew/main
|
||||
|
||||
# Clone for refinery (canonical main)
|
||||
git clone git@github.com:steveyegge/gastown.git refinery/rig
|
||||
|
||||
# Clone for crew workspace
|
||||
git clone git@github.com:steveyegge/gastown.git crew/main
|
||||
```
|
||||
|
||||
### Beads Rig
|
||||
|
||||
```bash
|
||||
cd ~/gt/beads
|
||||
|
||||
# Create directories
|
||||
mkdir -p refinery mayor witness polecats crew/main
|
||||
|
||||
# Clone for each agent
|
||||
git clone git@github.com:steveyegge/beads.git refinery/rig
|
||||
git clone git@github.com:steveyegge/beads.git mayor/rig
|
||||
git clone git@github.com:steveyegge/beads.git crew/main
|
||||
```
|
||||
|
||||
## Step 6: Create Agent State Files
|
||||
|
||||
Each agent needs a state.json file:
|
||||
|
||||
```bash
|
||||
# Gastown agents
|
||||
echo '{"role": "refinery", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/gastown/refinery/state.json
|
||||
echo '{"role": "witness", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/gastown/witness/state.json
|
||||
echo '{"role": "mayor", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/gastown/mayor/state.json
|
||||
|
||||
# Beads agents
|
||||
echo '{"role": "refinery", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/beads/refinery/state.json
|
||||
echo '{"role": "witness", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/beads/witness/state.json
|
||||
echo '{"role": "mayor", "last_active": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > ~/gt/beads/mayor/state.json
|
||||
```
|
||||
|
||||
## Step 7: Initialize Town-Level Beads
|
||||
|
||||
```bash
|
||||
cd ~/gt
|
||||
bd init --prefix gm
|
||||
```
|
||||
|
||||
If there are existing issues in JSONL that fail to import (e.g., due to invalid issue types), you can:
|
||||
- Fix the JSONL manually and re-run `bd sync --import-only`
|
||||
- Or start fresh with the empty database
|
||||
|
||||
Run doctor to check for issues:
|
||||
|
||||
```bash
|
||||
bd doctor --fix
|
||||
```
|
||||
|
||||
## Step 8: Verify Installation
|
||||
|
||||
```bash
|
||||
cd ~/gt
|
||||
|
||||
# Check gt works
|
||||
gt status
|
||||
|
||||
# Check rig structure
|
||||
gt rig list
|
||||
|
||||
# Expected output:
|
||||
# gastown - Polecats: 0, Crew: 1, Agents: [refinery mayor]
|
||||
# beads - Polecats: 0, Crew: 1, Agents: [refinery mayor]
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Go not in PATH
|
||||
If `go` command fails, ensure Homebrew's bin is in your PATH:
|
||||
```bash
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
```
|
||||
|
||||
### Witnesses Not Detected
|
||||
Known issue (gm-2ej): The witness detection code checks for `witness/rig` but witnesses don't have a git clone. This is a bug in the detection logic - witnesses should still work.
|
||||
|
||||
### Beads Import Fails
|
||||
Known issue (gm-r6e): If your JSONL contains invalid issue types (e.g., "merge-request"), the import will fail. Either fix the JSONL or start with an empty database.
|
||||
|
||||
## Full Bootstrap Script
|
||||
|
||||
Here's a condensed script for bootstrapping:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
HQ_REPO="git@github.com:steveyegge/stevey-gt.git"
|
||||
GASTOWN_REPO="git@github.com:steveyegge/gastown.git"
|
||||
BEADS_REPO="git@github.com:steveyegge/beads.git"
|
||||
TOWN_ROOT="$HOME/gt"
|
||||
|
||||
# Clone HQ
|
||||
git clone "$HQ_REPO" "$TOWN_ROOT"
|
||||
cd "$TOWN_ROOT"
|
||||
|
||||
# Install Go if needed
|
||||
if ! command -v go &> /dev/null; then
|
||||
brew install go
|
||||
fi
|
||||
|
||||
# Clone and build gastown
|
||||
mkdir -p gastown/mayor
|
||||
git clone "$GASTOWN_REPO" gastown/mayor/rig
|
||||
cd gastown/mayor/rig
|
||||
go build -o gt ./cmd/gt
|
||||
cp gt ~/bin/gt
|
||||
cd "$TOWN_ROOT"
|
||||
|
||||
# Populate gastown rig
|
||||
cd gastown
|
||||
mkdir -p refinery witness polecats crew/main
|
||||
git clone "$GASTOWN_REPO" refinery/rig
|
||||
git clone "$GASTOWN_REPO" crew/main
|
||||
echo '{"role": "refinery"}' > refinery/state.json
|
||||
echo '{"role": "witness"}' > witness/state.json
|
||||
echo '{"role": "mayor"}' > mayor/state.json
|
||||
cd "$TOWN_ROOT"
|
||||
|
||||
# Populate beads rig
|
||||
cd beads
|
||||
mkdir -p refinery mayor witness polecats crew/main
|
||||
git clone "$BEADS_REPO" refinery/rig
|
||||
git clone "$BEADS_REPO" mayor/rig
|
||||
git clone "$BEADS_REPO" crew/main
|
||||
echo '{"role": "refinery"}' > refinery/state.json
|
||||
echo '{"role": "witness"}' > witness/state.json
|
||||
echo '{"role": "mayor"}' > mayor/state.json
|
||||
cd "$TOWN_ROOT"
|
||||
|
||||
# Initialize beads
|
||||
bd init --prefix gm
|
||||
|
||||
# Verify
|
||||
gt status
|
||||
echo "Bootstrap complete!"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
After bootstrapping:
|
||||
1. Start a Mayor session: `gt mayor attach`
|
||||
2. Check for work: `bd ready`
|
||||
3. Spawn workers with molecules: `gt sling <id> <rig> --molecule mol-shiny`
|
||||
@@ -1,156 +0,0 @@
|
||||
# Crew tmux Configuration
|
||||
|
||||
> **Status**: Personal Workflow
|
||||
> **Location**: Your personal dotfiles (e.g., `~/.emacs.d/tmux.conf`)
|
||||
|
||||
## Overview
|
||||
|
||||
Crew workers are persistent identities. Unlike polecats (ephemeral, witness-managed),
|
||||
crew members keep their names and workspaces across sessions. This makes them ideal
|
||||
candidates for hardwired tmux keybindings.
|
||||
|
||||
This document explains how to configure your personal tmux config to enable quick
|
||||
cycling between crew sessions within a rig.
|
||||
|
||||
## Why Personal Config?
|
||||
|
||||
Crew session linking is **personal workflow**, not core Gas Town infrastructure:
|
||||
|
||||
- Your crew members are stable identities you control
|
||||
- The groupings reflect how *you* want to work
|
||||
- Different users may have different crew setups
|
||||
- Keeps Gas Town codebase focused on agent mechanics
|
||||
|
||||
Store this in your personal dotfiles repo (e.g., `~/.emacs.d/`) for:
|
||||
- Version control
|
||||
- Sharing across machines
|
||||
- Separation from project code
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create tmux.conf in your dotfiles
|
||||
|
||||
```bash
|
||||
# Example: ~/.emacs.d/tmux.conf
|
||||
```
|
||||
|
||||
### 2. Symlink from home
|
||||
|
||||
```bash
|
||||
ln -s ~/.emacs.d/tmux.conf ~/.tmux.conf
|
||||
```
|
||||
|
||||
### 3. Configure crew cycling groups
|
||||
|
||||
The key insight: use `run-shell` with a case statement to route `C-b n`/`C-b p`
|
||||
based on the current session name.
|
||||
|
||||
```tmux
|
||||
# Crew session cycling - hardwired groups
|
||||
# Group 1: gastown crew (max <-> joe)
|
||||
# Group 2: beads crew (dave -> emma -> zoey -> dave)
|
||||
|
||||
bind n run-shell ' \
|
||||
s="#{session_name}"; \
|
||||
case "$s" in \
|
||||
gt-gastown-crew-max) tmux switch-client -t gt-gastown-crew-joe ;; \
|
||||
gt-gastown-crew-joe) tmux switch-client -t gt-gastown-crew-max ;; \
|
||||
gt-beads-crew-dave) tmux switch-client -t gt-beads-crew-emma ;; \
|
||||
gt-beads-crew-emma) tmux switch-client -t gt-beads-crew-zoey ;; \
|
||||
gt-beads-crew-zoey) tmux switch-client -t gt-beads-crew-dave ;; \
|
||||
*) tmux switch-client -n ;; \
|
||||
esac'
|
||||
|
||||
bind p run-shell ' \
|
||||
s="#{session_name}"; \
|
||||
case "$s" in \
|
||||
gt-gastown-crew-max) tmux switch-client -t gt-gastown-crew-joe ;; \
|
||||
gt-gastown-crew-joe) tmux switch-client -t gt-gastown-crew-max ;; \
|
||||
gt-beads-crew-dave) tmux switch-client -t gt-beads-crew-zoey ;; \
|
||||
gt-beads-crew-emma) tmux switch-client -t gt-beads-crew-dave ;; \
|
||||
gt-beads-crew-zoey) tmux switch-client -t gt-beads-crew-emma ;; \
|
||||
*) tmux switch-client -p ;; \
|
||||
esac'
|
||||
```
|
||||
|
||||
### 4. Reload config
|
||||
|
||||
```bash
|
||||
tmux source-file ~/.tmux.conf
|
||||
```
|
||||
|
||||
## Session Naming Convention
|
||||
|
||||
Gas Town uses predictable session names:
|
||||
|
||||
```
|
||||
gt-<rig>-crew-<name>
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `gt-gastown-crew-max`
|
||||
- `gt-gastown-crew-joe`
|
||||
- `gt-beads-crew-dave`
|
||||
- `gt-beads-crew-emma`
|
||||
- `gt-beads-crew-zoey`
|
||||
|
||||
This predictability enables hardwired keybindings.
|
||||
|
||||
## Adding New Crew Members
|
||||
|
||||
When you add a new crew member:
|
||||
|
||||
1. Add entries to both `bind n` and `bind p` case statements
|
||||
2. Maintain the cycle order (n goes forward, p goes backward)
|
||||
3. Reload config: `tmux source-file ~/.tmux.conf`
|
||||
|
||||
Example - adding `frank` to gastown crew:
|
||||
|
||||
```tmux
|
||||
# In bind n:
|
||||
gt-gastown-crew-max) tmux switch-client -t gt-gastown-crew-joe ;;
|
||||
gt-gastown-crew-joe) tmux switch-client -t gt-gastown-crew-frank ;;
|
||||
gt-gastown-crew-frank) tmux switch-client -t gt-gastown-crew-max ;;
|
||||
|
||||
# In bind p (reverse order):
|
||||
gt-gastown-crew-max) tmux switch-client -t gt-gastown-crew-frank ;;
|
||||
gt-gastown-crew-joe) tmux switch-client -t gt-gastown-crew-max ;;
|
||||
gt-gastown-crew-frank) tmux switch-client -t gt-gastown-crew-joe ;;
|
||||
```
|
||||
|
||||
## Fallback Behavior
|
||||
|
||||
The `*) tmux switch-client -n ;;` fallback means:
|
||||
- In a crew session → cycles within your group
|
||||
- In any other session (mayor, witness, refinery) → standard all-session cycling
|
||||
|
||||
This keeps the default behavior for non-crew contexts.
|
||||
|
||||
## Starting Crew Sessions
|
||||
|
||||
When starting crew sessions manually (not through Gas Town spawn), remember to
|
||||
configure the status line:
|
||||
|
||||
```bash
|
||||
# Start session
|
||||
tmux new-session -d -s gt-<rig>-crew-<name> -c /path/to/crew/<name>
|
||||
|
||||
# Configure status (Gas Town normally does this automatically)
|
||||
tmux set-option -t gt-<rig>-crew-<name> status-left-length 25
|
||||
tmux set-option -t gt-<rig>-crew-<name> status-left "👷 <rig>/crew/<name> "
|
||||
|
||||
# Start Claude
|
||||
tmux send-keys -t gt-<rig>-crew-<name> 'claude' Enter
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- **Two-member groups**: For pairs like max/joe, n and p do the same thing (toggle)
|
||||
- **Larger groups**: n cycles forward, p cycles backward
|
||||
- **Mixed rigs**: Each rig's crew is a separate group - no cross-rig cycling
|
||||
- **Testing**: Use `tmux display-message -p '#{session_name}'` to verify session names
|
||||
|
||||
## Related
|
||||
|
||||
- [session-lifecycle.md](session-lifecycle.md) - How sessions cycle
|
||||
- [propulsion-principle.md](propulsion-principle.md) - The "RUN IT" protocol
|
||||
@@ -1,248 +0,0 @@
|
||||
# Cross-Project Dependencies
|
||||
|
||||
> Design for tracking dependencies across project boundaries without coupling to
|
||||
> specific orchestrators.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
When working on Gas Town, we frequently hit dependencies on Beads features (and
|
||||
vice versa). Currently there's no formal mechanism to:
|
||||
|
||||
1. Declare "I need capability X from project Y"
|
||||
2. Signal "capability X is now available"
|
||||
3. Park work waiting on external dependencies
|
||||
4. Resume work when dependencies are satisfied
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Beads-native**: The core mechanism lives in Beads, not Gas Town
|
||||
2. **Orchestrator-agnostic**: Works with Gas Town, other orchestrators, or none
|
||||
3. **Capability-based**: Reference published capabilities, not internal issue IDs
|
||||
4. **Semaphore pattern**: Producer signals, consumers wait, no coordinator required
|
||||
|
||||
## The Mechanism
|
||||
|
||||
### Provider Side: Shipping Capabilities
|
||||
|
||||
Projects declare and ship capabilities using labels:
|
||||
|
||||
```bash
|
||||
# Declare intent (optional, for visibility)
|
||||
bd create --title="Add --assignee to mol run" \
|
||||
--add-label=export:mol-run-assignee
|
||||
|
||||
# ... do work, close issue ...
|
||||
|
||||
# Ship the capability (adds provides: label)
|
||||
bd ship mol-run-assignee
|
||||
```
|
||||
|
||||
The `bd ship` command:
|
||||
- Finds issue with `export:mol-run-assignee` label
|
||||
- Validates issue is closed (or `--force`)
|
||||
- Adds `provides:mol-run-assignee` label
|
||||
- Optionally notifies downstream (future)
|
||||
|
||||
**Protected namespace**: `provides:*` labels can only be added via `bd ship`.
|
||||
|
||||
### Consumer Side: Declaring Dependencies
|
||||
|
||||
Projects declare external dependencies on capabilities:
|
||||
|
||||
```bash
|
||||
# At creation
|
||||
bd create --title="Use --assignee in spawn.go" \
|
||||
--blocked-by="external:beads:mol-run-assignee"
|
||||
|
||||
# Or later
|
||||
bd update gt-xyz --blocked-by="external:beads:mol-run-assignee"
|
||||
```
|
||||
|
||||
The `external:project:capability` syntax:
|
||||
- `external:` - prefix indicating cross-project reference
|
||||
- `project` - name from `external_projects` config
|
||||
- `capability` - the capability name (matches `provides:X` label)
|
||||
|
||||
### Configuration
|
||||
|
||||
Projects configure paths to external projects:
|
||||
|
||||
```yaml
|
||||
# .beads/config.yaml
|
||||
external_projects:
|
||||
beads: ../beads
|
||||
gastown: ../gastown
|
||||
# Can also use absolute paths
|
||||
other: /Users/steve/projects/other
|
||||
```
|
||||
|
||||
### Resolution
|
||||
|
||||
`bd ready` checks external dependencies:
|
||||
|
||||
```bash
|
||||
bd ready
|
||||
# gt-xyz: blocked by external:beads:mol-run-assignee (not provided)
|
||||
# gt-abc: ready
|
||||
|
||||
# After beads ships the capability:
|
||||
bd ready
|
||||
# gt-xyz: ready
|
||||
# gt-abc: ready
|
||||
```
|
||||
|
||||
## Molecule Integration
|
||||
|
||||
### Parking a Molecule
|
||||
|
||||
When a polecat hits an external dependency mid-molecule:
|
||||
|
||||
```bash
|
||||
# Polecat discovers external dep not satisfied
|
||||
gt park --step=gt-mol.3 --waiting="beads:mol-run-assignee"
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Adds `blocked_by: external:beads:mol-run-assignee` to the step
|
||||
2. Clears assignee on the step and molecule root
|
||||
3. Sends handoff mail to self with context
|
||||
4. Shuts down the polecat
|
||||
|
||||
**"Parked" is a derived state**, not a new status:
|
||||
- Molecule status: `in_progress`
|
||||
- Molecule assignee: `null`
|
||||
- Has step with unsatisfied external `blocked_by`
|
||||
|
||||
### Querying Parked Work
|
||||
|
||||
```bash
|
||||
# Find parked molecules
|
||||
bd list --status=in_progress --no-assignee --type=molecule
|
||||
|
||||
# See what's blocked and on what
|
||||
bd list --has-external-block
|
||||
```
|
||||
|
||||
### Resuming Parked Work
|
||||
|
||||
**Manual (launch):**
|
||||
```bash
|
||||
gt sling gt-mol-root
|
||||
# Spawns polecat, which reads handoff mail and continues
|
||||
```
|
||||
|
||||
**Automated (future):**
|
||||
Deacon patrol checks parked molecules:
|
||||
```yaml
|
||||
- step: check-parked-molecules
|
||||
action: |
|
||||
For each molecule with status=in_progress, no assignee:
|
||||
Check if external deps are satisfied
|
||||
If yes: spawn polecat to resume
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Beads Core (bd-* issues)
|
||||
|
||||
1. **bd ship command**: Add `provides:` label, protect namespace
|
||||
2. **external: blocked_by**: Parse and store external references
|
||||
3. **external_projects config**: Add to config schema
|
||||
4. **bd ready resolution**: Check external deps via configured paths
|
||||
|
||||
### Phase 2: Gas Town Integration (gt-* issues)
|
||||
|
||||
1. **gt park command**: Set blocked_by, clear assignee, handoff, shutdown
|
||||
2. **gt sling**: Resume parked molecule
|
||||
3. **Patrol step**: Check parked molecules for unblocked
|
||||
|
||||
### Phase 3: Automation (future)
|
||||
|
||||
1. Push notifications on `bd ship`
|
||||
2. Auto-resume via patrol
|
||||
3. Cross-rig visibility in `gt status`
|
||||
|
||||
## Examples
|
||||
|
||||
### Full Flow: Adding --assignee to bd mol run
|
||||
|
||||
**In beads repo:**
|
||||
```bash
|
||||
# Dave creates the issue
|
||||
bd create --title="Add --assignee flag to bd mol run" \
|
||||
--type=feature \
|
||||
--add-label=export:mol-run-assignee
|
||||
|
||||
# Dave implements, tests, closes
|
||||
bd close bd-xyz
|
||||
|
||||
# Dave ships the capability
|
||||
bd ship mol-run-assignee
|
||||
# Output: Shipped mol-run-assignee (bd-xyz)
|
||||
```
|
||||
|
||||
**In gastown repo:**
|
||||
```bash
|
||||
# Earlier: Joe created dependent issue
|
||||
bd create --title="Use --assignee in spawn.go" \
|
||||
--blocked-by="external:beads:mol-run-assignee"
|
||||
|
||||
# bd ready showed it as blocked
|
||||
bd ready
|
||||
# gt-abc: blocked by external:beads:mol-run-assignee
|
||||
|
||||
# After Dave ships:
|
||||
bd ready
|
||||
# gt-abc: ready
|
||||
|
||||
# Joe picks it up
|
||||
bd update gt-abc --status=in_progress --assignee=gastown/joe
|
||||
```
|
||||
|
||||
### Parking Mid-Molecule
|
||||
|
||||
```bash
|
||||
# Polecat working on molecule, hits step 3
|
||||
# Step 3 needs beads:mol-run-assignee which isn't shipped
|
||||
|
||||
gt park --step=gt-mol.3 --waiting="beads:mol-run-assignee"
|
||||
# Setting blocked_by on gt-mol.3...
|
||||
# Clearing assignee on gt-mol.3...
|
||||
# Clearing assignee on gt-mol-root...
|
||||
# Sending handoff mail...
|
||||
# Polecat shutting down.
|
||||
|
||||
# Later, after beads ships:
|
||||
gt sling gt-mol-root
|
||||
# Resuming molecule gt-mol-root...
|
||||
# Reading handoff context...
|
||||
# Continuing from step gt-mol.3
|
||||
```
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Why capability labels, not issue references?
|
||||
|
||||
Referencing `beads:bd-xyz` couples consumer to producer's internal tracking.
|
||||
Referencing `beads:mol-run-assignee` couples to a published interface.
|
||||
|
||||
The producer can refactor, split, or reimplement bd-xyz without breaking consumers.
|
||||
|
||||
### Why "parked" as derived state?
|
||||
|
||||
Adding a new status creates migration burden and complicates the state machine.
|
||||
Deriving from existing fields (in_progress + no assignee + blocked) is simpler.
|
||||
|
||||
### Why handoff via mail?
|
||||
|
||||
Mail already handles context preservation. Parking is just a handoff to future-self
|
||||
(or future-polecat). No new mechanism needed.
|
||||
|
||||
### Why config-based project resolution?
|
||||
|
||||
Alternatives considered:
|
||||
- Git remote queries (complex, requires network)
|
||||
- Hardcoded paths (inflexible)
|
||||
- Central registry (single point of failure)
|
||||
|
||||
Config is simple, explicit, and works offline.
|
||||
@@ -1,289 +0,0 @@
|
||||
# Deacon Plugins
|
||||
|
||||
Town-level plugins that run during the Deacon's patrol loop.
|
||||
|
||||
## Overview
|
||||
|
||||
The Deacon's patrol includes a `plugin-run` step. Rather than hardcoding what
|
||||
runs, we use **directory-based discovery** at `~/gt/plugins/`. Each plugin
|
||||
directory contains enough information for the Deacon to decide whether to run
|
||||
it and what to do.
|
||||
|
||||
Plugins are not pre-validated by tooling. The Deacon reads each plugin's
|
||||
metadata and instructions, decides if the gate is open, and executes
|
||||
accordingly.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
~/gt/plugins/
|
||||
├── beads-cleanup/
|
||||
│ ├── plugin.md # Gate info + instructions (combined)
|
||||
│ └── state.json # Auto-managed runtime state
|
||||
├── health-report/
|
||||
│ ├── plugin.md
|
||||
│ └── state.json
|
||||
└── wisp-pressure/
|
||||
├── plugin.md
|
||||
└── state.json
|
||||
```
|
||||
|
||||
Each plugin is a directory. If the directory exists, the plugin exists.
|
||||
|
||||
## Plugin Metadata (plugin.md)
|
||||
|
||||
The `plugin.md` file contains YAML frontmatter for gate configuration,
|
||||
followed by markdown instructions for the Deacon.
|
||||
|
||||
```markdown
|
||||
---
|
||||
gate: cooldown
|
||||
interval: 24h
|
||||
parallel: true
|
||||
---
|
||||
|
||||
# Beads Cleanup
|
||||
|
||||
Clean up old wisps daily.
|
||||
|
||||
## Actions
|
||||
|
||||
1. Run `bd cleanup --wisp --force`
|
||||
2. Run `bd doctor` to verify health
|
||||
3. Log summary of cleaned items
|
||||
```
|
||||
|
||||
### Frontmatter Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `gate` | Yes | Gate type: `cooldown`, `cron`, `condition`, `event` |
|
||||
| `interval` | For cooldown | Duration: `1h`, `24h`, `5m`, etc. |
|
||||
| `schedule` | For cron | Cron expression: `0 9 * * *` |
|
||||
| `check` | For condition | Command that outputs a number |
|
||||
| `operator` | For condition | Comparison: `gt`, `lt`, `eq`, `ge`, `le` |
|
||||
| `threshold` | For condition | Numeric threshold |
|
||||
| `trigger` | For event | Event name: `startup`, `heartbeat` |
|
||||
| `cooldown` | Optional | Min time between runs (for condition gates) |
|
||||
| `parallel` | Optional | If true, can run concurrently with other plugins |
|
||||
|
||||
## Gate Types
|
||||
|
||||
### Cooldown
|
||||
|
||||
Run at most once per interval since last successful run.
|
||||
|
||||
```yaml
|
||||
---
|
||||
gate: cooldown
|
||||
interval: 24h
|
||||
---
|
||||
```
|
||||
|
||||
### Cron
|
||||
|
||||
Run on a schedule (standard cron syntax).
|
||||
|
||||
```yaml
|
||||
---
|
||||
gate: cron
|
||||
schedule: "0 9 * * *" # 9am daily
|
||||
---
|
||||
```
|
||||
|
||||
### Condition
|
||||
|
||||
Run when a command's output crosses a threshold.
|
||||
|
||||
```yaml
|
||||
---
|
||||
gate: condition
|
||||
check: "bd count --type=wisp"
|
||||
operator: gt
|
||||
threshold: 50
|
||||
cooldown: 30m # Don't spam even if condition stays true
|
||||
---
|
||||
```
|
||||
|
||||
The `check` command must output a single number. The Deacon compares it
|
||||
against `threshold` using `operator`.
|
||||
|
||||
### Event
|
||||
|
||||
Run in response to specific events.
|
||||
|
||||
```yaml
|
||||
---
|
||||
gate: event
|
||||
trigger: startup # Run once when daemon starts
|
||||
---
|
||||
```
|
||||
|
||||
Triggers: `startup`, `heartbeat` (every patrol), `mail` (when inbox has items).
|
||||
|
||||
## State Management
|
||||
|
||||
The Deacon maintains `state.json` in each plugin directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"lastRun": "2025-12-21T10:00:00Z",
|
||||
"lastResult": "success",
|
||||
"runCount": 42,
|
||||
"nextEligible": "2025-12-22T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
This is auto-managed. Plugins don't need to touch it.
|
||||
|
||||
## Parallel Execution
|
||||
|
||||
Plugins marked `parallel: true` can run concurrently. During `plugin-run`,
|
||||
the Deacon:
|
||||
|
||||
1. Scans `~/gt/plugins/` for plugins with open gates
|
||||
2. Groups them: parallel vs sequential
|
||||
3. Launches parallel plugins using Task tool subagents
|
||||
4. Runs sequential plugins one at a time
|
||||
5. Waits for all to complete before continuing patrol
|
||||
|
||||
Sequential plugins (default) run in directory order. Use sequential for
|
||||
plugins that modify shared state or have ordering dependencies.
|
||||
|
||||
## Example Plugins
|
||||
|
||||
### beads-cleanup (daily maintenance)
|
||||
|
||||
```markdown
|
||||
---
|
||||
gate: cooldown
|
||||
interval: 24h
|
||||
parallel: true
|
||||
---
|
||||
|
||||
# Beads Cleanup
|
||||
|
||||
Daily cleanup of wisp storage.
|
||||
|
||||
## Actions
|
||||
|
||||
1. Run `bd cleanup --wisp --force` to remove old wisps
|
||||
2. Run `bd doctor` to check for issues
|
||||
3. Report count of cleaned items
|
||||
```
|
||||
|
||||
### wisp-pressure (condition-triggered)
|
||||
|
||||
```markdown
|
||||
---
|
||||
gate: condition
|
||||
check: "bd count --type=wisp"
|
||||
operator: gt
|
||||
threshold: 100
|
||||
cooldown: 1h
|
||||
parallel: true
|
||||
---
|
||||
|
||||
# Wisp Pressure Relief
|
||||
|
||||
Triggered when wisp count gets too high.
|
||||
|
||||
## Actions
|
||||
|
||||
1. Run `bd cleanup --wisp --age=4h` (remove wisps > 4h old)
|
||||
2. Check `bd count --type=wisp`
|
||||
3. If still > 50, run `bd cleanup --wisp --age=1h`
|
||||
4. Report final count
|
||||
```
|
||||
|
||||
### health-report (scheduled)
|
||||
|
||||
```markdown
|
||||
---
|
||||
gate: cron
|
||||
schedule: "0 9 * * *"
|
||||
parallel: false
|
||||
---
|
||||
|
||||
# Morning Health Report
|
||||
|
||||
Generate daily health summary for the overseer.
|
||||
|
||||
## Actions
|
||||
|
||||
1. Run `gt status` to get overall health
|
||||
2. Run `bd stats` to get metrics
|
||||
3. Check for stale agents (no heartbeat > 1h)
|
||||
4. Send summary: `gt mail send --human -s "Morning Report" -m "..."`
|
||||
```
|
||||
|
||||
### startup-check (event-triggered)
|
||||
|
||||
```markdown
|
||||
---
|
||||
gate: event
|
||||
trigger: startup
|
||||
parallel: false
|
||||
---
|
||||
|
||||
# Startup Verification
|
||||
|
||||
Run once when the daemon starts.
|
||||
|
||||
## Actions
|
||||
|
||||
1. Verify `gt doctor` passes
|
||||
2. Check all rigs have healthy Witnesses
|
||||
3. Report any issues to Mayor inbox
|
||||
```
|
||||
|
||||
## Patrol Integration
|
||||
|
||||
The `mol-deacon-patrol` step 3 (plugin-run) works as follows:
|
||||
|
||||
```markdown
|
||||
## Step: plugin-run
|
||||
|
||||
Execute registered plugins whose gates are open.
|
||||
|
||||
1. Scan `~/gt/plugins/` for plugin directories
|
||||
2. For each plugin, read `plugin.md` frontmatter
|
||||
3. Check if gate is open (based on type and state.json)
|
||||
4. Collect eligible plugins into parallel and sequential groups
|
||||
|
||||
5. For parallel plugins:
|
||||
- Use Task tool to spawn subagents for each
|
||||
- Each subagent reads the plugin's instructions and executes
|
||||
- Wait for all to complete
|
||||
|
||||
6. For sequential plugins:
|
||||
- Execute each in order
|
||||
- Read instructions, run actions, update state
|
||||
|
||||
7. Update state.json for each completed plugin
|
||||
8. Log summary: "Ran N plugins (M parallel, K sequential)"
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- **No polecat spawning**: Plugins cannot spawn polecats. If a plugin tries
|
||||
to use `gt sling`, behavior is undefined. This may change in the future.
|
||||
|
||||
- **No cross-plugin dependencies**: Plugins don't declare dependencies on
|
||||
each other. If ordering matters, mark both as `parallel: false`.
|
||||
|
||||
- **No plugin-specific state API**: Plugins can write to their own directory
|
||||
if needed, but there's no structured API for plugin state beyond what the
|
||||
Deacon auto-manages.
|
||||
|
||||
## CLI Commands (Future)
|
||||
|
||||
```bash
|
||||
gt plugins list # Show all plugins, gates, status
|
||||
gt plugins check # Show plugins with open gates
|
||||
gt plugins show <name> # Detail view of one plugin
|
||||
gt plugins run <name> # Force-run (ignore gate)
|
||||
```
|
||||
|
||||
These commands are not yet implemented. The Deacon reads the directory
|
||||
structure directly during patrol.
|
||||
@@ -1,217 +0,0 @@
|
||||
# Gas Town Account Management Design
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Claude Code users with multiple accounts (e.g., personal, work, team) need to:
|
||||
1. Switch between accounts easily when quotas are exhausted
|
||||
2. Set a global default account for all agents
|
||||
3. Override per-spawn or per-role as needed
|
||||
|
||||
## Current State
|
||||
|
||||
- Claude Code stores config in `~/.claude/` and `~/.claude.json`
|
||||
- Auth tokens stored in system keychain
|
||||
- `CLAUDE_CONFIG_DIR` env var controls config location
|
||||
- `/login` command switches accounts but overwrites in-place
|
||||
- No built-in profile/multi-account support
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Core Mechanism
|
||||
|
||||
Each registered account gets its own config directory:
|
||||
|
||||
```
|
||||
~/.claude-accounts/
|
||||
├── yegge/ # steve.yegge@gmail.com
|
||||
│ ├── .claude/ # Full Claude Code config
|
||||
│ └── .claude.json # Root config file
|
||||
├── ghosttrack/ # steve@ghosttrack.com
|
||||
│ ├── .claude/
|
||||
│ └── .claude.json
|
||||
└── default -> ghosttrack/ # Symlink to current default
|
||||
```
|
||||
|
||||
### Town Configuration
|
||||
|
||||
File: `~/gt/mayor/accounts.json`
|
||||
|
||||
This follows the existing pattern where town-level config lives in `mayor/`.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"accounts": {
|
||||
"yegge": {
|
||||
"email": "steve.yegge@gmail.com",
|
||||
"description": "Personal/Gmail account",
|
||||
"config_dir": "~/.claude-accounts/yegge"
|
||||
},
|
||||
"ghosttrack": {
|
||||
"email": "steve@ghosttrack.com",
|
||||
"description": "Ghost Track business account",
|
||||
"config_dir": "~/.claude-accounts/ghosttrack"
|
||||
}
|
||||
},
|
||||
"default": "ghosttrack"
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variable: GT_ACCOUNT
|
||||
|
||||
Highest priority override. Set this to use a specific account:
|
||||
|
||||
```bash
|
||||
export GT_ACCOUNT=yegge
|
||||
gt sling <bead> gastown # Uses yegge account
|
||||
```
|
||||
|
||||
### Command Interface
|
||||
|
||||
#### Account Management
|
||||
|
||||
```bash
|
||||
# List registered accounts
|
||||
gt account list
|
||||
# Output:
|
||||
# yegge steve.yegge@gmail.com (personal)
|
||||
# * ghosttrack steve@ghosttrack.com (default)
|
||||
|
||||
# Add new account (creates config dir, prompts login)
|
||||
gt account add <handle> [email]
|
||||
gt account add work steve@company.com
|
||||
|
||||
# Set default
|
||||
gt account default <handle>
|
||||
gt account default ghosttrack
|
||||
|
||||
# Remove account (keeps config dir by default)
|
||||
gt account remove <handle>
|
||||
gt account remove yegge --delete-config
|
||||
|
||||
# Check current/status
|
||||
gt account status
|
||||
# Output:
|
||||
# Current: ghosttrack (steve@ghosttrack.com)
|
||||
# GT_ACCOUNT env: not set
|
||||
```
|
||||
|
||||
#### Spawn/Attach with Account Override
|
||||
|
||||
```bash
|
||||
# Override for a specific spawn
|
||||
gt sling <bead> gastown --account=yegge
|
||||
|
||||
# Override for crew attach
|
||||
gt crew at --account=ghosttrack max
|
||||
|
||||
# With env var (highest precedence)
|
||||
GT_ACCOUNT=yegge gt sling <bead> gastown
|
||||
```
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### Account Resolution Order
|
||||
|
||||
1. `GT_ACCOUNT` environment variable (highest)
|
||||
2. `--account` flag on command
|
||||
3. `default` in accounts.yaml (lowest)
|
||||
|
||||
#### How Spawning Works
|
||||
|
||||
When `gt sling` or `gt crew at` runs Claude Code:
|
||||
|
||||
```go
|
||||
func resolveAccountConfigDir() string {
|
||||
// Check env var first
|
||||
if handle := os.Getenv("GT_ACCOUNT"); handle != "" {
|
||||
return getAccountConfigDir(handle)
|
||||
}
|
||||
|
||||
// Check flag
|
||||
if handle := flags.Account; handle != "" {
|
||||
return getAccountConfigDir(handle)
|
||||
}
|
||||
|
||||
// Use default from config
|
||||
return getAccountConfigDir(config.Default)
|
||||
}
|
||||
|
||||
func spawnClaudeCode(workdir string, account string) {
|
||||
configDir := resolveAccountConfigDir()
|
||||
|
||||
cmd := exec.Command("claude", args...)
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("CLAUDE_CONFIG_DIR=%s", configDir),
|
||||
)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Account Login Flow
|
||||
|
||||
```bash
|
||||
gt account add ghosttrack steve@ghosttrack.com
|
||||
```
|
||||
|
||||
1. Creates `~/.claude-accounts/ghosttrack/`
|
||||
2. Sets `CLAUDE_CONFIG_DIR` and runs `claude`
|
||||
3. User completes `/login` with their account
|
||||
4. Adds entry to `accounts.yaml`
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- **No secrets in accounts.yaml** - Only handles and email addresses
|
||||
- **Auth tokens in keychain** - Claude Code handles this per-config-dir
|
||||
- **Config dir permissions** - Should be user-readable only
|
||||
|
||||
### Future Extensions
|
||||
|
||||
1. **Usage tracking** - `gt account status --usage` to show quota info
|
||||
2. **Auto-switching** - When one account hits limits, prompt to switch
|
||||
3. **Per-role defaults** - Different accounts for different roles:
|
||||
```yaml
|
||||
role_defaults:
|
||||
witness: yegge # Long-running patrol uses less quota
|
||||
refinery: ghosttrack
|
||||
```
|
||||
|
||||
4. **API key accounts** - For when we support direct API access:
|
||||
```yaml
|
||||
accounts:
|
||||
api-team:
|
||||
type: api_key
|
||||
key_ref: GT_API_KEY # Env var containing key
|
||||
```
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Immediate (Manual)
|
||||
|
||||
Users can start using separate config dirs today:
|
||||
|
||||
```bash
|
||||
# Set up account directories
|
||||
mkdir -p ~/.claude-accounts/ghosttrack
|
||||
export CLAUDE_CONFIG_DIR=~/.claude-accounts/ghosttrack
|
||||
claude # Login as ghosttrack
|
||||
```
|
||||
|
||||
### Phase 1: Basic Support
|
||||
|
||||
- Add `accounts.json` parsing
|
||||
- Add `gt account` subcommands
|
||||
- Wire up `GT_ACCOUNT` env var in spawn
|
||||
|
||||
### Phase 2: Full Integration
|
||||
|
||||
- Add `--account` flags to all relevant commands
|
||||
- Add status/usage tracking
|
||||
- Add per-role defaults
|
||||
|
||||
## Testing Plan
|
||||
|
||||
1. Create test accounts config
|
||||
2. Verify spawn uses correct config dir
|
||||
3. Test override precedence
|
||||
4. Test `gt account add` login flow
|
||||
@@ -1,472 +0,0 @@
|
||||
# Design: Tmux Status Bar Theming (gt-vc1n)
|
||||
|
||||
## Problem
|
||||
|
||||
All Gas Town tmux sessions look identical:
|
||||
- Same green/black status bars everywhere
|
||||
- Hard to tell which rig you're in at a glance
|
||||
- Session names get truncated (only 10 chars visible)
|
||||
- No visual indication of worker role (polecat vs crew vs mayor)
|
||||
|
||||
Current state:
|
||||
```
|
||||
[gt-gastown] 0:zsh* "pane_title" 14:30 19-Dec
|
||||
[gt-gastown] 0:zsh* "pane_title" 14:30 19-Dec <- which worker?
|
||||
[gt-mayor] 0:zsh* "pane_title" 14:30 19-Dec
|
||||
```
|
||||
|
||||
## Solution
|
||||
|
||||
Per-rig color themes applied when tmux sessions are created, with optional user customization.
|
||||
|
||||
### Goals
|
||||
1. Each rig has a distinct color theme
|
||||
2. Colors are automatically assigned from a predefined palette
|
||||
3. Users can override colors per-rig
|
||||
4. Status bar shows useful context (rig, worker, role)
|
||||
|
||||
## Design
|
||||
|
||||
### 1. Color Palette
|
||||
|
||||
A curated palette of distinct, visually appealing color pairs (bg/fg):
|
||||
|
||||
```go
|
||||
// internal/tmux/theme.go
|
||||
var DefaultPalette = []Theme{
|
||||
{Name: "ocean", BG: "#1e3a5f", FG: "#e0e0e0"}, // Deep blue
|
||||
{Name: "forest", BG: "#2d5a3d", FG: "#e0e0e0"}, // Forest green
|
||||
{Name: "rust", BG: "#8b4513", FG: "#f5f5dc"}, // Rust/brown
|
||||
{Name: "plum", BG: "#4a3050", FG: "#e0e0e0"}, // Purple
|
||||
{Name: "slate", BG: "#4a5568", FG: "#e0e0e0"}, // Slate gray
|
||||
{Name: "ember", BG: "#b33a00", FG: "#f5f5dc"}, // Burnt orange
|
||||
{Name: "midnight", BG: "#1a1a2e", FG: "#c0c0c0"}, // Dark blue-black
|
||||
{Name: "wine", BG: "#722f37", FG: "#f5f5dc"}, // Burgundy
|
||||
{Name: "teal", BG: "#0d5c63", FG: "#e0e0e0"}, // Teal
|
||||
{Name: "copper", BG: "#6d4c41", FG: "#f5f5dc"}, // Warm brown
|
||||
}
|
||||
```
|
||||
|
||||
Palette criteria:
|
||||
- Distinct from each other (no two look alike)
|
||||
- Readable (sufficient contrast)
|
||||
- Professional (no neon/garish colors)
|
||||
- Dark backgrounds (easier on eyes in terminals)
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
#### Per-Rig Config Extension
|
||||
|
||||
Extend `RigConfig` in `internal/config/types.go`:
|
||||
|
||||
```go
|
||||
type RigConfig struct {
|
||||
Type string `json:"type"`
|
||||
Version int `json:"version"`
|
||||
MergeQueue *MergeQueueConfig `json:"merge_queue,omitempty"`
|
||||
Theme *ThemeConfig `json:"theme,omitempty"` // NEW
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
// Name picks from palette (e.g., "ocean", "forest")
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Custom overrides the palette with specific colors
|
||||
Custom *CustomTheme `json:"custom,omitempty"`
|
||||
}
|
||||
|
||||
type CustomTheme struct {
|
||||
BG string `json:"bg"` // hex color or tmux color name
|
||||
FG string `json:"fg"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Town-Level Config (optional)
|
||||
|
||||
Allow global palette override in `mayor/town.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": {
|
||||
"palette": ["ocean", "forest", "rust", "plum"],
|
||||
"mayor_theme": "midnight"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Theme Assignment
|
||||
|
||||
When a rig is added (or first session created), auto-assign a theme:
|
||||
|
||||
```go
|
||||
// internal/tmux/theme.go
|
||||
|
||||
// AssignTheme picks a theme for a rig based on its name.
|
||||
// Uses consistent hashing so the same rig always gets the same color.
|
||||
func AssignTheme(rigName string, palette []Theme) Theme {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(rigName))
|
||||
idx := int(h.Sum32()) % len(palette)
|
||||
return palette[idx]
|
||||
}
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Same rig always gets same color (deterministic)
|
||||
- Different rigs get different colors (distributed)
|
||||
- No persistent state needed for assignment
|
||||
|
||||
### 4. Session Creation Changes
|
||||
|
||||
Modify `tmux.NewSession` to accept optional theming:
|
||||
|
||||
```go
|
||||
// SessionOptions configures session creation.
|
||||
type SessionOptions struct {
|
||||
WorkDir string
|
||||
Theme *Theme // nil = use default
|
||||
}
|
||||
|
||||
// NewSessionWithOptions creates a session with theming.
|
||||
func (t *Tmux) NewSessionWithOptions(name string, opts SessionOptions) error {
|
||||
args := []string{"new-session", "-d", "-s", name}
|
||||
if opts.WorkDir != "" {
|
||||
args = append(args, "-c", opts.WorkDir)
|
||||
}
|
||||
|
||||
if _, err := t.run(args...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply theme
|
||||
if opts.Theme != nil {
|
||||
t.ApplyTheme(name, *opts.Theme)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyTheme sets the status bar style for a session.
|
||||
func (t *Tmux) ApplyTheme(session string, theme Theme) error {
|
||||
style := fmt.Sprintf("bg=%s,fg=%s", theme.BG, theme.FG)
|
||||
_, err := t.run("set-option", "-t", session, "status-style", style)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Status Line Format
|
||||
|
||||
#### Static Identity (Left)
|
||||
|
||||
```go
|
||||
// SetStatusFormat configures the status line for Gas Town sessions.
|
||||
func (t *Tmux) SetStatusFormat(session, rig, worker, role string) error {
|
||||
// Format: [gastown/Rictus] polecat
|
||||
left := fmt.Sprintf("[%s/%s] %s ", rig, worker, role)
|
||||
|
||||
if _, err := t.run("set-option", "-t", session, "status-left-length", "40"); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.run("set-option", "-t", session, "status-left", left)
|
||||
}
|
||||
```
|
||||
|
||||
#### Dynamic Context (Right)
|
||||
|
||||
The right side shows dynamic info that agents can update:
|
||||
|
||||
```
|
||||
gt-70b3 | 📬 2 | 14:30
|
||||
```
|
||||
|
||||
Components:
|
||||
- **Current issue** - what the agent is working on
|
||||
- **Mail indicator** - unread mail count (hidden if 0)
|
||||
- **Time** - simple clock
|
||||
|
||||
Implementation via tmux environment variables + shell expansion:
|
||||
|
||||
```go
|
||||
// SetDynamicStatus configures the right side with dynamic content.
|
||||
func (t *Tmux) SetDynamicStatus(session string) error {
|
||||
// Use a shell command that reads from env vars we set
|
||||
// Agents update GT_ISSUE, we poll mail count
|
||||
//
|
||||
// Format: #{GT_ISSUE} | 📬 #{mail_count} | %H:%M
|
||||
//
|
||||
// tmux can run shell commands in status-right with #()
|
||||
right := `#(gt status-line --session=` + session + `) %H:%M`
|
||||
|
||||
if _, err := t.run("set-option", "-t", session, "status-right-length", "50"); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.run("set-option", "-t", session, "status-right", right)
|
||||
}
|
||||
```
|
||||
|
||||
#### `gt status-line` Command
|
||||
|
||||
A fast command for tmux to call every few seconds:
|
||||
|
||||
```go
|
||||
// cmd/statusline.go
|
||||
func runStatusLine(cmd *cobra.Command, args []string) error {
|
||||
session := cmd.Flag("session").Value.String()
|
||||
|
||||
// Get current issue from tmux env
|
||||
issue, _ := tmux.GetEnvironment(session, "GT_ISSUE")
|
||||
|
||||
// Get mail count (fast - just counts files or queries beads)
|
||||
mailCount := mail.UnreadCount(identity)
|
||||
|
||||
// Build output
|
||||
var parts []string
|
||||
if issue != "" {
|
||||
parts = append(parts, issue)
|
||||
}
|
||||
if mailCount > 0 {
|
||||
parts = append(parts, fmt.Sprintf("📬 %d", mailCount))
|
||||
}
|
||||
|
||||
fmt.Print(strings.Join(parts, " | "))
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Agent Updates Issue
|
||||
|
||||
Agents call this when starting/finishing work:
|
||||
|
||||
```bash
|
||||
# When starting work on an issue
|
||||
gt issue set gt-70b3
|
||||
|
||||
# When done
|
||||
gt issue clear
|
||||
```
|
||||
|
||||
Implementation:
|
||||
|
||||
```go
|
||||
// cmd/issue.go
|
||||
func runIssueSet(cmd *cobra.Command, args []string) error {
|
||||
issueID := args[0]
|
||||
session := os.Getenv("TMUX_PANE") // or detect from GT_* vars
|
||||
|
||||
return tmux.SetEnvironment(session, "GT_ISSUE", issueID)
|
||||
}
|
||||
```
|
||||
|
||||
#### Mayor-Specific Status
|
||||
|
||||
Mayor gets a different right-side format:
|
||||
|
||||
```
|
||||
5 polecats | 2 rigs | 📬 1 | 14:30
|
||||
```
|
||||
|
||||
```go
|
||||
func runMayorStatusLine() {
|
||||
polecats := countActivePolecats()
|
||||
rigs := countActiveRigs()
|
||||
mail := mail.UnreadCount("mayor/")
|
||||
|
||||
var parts []string
|
||||
parts = append(parts, fmt.Sprintf("%d polecats", polecats))
|
||||
parts = append(parts, fmt.Sprintf("%d rigs", rigs))
|
||||
if mail > 0 {
|
||||
parts = append(parts, fmt.Sprintf("📬 %d", mail))
|
||||
}
|
||||
fmt.Print(strings.Join(parts, " | "))
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Status Bars
|
||||
|
||||
**Polecat working on issue:**
|
||||
```
|
||||
[gastown/Rictus] polecat gt-70b3 | 📬 1 | 14:30
|
||||
```
|
||||
|
||||
**Crew worker, no mail:**
|
||||
```
|
||||
[gastown/max] crew gt-vc1n | 14:30
|
||||
```
|
||||
|
||||
**Mayor overview:**
|
||||
```
|
||||
[Mayor] coordinator 5 polecats | 2 rigs | 📬 2 | 14:30
|
||||
```
|
||||
|
||||
**Idle polecat:**
|
||||
```
|
||||
[gastown/Wez] polecat | 14:30
|
||||
```
|
||||
|
||||
### 6. Integration Points
|
||||
|
||||
#### Session Manager (session/manager.go)
|
||||
|
||||
```go
|
||||
func (m *Manager) Start(polecat string, opts StartOptions) error {
|
||||
// ... existing code ...
|
||||
|
||||
// Get theme from rig config
|
||||
theme := m.getTheme()
|
||||
|
||||
// Create session with theme
|
||||
if err := m.tmux.NewSessionWithOptions(sessionID, tmux.SessionOptions{
|
||||
WorkDir: workDir,
|
||||
Theme: theme,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set status format
|
||||
m.tmux.SetStatusFormat(sessionID, m.rig.Name, polecat, "polecat")
|
||||
|
||||
// ... rest of existing code ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Mayor (cmd/mayor.go)
|
||||
|
||||
```go
|
||||
func runMayorStart(cmd *cobra.Command, args []string) error {
|
||||
// ... existing code ...
|
||||
|
||||
// Mayor uses a special theme
|
||||
theme := tmux.MayorTheme() // Gold/dark - distinguished
|
||||
|
||||
if err := t.NewSessionWithOptions(MayorSessionName, tmux.SessionOptions{
|
||||
WorkDir: townRoot,
|
||||
Theme: &theme,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
t.SetStatusFormat(MayorSessionName, "town", "mayor", "coordinator")
|
||||
|
||||
// ... rest ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Crew (cmd/crew.go)
|
||||
|
||||
Similar pattern - get rig theme and apply.
|
||||
|
||||
### 7. Commands
|
||||
|
||||
#### `gt theme` - View/Set Themes
|
||||
|
||||
```bash
|
||||
# View current rig theme
|
||||
gt theme
|
||||
# Theme: ocean (bg=#1e3a5f, fg=#e0e0e0)
|
||||
|
||||
# View available themes
|
||||
gt theme --list
|
||||
# ocean, forest, rust, plum, slate, ember, midnight, wine, teal, copper
|
||||
|
||||
# Set theme for current rig
|
||||
gt theme set forest
|
||||
|
||||
# Set custom colors
|
||||
gt theme set --bg="#2d5a3d" --fg="#e0e0e0"
|
||||
```
|
||||
|
||||
#### `gt theme apply` - Apply to Running Sessions
|
||||
|
||||
```bash
|
||||
# Re-apply theme to all running sessions in this rig
|
||||
gt theme apply
|
||||
```
|
||||
|
||||
### 8. Backward Compatibility
|
||||
|
||||
- Existing sessions without themes continue to work (they'll just have default green)
|
||||
- New sessions get themed automatically
|
||||
- Users can run `gt theme apply` to update running sessions
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
1. Add Theme types to `internal/tmux/theme.go`
|
||||
2. Add ThemeConfig to `internal/config/types.go`
|
||||
3. Implement `AssignTheme()` function
|
||||
4. Add `ApplyTheme()` to Tmux wrapper
|
||||
|
||||
### Phase 2: Session Integration
|
||||
5. Modify `NewSession` to accept SessionOptions
|
||||
6. Update session.Manager.Start() to apply themes
|
||||
7. Update cmd/mayor.go to theme Mayor session
|
||||
8. Update cmd/crew.go to theme crew sessions
|
||||
|
||||
### Phase 3: Static Status Line
|
||||
9. Implement SetStatusFormat() for left side
|
||||
10. Apply to all session creation points
|
||||
11. Update witness.go, spawn.go, refinery, daemon
|
||||
|
||||
### Phase 4: Dynamic Status Line
|
||||
12. Add `gt status-line` command (fast, tmux-callable)
|
||||
13. Implement mail count lookup (fast path)
|
||||
14. Implement `gt issue set/clear` for agents to update current issue
|
||||
15. Configure status-right to call `gt status-line`
|
||||
16. Add Mayor-specific status line variant
|
||||
|
||||
### Phase 5: Commands & Polish
|
||||
17. Add `gt theme` command (view/set/apply)
|
||||
18. Add config file support for custom themes
|
||||
19. Documentation
|
||||
20. Update CLAUDE.md with `gt issue set` guidance for agents
|
||||
|
||||
## File Changes
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `internal/tmux/theme.go` | NEW - Theme types, palette, assignment |
|
||||
| `internal/tmux/tmux.go` | Add ApplyTheme, SetStatusFormat, SetDynamicStatus |
|
||||
| `internal/config/types.go` | Add ThemeConfig |
|
||||
| `internal/session/manager.go` | Use themed session creation |
|
||||
| `internal/cmd/mayor.go` | Apply Mayor theme + Mayor status format |
|
||||
| `internal/cmd/crew.go` | Apply rig theme to crew sessions |
|
||||
| `internal/cmd/witness.go` | Apply rig theme |
|
||||
| `internal/cmd/spawn.go` | Apply rig theme |
|
||||
| `internal/cmd/theme.go` | NEW - gt theme command |
|
||||
| `internal/cmd/statusline.go` | NEW - gt status-line (tmux-callable) |
|
||||
| `internal/cmd/issue.go` | NEW - gt issue set/clear |
|
||||
| `internal/daemon/lifecycle.go` | Apply rig theme |
|
||||
| `internal/refinery/manager.go` | Apply rig theme |
|
||||
| `CLAUDE.md` (various) | Document `gt issue set` for agents |
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. ~~**Should refinery/witness have distinct colors?**~~ **RESOLVED**
|
||||
- Answer: Same as rig polecats, role shown in status-left
|
||||
|
||||
2. **Color storage location?**
|
||||
- Option A: In rig config.json (requires file write)
|
||||
- Option B: In beads (config-as-data approach from gt-vc1n)
|
||||
- Recommendation: Start with config.json for simplicity
|
||||
|
||||
3. **Hex colors vs tmux color names?**
|
||||
- Hex: More precise, but some terminals don't support
|
||||
- Names: Limited palette, but universal support
|
||||
- Recommendation: Support both, default to hex with true-color fallback
|
||||
|
||||
4. **Status-line refresh frequency?**
|
||||
- tmux calls `#()` commands every `status-interval` seconds (default 15)
|
||||
- Trade-off: Faster = more responsive, but more CPU
|
||||
- Recommendation: 5 seconds (`set -g status-interval 5`)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Each rig has distinct status bar color
|
||||
- [ ] Users can identify rig at a glance
|
||||
- [ ] Status bar shows rig/worker/role clearly (left side)
|
||||
- [ ] Current issue displayed when agent sets it
|
||||
- [ ] Mail indicator shows unread count
|
||||
- [ ] Mayor shows aggregate stats (polecats, rigs)
|
||||
- [ ] Custom colors configurable per-rig
|
||||
- [ ] Works with existing sessions after `gt theme apply`
|
||||
- [ ] Agents can update issue via `gt issue set`
|
||||
@@ -1,472 +0,0 @@
|
||||
# Federation Architecture: Ultrathink
|
||||
|
||||
## The Problem
|
||||
|
||||
Gas Town needs to scale beyond a single machine:
|
||||
- More workers than one machine can handle (RAM, CPU, context windows)
|
||||
- Geographic distribution (workers close to data/services)
|
||||
- Cost efficiency (pay-per-use vs always-on VMs)
|
||||
- Platform flexibility (support various deployment targets)
|
||||
|
||||
## Two Deployment Models
|
||||
|
||||
### Model A: "Town Clone" (VMs)
|
||||
|
||||
Clone the entire `~/gt` workspace to a remote VM. It runs like a regular Gas Town:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GCE VM (or any Linux box) │
|
||||
│ │
|
||||
│ ~/gt/ # Full town clone │
|
||||
│ ├── config/ # Town config │
|
||||
│ ├── mayor/ # Mayor (or none) │
|
||||
│ ├── gastown/ # Rig with agents │
|
||||
│ │ ├── polecats/ # Workers here │
|
||||
│ │ ├── refinery/ │
|
||||
│ │ └── witness/ │
|
||||
│ └── beads/ # Another rig │
|
||||
│ │
|
||||
│ Runs autonomously, syncs via git │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- Full autonomy if disconnected
|
||||
- Familiar model - it's just another Gas Town
|
||||
- VM overhead (cost, management, always-on)
|
||||
- Coarse-grained scaling (spin up whole VMs)
|
||||
- Good for: always-on capacity, long-running work, full independence
|
||||
|
||||
**Federation via:**
|
||||
- Git sync for beads (already works)
|
||||
- Extended mail routing (`vm1:gastown/polecat`)
|
||||
- SSH for remote commands
|
||||
|
||||
### Model B: "Cloud Run Workers" (Containers)
|
||||
|
||||
Workers are stateless containers that wake on demand:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Cloud Run Service: gastown-worker │
|
||||
│ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ Container Instance │ │
|
||||
│ │ - Claude Code + git │ │
|
||||
│ │ - HTTP endpoint for work │ │
|
||||
│ │ - Persistent volume mount │ │
|
||||
│ │ - Scales 0→N automatically │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Zero cost when idle │
|
||||
│ Persistent connections keep warm │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Characteristics:**
|
||||
- Pay-per-use (nearly free when idle)
|
||||
- Scales elastically (0 to many workers)
|
||||
- No VM management
|
||||
- Stateless(ish) - needs fast bootstrap or persistent storage
|
||||
- Good for: burst capacity, background work, elastic scaling
|
||||
|
||||
**Key insight from your friend:**
|
||||
Persistent connections solve the "zero to one" problem. Keep the connection open, container stays warm, subsequent requests are fast. This transforms Cloud Run from "cold functions" to "elastic workers."
|
||||
|
||||
## Unified Abstraction: Outposts
|
||||
|
||||
To support "however people want to do it," we need an abstraction that covers both models (and future ones like K8s, bare metal, etc.).
|
||||
|
||||
### The Outpost Concept
|
||||
|
||||
An **Outpost** is a remote compute environment that can run workers.
|
||||
|
||||
```go
|
||||
type Outpost interface {
|
||||
// Identity
|
||||
Name() string
|
||||
Type() OutpostType // local, ssh, cloudrun, k8s
|
||||
|
||||
// Capacity
|
||||
MaxWorkers() int
|
||||
ActiveWorkers() int
|
||||
|
||||
// Worker lifecycle
|
||||
Spawn(issue string, config WorkerConfig) (Worker, error)
|
||||
Workers() []Worker
|
||||
|
||||
// Health
|
||||
Ping() error
|
||||
|
||||
// Optional: Direct communication (VM outposts)
|
||||
SendMail(worker string, msg Message) error
|
||||
}
|
||||
|
||||
type OutpostType string
|
||||
const (
|
||||
OutpostLocal OutpostType = "local"
|
||||
OutpostSSH OutpostType = "ssh" // Full VM clone
|
||||
OutpostCloudRun OutpostType = "cloudrun" // Container workers
|
||||
OutpostK8s OutpostType = "k8s" // Future
|
||||
)
|
||||
```
|
||||
|
||||
### Worker Interface
|
||||
|
||||
```go
|
||||
type Worker interface {
|
||||
ID() string
|
||||
Outpost() string
|
||||
Status() WorkerStatus // idle, working, done, failed
|
||||
Issue() string // Current issue being worked
|
||||
|
||||
// For interactive outposts (local, SSH)
|
||||
Attach() error // Connect to worker session
|
||||
|
||||
// For all outposts
|
||||
Logs() (io.Reader, error)
|
||||
Stop() error
|
||||
}
|
||||
```
|
||||
|
||||
### Outpost Implementations
|
||||
|
||||
#### LocalOutpost
|
||||
- Current model: tmux panes on localhost
|
||||
- Uses existing Connection interface (LocalConnection)
|
||||
- Workers are tmux sessions
|
||||
|
||||
#### SSHOutpost
|
||||
- Full Gas Town clone on remote VM
|
||||
- Uses SSHConnection for remote ops
|
||||
- Workers are remote tmux sessions
|
||||
- Town config replicated to VM
|
||||
|
||||
#### CloudRunOutpost
|
||||
- Workers are container instances
|
||||
- HTTP/gRPC for work dispatch
|
||||
- No tmux (stateless containers)
|
||||
- Persistent connections for warmth
|
||||
|
||||
## Cloud Run Deep Dive
|
||||
|
||||
### Container Design
|
||||
|
||||
```dockerfile
|
||||
FROM golang:1.21 AS builder
|
||||
# Build gt binary...
|
||||
|
||||
FROM ubuntu:22.04
|
||||
# Install Claude Code
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# Install git, common tools
|
||||
RUN apt-get update && apt-get install -y git
|
||||
|
||||
# Copy gt binary
|
||||
COPY --from=builder /app/gt /usr/local/bin/gt
|
||||
|
||||
# Entrypoint accepts work via HTTP
|
||||
COPY worker-entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
```
|
||||
|
||||
### HTTP Work Protocol
|
||||
|
||||
```
|
||||
POST /work
|
||||
{
|
||||
"issue_id": "gt-abc123",
|
||||
"rig_url": "https://github.com/steveyegge/gastown",
|
||||
"beads_url": "https://github.com/steveyegge/gastown",
|
||||
"context": { /* optional hints */ }
|
||||
}
|
||||
|
||||
Response (streaming):
|
||||
{
|
||||
"status": "working|done|failed",
|
||||
"branch": "polecat/gt-abc123",
|
||||
"logs": "...",
|
||||
"pr_url": "..." // if created
|
||||
}
|
||||
```
|
||||
|
||||
### Persistent Connections
|
||||
|
||||
The "zero to one" solution:
|
||||
1. Mayor opens HTTP/2 connection to Cloud Run
|
||||
2. Connection stays open (Cloud Run keeps container warm)
|
||||
3. Send work requests over same connection
|
||||
4. Container processes work, streams results back
|
||||
5. On idle timeout, connection closes, container scales down
|
||||
6. Next request: small cold start, but acceptable
|
||||
|
||||
```
|
||||
┌──────────┐ ┌────────────────┐
|
||||
│ Mayor │────HTTP/2 stream───▶│ Cloud Run │
|
||||
│ │◀───results stream───│ Container │
|
||||
└──────────┘ └────────────────┘
|
||||
│ │
|
||||
│ Connection persists │ Container stays
|
||||
│ for hours if needed │ warm while
|
||||
│ │ connection open
|
||||
▼ ▼
|
||||
[New work requests go over same connection]
|
||||
```
|
||||
|
||||
### Git in Cloud Run
|
||||
|
||||
Options for code access:
|
||||
|
||||
1. **Clone on startup** (slow, ~30s+ for large repos)
|
||||
- Simple but adds latency
|
||||
- Acceptable if persistent connection keeps container warm
|
||||
|
||||
2. **Cloud Storage FUSE mount** (read-only)
|
||||
- Mount bucket with repo snapshot
|
||||
- Fast startup
|
||||
- Read-only limits usefulness
|
||||
|
||||
3. **Persistent volume** (Cloud Run now supports!)
|
||||
- Attach Cloud Storage or Filestore volume
|
||||
- Git clone persists across container restarts
|
||||
- Best of both worlds
|
||||
|
||||
4. **Shallow clone with depth**
|
||||
- `git clone --depth 1` for speed
|
||||
- Sufficient for most worker tasks
|
||||
- Can fetch more history if needed
|
||||
|
||||
**Recommendation:** Persistent volume with shallow clone. Container starts, checks if clone exists, pulls if yes, shallow clones if no.
|
||||
|
||||
### Beads Sync in Cloud Run
|
||||
|
||||
Workers need beads access. Options:
|
||||
|
||||
1. **Clone beads repo at startup**
|
||||
- Same as code: persistent volume helps
|
||||
- `bd sync` before and after work
|
||||
|
||||
2. **Beads as API** (future)
|
||||
- Central beads server
|
||||
- Workers query/update via HTTP
|
||||
- More complex but cleaner for distributed
|
||||
|
||||
3. **Beads in git (current)**
|
||||
- Works today
|
||||
- Worker clones .beads, does work, pushes
|
||||
- Git handles conflicts
|
||||
|
||||
**Recommendation:** Start with git-based beads. It works today and Cloud Run workers can push to the beads repo just like local workers.
|
||||
|
||||
### Mail in Cloud Run
|
||||
|
||||
For VM outposts, mail is filesystem-based. For Cloud Run:
|
||||
|
||||
**Option A: No mail needed**
|
||||
- Cloud Run workers are "fire and forget"
|
||||
- Mayor pushes work via HTTP, gets results via HTTP
|
||||
- Simpler model for stateless workers
|
||||
|
||||
**Option B: Mail via git**
|
||||
- Worker checks `mail/inbox.jsonl` in repo
|
||||
- Rare for workers to need incoming mail
|
||||
- Mostly they just do work and report results
|
||||
|
||||
**Recommendation:** Start with Option A. Cloud Run workers receive work via HTTP, report via HTTP. Mail is for long-running stateful agents (Witness, Refinery), not burst workers.
|
||||
|
||||
### Cost Model
|
||||
|
||||
Cloud Run pricing (as of late 2024):
|
||||
- CPU: ~$0.00002400/vCPU-second
|
||||
- Memory: ~$0.00000250/GiB-second
|
||||
- Requests: ~$0.40/million
|
||||
|
||||
For a worker running 5 minutes (300s) with 2 vCPU, 4GB RAM:
|
||||
- CPU: 300 × 2 × $0.000024 = $0.0144
|
||||
- Memory: 300 × 4 × $0.0000025 = $0.003
|
||||
- **Total: ~$0.017 per worker session**
|
||||
|
||||
50 workers × 5 minutes each = ~$0.85
|
||||
|
||||
**Key insight:** When idle (connection closed, scaled to zero): **$0**
|
||||
|
||||
Compare to a VM running 24/7: ~$50-200/month
|
||||
|
||||
Cloud Run makes burst capacity essentially free when not in use.
|
||||
|
||||
### Claude API in Cloud Run
|
||||
|
||||
Workers need Claude API access:
|
||||
|
||||
1. **API key in Secret Manager**
|
||||
- Cloud Run mounts secret as env var
|
||||
- Standard pattern
|
||||
|
||||
2. **Workload Identity** (if using Vertex AI)
|
||||
- Service account with Claude access
|
||||
- No keys to manage
|
||||
|
||||
3. **Rate limiting concerns**
|
||||
- Many concurrent workers = many API calls
|
||||
- May need to coordinate or queue
|
||||
- Could use Mayor as API proxy (future)
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MAYOR │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Outpost Manager │ │
|
||||
│ │ - Tracks all registered outposts │ │
|
||||
│ │ - Routes work to appropriate outpost │ │
|
||||
│ │ - Monitors worker status across outposts │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ Local │ │ SSH │ │ CloudRun │ │
|
||||
│ │ Outpost │ │ Outpost │ │ Outpost │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
|
||||
└───────┼──────────────┼──────────────────┼───────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────────┐
|
||||
│ tmux │ │ SSH │ │ HTTP/2 │
|
||||
│ panes │ │sessions │ │ connections │
|
||||
└─────────┘ └─────────┘ └─────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────────┐
|
||||
│ Workers │ │ Workers │ │ Workers │
|
||||
│ (local) │ │ (VM) │ │ (containers)│
|
||||
└─────────┘ └─────────┘ └─────────────┘
|
||||
│ │ │
|
||||
└──────────────┼──────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Git Repos │
|
||||
│ (beads sync) │
|
||||
│ (code repos) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# ~/gt/config/outposts.yaml
|
||||
outposts:
|
||||
# Always present - the local machine
|
||||
- name: local
|
||||
type: local
|
||||
max_workers: 4
|
||||
|
||||
# VM with full Gas Town clone
|
||||
- name: gce-worker-1
|
||||
type: ssh
|
||||
host: 10.0.0.5
|
||||
user: steve
|
||||
ssh_key: ~/.ssh/gce_worker
|
||||
town_path: /home/steve/ai
|
||||
max_workers: 8
|
||||
|
||||
# Cloud Run for burst capacity
|
||||
- name: cloudrun-burst
|
||||
type: cloudrun
|
||||
project: my-gcp-project
|
||||
region: us-central1
|
||||
service: gastown-worker
|
||||
max_workers: 20 # Or unlimited with cost cap
|
||||
cost_cap_hourly: 5.00 # Optional spending limit
|
||||
|
||||
# Work assignment policy
|
||||
policy:
|
||||
# Try local first, then VM, then Cloud Run
|
||||
default_preference: [local, gce-worker-1, cloudrun-burst]
|
||||
|
||||
# Override for specific scenarios
|
||||
overrides:
|
||||
- condition: "priority >= P3" # Background work
|
||||
prefer: cloudrun-burst
|
||||
- condition: "estimated_duration > 30m" # Long tasks
|
||||
prefer: gce-worker-1
|
||||
```
|
||||
|
||||
**When policy becomes a molecule**: Static policy handles simple preference-based routing. But when assignment requires cognition - analyzing work characteristics, checking outpost health in real-time, making cost/speed tradeoffs, or handling degraded outposts - escalate to `mol-outpost-assign`. The policy file declares preferences; the molecule executes intelligent assignment. See [Config vs Molecule](architecture.md#config-vs-molecule-when-to-use-which) for the general principle.
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Outpost Abstraction (Local)
|
||||
- Define Outpost/Worker interfaces
|
||||
- Implement LocalOutpost (refactor current polecat spawning)
|
||||
- Configuration file for outposts
|
||||
- `gt outpost list`, `gt outpost status`
|
||||
|
||||
### Phase 2: SSH Outpost (VMs)
|
||||
- Implement SSHConnection (extends existing Connection interface)
|
||||
- Implement SSHOutpost
|
||||
- VM provisioning docs (Terraform examples)
|
||||
- `gt outpost add ssh ...`
|
||||
- Test with actual GCE VM
|
||||
|
||||
### Phase 3: Cloud Run Outpost
|
||||
- Define worker container image
|
||||
- Implement CloudRunOutpost
|
||||
- HTTP/2 work dispatch protocol
|
||||
- Persistent connection management
|
||||
- Cost tracking/limits
|
||||
- `gt outpost add cloudrun ...`
|
||||
|
||||
### Phase 4: Policy & Intelligence
|
||||
- Smart assignment based on workload characteristics
|
||||
- Cost optimization (prefer free capacity)
|
||||
- Auto-scaling policies
|
||||
- Dashboard for cross-outpost visibility
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### 1. Outpost as First-Class Concept
|
||||
Rather than baking in specific platforms (SSH, Cloud Run), model the abstraction. This gives flexibility for future platforms (K8s, bare metal, other clouds).
|
||||
|
||||
### 2. Workers Are Ephemeral
|
||||
Whether local tmux, VM process, or Cloud Run container - workers are spawned for work and can be terminated. Don't assume persistence.
|
||||
|
||||
### 3. Git as Source of Truth
|
||||
Code and beads always sync via git. This works regardless of where workers run. Even Cloud Run workers clone/pull from git.
|
||||
|
||||
### 4. HTTP for Cloud Run Control Plane
|
||||
For Cloud Run specifically, use HTTP for work dispatch. Don't try to make filesystem mail work across containers. Keep it simple.
|
||||
|
||||
### 5. Local-First Default
|
||||
Always try local workers first. Remote outposts are for overflow/burst, not primary capacity. This keeps latency low and costs down.
|
||||
|
||||
### 6. Graceful Degradation
|
||||
If Cloud Run is unavailable, fall back to VM. If VM is down, use local only. System works with any subset of outposts.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Long-running sessions**: Cloud Run has request timeout limits (configurable up to 60 min, maybe longer now?). How does this interact with long Claude sessions?
|
||||
|
||||
2. **Context handoff**: If a Cloud Run worker's container restarts mid-task, how do we resume? Mail-to-self? Checkpoint to storage?
|
||||
|
||||
3. **Refinery in Cloud Run**: Could the Refinery itself run as a Cloud Run service? Long-running connection for merge queue processing?
|
||||
|
||||
4. **Witness in Cloud Run**: Worker monitoring from Cloud Run? Or does Witness need to be local/VM?
|
||||
|
||||
5. **Multi-region**: Cloud Run in multiple regions for geographic distribution? How to coordinate?
|
||||
|
||||
## Summary
|
||||
|
||||
The Outpost abstraction lets Gas Town scale flexibly:
|
||||
|
||||
| Outpost Type | Best For | Cost Model | Scaling |
|
||||
|--------------|----------|------------|---------|
|
||||
| Local | Development, primary work | Free (your machine) | Fixed |
|
||||
| SSH/VM | Long-running, full autonomy | Always-on VM cost | Manual |
|
||||
| Cloud Run | Burst, background, elastic | Pay-per-use | Auto |
|
||||
|
||||
Cloud Run's persistent connections solve the cold start problem, making it viable for interactive-ish work. Combined with VMs for heavier work and local for development, this gives a flexible spectrum of compute options.
|
||||
|
||||
The key insight: **don't pick one model, support both.** Let users configure their outposts based on their needs, budget, and scale requirements.
|
||||
@@ -1,248 +0,0 @@
|
||||
# Formula Evolution: Predictions and Future Directions
|
||||
|
||||
> Written: 2024-12-26 (Day 5 of formulas, Day 10 of molecules)
|
||||
> Status: Speculative - institutional knowledge capture
|
||||
|
||||
## Context
|
||||
|
||||
Gas Town's workflow system has evolved rapidly:
|
||||
- **Day 0**: Molecules conceived as workflow instances
|
||||
- **Day 5**: Formulas introduced as compile-time templates
|
||||
- **Day 10**: Patrols, session persistence, bond operators, protomolecules
|
||||
|
||||
We've essentially discovered molecular chemistry for workflows. This document captures predictions about where this goes next.
|
||||
|
||||
## Current Architecture
|
||||
|
||||
```
|
||||
Formulas (TOML) → compile → Molecules (runtime)
|
||||
↓ ↓
|
||||
Templates with Executable workflows
|
||||
{{variables}} with state persistence
|
||||
↓ ↓
|
||||
Stored in Walked by patrols,
|
||||
.beads/formulas/ survive restarts
|
||||
```
|
||||
|
||||
## The Package Ecosystem Parallel
|
||||
|
||||
Every successful package ecosystem evolved through similar phases:
|
||||
|
||||
| Phase | npm | Maven | Go | Formulas (predicted) |
|
||||
|-------|-----|-------|-----|---------------------|
|
||||
| 1. Local files | `./lib/` | `lib/*.jar` | `vendor/` | `.beads/formulas/` |
|
||||
| 2. Central registry | npmjs.org | Maven Central | proxy.golang.org | Mol Mall |
|
||||
| 3. Namespacing | `@org/pkg` | `groupId:artifactId` | `github.com/org/pkg` | `@org/formula` |
|
||||
| 4. Version locking | `package-lock.json` | `<version>` in pom | `go.sum` | `formulas.lock`? |
|
||||
| 5. Composition | peer deps, plugins | BOM, parent POMs | embedding | nesting, AOP |
|
||||
|
||||
## Formula Resolution Hierarchy
|
||||
|
||||
Like `$PATH` or module resolution, formulas should resolve through layers:
|
||||
|
||||
```
|
||||
1. Project: ./.beads/formulas/ # Project-specific workflows
|
||||
2. Rig: ~/gt/<rig>/.formulas/ # Rig-wide (shared across clones)
|
||||
3. Town: ~/gt/.formulas/ # Town-wide (mayor's standard library)
|
||||
4. User: ~/.gastown/formulas/ # Personal collection
|
||||
5. Mall: mol-mall.io/ # Published packages
|
||||
```
|
||||
|
||||
**Resolution order**: project → rig → town → user → mall (first match wins)
|
||||
|
||||
**Explicit scoping**: `@gastown/shiny` always resolves to Mol Mall's gastown org
|
||||
|
||||
## Formula Combinators
|
||||
|
||||
Current composition is implicit (nesting, `needs` dependencies). We should formalize **formula combinators** - higher-order operations on workflows:
|
||||
|
||||
### Sequential Composition
|
||||
```
|
||||
A >> B # A then B
|
||||
release >> announce # Release, then announce
|
||||
```
|
||||
|
||||
### Parallel Composition
|
||||
```
|
||||
A | B # A and B concurrent (join before next)
|
||||
(build-linux | build-mac | build-windows) >> package
|
||||
```
|
||||
|
||||
### Conditional Composition
|
||||
```
|
||||
A ? B : C # if A succeeds then B else C
|
||||
test ? deploy : rollback
|
||||
```
|
||||
|
||||
### Wrapping (AOP)
|
||||
```
|
||||
wrap(A, with=B) # B's before/after around A's steps
|
||||
wrap(deploy, with=compliance-audit)
|
||||
```
|
||||
|
||||
### Injection
|
||||
```
|
||||
inject(A, at="step-id", B) # Insert B into A at specified step
|
||||
inject(polecat-work, at="issue-work", shiny)
|
||||
```
|
||||
|
||||
The Shiny-wrapping-polecat example: a polecat runs `polecat-work` formula, but the "issue-work" step (where actual coding happens) gets replaced/wrapped with `shiny`, adding design-review-test phases.
|
||||
|
||||
### Extension/Override
|
||||
```
|
||||
B extends A {
|
||||
override step "deploy" { ... }
|
||||
add step "notify" after "deploy" { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Algebra Properties
|
||||
|
||||
These combinators should satisfy algebraic laws:
|
||||
- **Associativity**: `(A >> B) >> C = A >> (B >> C)`
|
||||
- **Identity**: `A >> noop = noop >> A = A`
|
||||
- **Parallel commutativity**: `A | B = B | A` (order doesn't matter)
|
||||
|
||||
## Higher Abstractions (Speculative)
|
||||
|
||||
### 1. Formula Algebras
|
||||
Formal composition with provable guarantees:
|
||||
```toml
|
||||
[guarantees]
|
||||
compliance = "all MRs pass audit step"
|
||||
coverage = "test step runs before any deploy"
|
||||
```
|
||||
|
||||
The system could verify these properties statically.
|
||||
|
||||
### 2. Constraint Workflows
|
||||
Declare goals, let the system derive steps:
|
||||
```toml
|
||||
[goals]
|
||||
reviewed = true
|
||||
tested = true
|
||||
compliant = true
|
||||
deployed_to = "production"
|
||||
|
||||
# System generates: review >> test >> compliance >> deploy
|
||||
```
|
||||
|
||||
### 3. Adaptive Formulas
|
||||
Learn from execution history:
|
||||
```toml
|
||||
[adapt]
|
||||
on_failure_rate = { threshold = 0.3, action = "add-retry" }
|
||||
on_slow_step = { threshold = "5m", action = "parallelize" }
|
||||
```
|
||||
|
||||
### 4. Formula Schemas/Interfaces
|
||||
Type system for workflows:
|
||||
```toml
|
||||
implements = ["Reviewable", "Deployable"]
|
||||
|
||||
# Reviewable requires: has step with tag "review"
|
||||
# Deployable requires: has step with tag "deploy", depends on "test"
|
||||
```
|
||||
|
||||
Enables: "Give me all formulas that implement Deployable"
|
||||
|
||||
### 5. Meta-Formulas
|
||||
Formulas that generate formulas:
|
||||
```toml
|
||||
[meta]
|
||||
for_each = ["service-a", "service-b", "service-c"]
|
||||
generate = "deploy-{{item}}"
|
||||
template = "microservice-deploy"
|
||||
```
|
||||
|
||||
## Mol Mall Architecture (Predicted)
|
||||
|
||||
### Registry Structure
|
||||
```
|
||||
mol-mall.io/
|
||||
├── @gastown/ # Official Gas Town formulas
|
||||
│ ├── shiny
|
||||
│ ├── polecat-work
|
||||
│ └── release
|
||||
├── @acme-corp/ # Enterprise org (private)
|
||||
│ └── compliance-review
|
||||
├── @linus/ # Individual publisher
|
||||
│ └── kernel-review
|
||||
└── community/ # Unscoped public formulas
|
||||
└── towers-of-hanoi
|
||||
```
|
||||
|
||||
### Package Manifest
|
||||
```toml
|
||||
# formula.toml or mol.toml
|
||||
[package]
|
||||
name = "shiny"
|
||||
version = "1.2.0"
|
||||
description = "Engineer in a Box"
|
||||
authors = ["Gas Town <gastown@example.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
"@gastown/review-patterns" = "^2.0"
|
||||
|
||||
[peer-dependencies]
|
||||
# Must be provided by the consuming workflow
|
||||
"@gastown/test-runner" = "*"
|
||||
```
|
||||
|
||||
### Lock File
|
||||
```toml
|
||||
# formulas.lock
|
||||
[[package]]
|
||||
name = "@gastown/shiny"
|
||||
version = "1.2.0"
|
||||
checksum = "sha256:abc123..."
|
||||
source = "mol-mall.io"
|
||||
|
||||
[[package]]
|
||||
name = "@gastown/review-patterns"
|
||||
version = "2.1.3"
|
||||
checksum = "sha256:def456..."
|
||||
source = "mol-mall.io"
|
||||
```
|
||||
|
||||
## Distribution Scenarios
|
||||
|
||||
### Scenario 1: Celebrity Formulas
|
||||
"Linus Torvalds' kernel review formula" - expert-curated workflows that encode deep domain knowledge. High signal, community trust.
|
||||
|
||||
### Scenario 2: Enterprise Compliance
|
||||
Internal company formula for compliance reviews. Private registry, mandatory inclusion in all MR workflows via policy.
|
||||
|
||||
### Scenario 3: Standard Library
|
||||
Gas Town ships `@gastown/*` formulas as blessed defaults. Like Go's standard library - high quality, well-maintained, always available.
|
||||
|
||||
### Scenario 4: Community Ecosystem
|
||||
Thousands of community formulas of varying quality. Need: ratings, downloads, verified publishers, security scanning.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Versioning semantics**: SemVer? How do breaking changes work for workflows?
|
||||
|
||||
2. **Security model**: Formulas execute code. Sandboxing? Permissions? Signing?
|
||||
|
||||
3. **Testing formulas**: How do you unit test a workflow? Mock steps?
|
||||
|
||||
4. **Formula debugging**: Step through execution? Breakpoints?
|
||||
|
||||
5. **Rollback semantics**: If step 7 of 10 fails, what's the undo story?
|
||||
|
||||
6. **Cross-rig formulas**: Can a gastown formula reference a beads formula?
|
||||
|
||||
7. **Formula inheritance depth**: How deep can `extends` chains go? Diamond problem?
|
||||
|
||||
## Next Steps (Suggested)
|
||||
|
||||
1. **Formalize combinator semantics** - Write spec for `>>`, `|`, `wrap`, `inject`
|
||||
2. **Prototype Mol Mall** - Even a simple file-based registry to prove the model
|
||||
3. **Add formula schemas** - `implements` field with interface definitions
|
||||
4. **Build formula test harness** - Run formulas in dry-run/mock mode
|
||||
|
||||
---
|
||||
|
||||
*This document captures thinking as of late December 2024. Formulas are evolving rapidly - update as we learn more.*
|
||||
287
docs/hq.md
287
docs/hq.md
@@ -1,287 +0,0 @@
|
||||
# Gas Town HQ Design
|
||||
|
||||
The **HQ** (headquarters) is the top-level directory where Gas Town is installed - the workspace that contains all your rigs, agents, and coordination infrastructure.
|
||||
|
||||
## What Is an HQ?
|
||||
|
||||
Think of HQ as the "mount point" for Gas Town. It's the root directory where:
|
||||
- The Mayor operates from
|
||||
- Rigs are registered and managed
|
||||
- Town-level beads coordinate mail and handoffs
|
||||
- The entire workspace is versioned as a git repository
|
||||
|
||||
An HQ is NOT:
|
||||
- A git clone of any project (rigs contain the clones)
|
||||
- A hidden directory (it's visible and user-controlled)
|
||||
- Tied to any specific project (it can manage multiple rigs)
|
||||
|
||||
## HQ Structure
|
||||
|
||||
```
|
||||
~/gt/ # HQ ROOT
|
||||
├── .git/ # HQ is a git repo
|
||||
├── .gitignore # Generated by gt git-init
|
||||
├── .beads/ # Town-level beads (hq-* prefix)
|
||||
│ ├── beads.db # Mayor mail, coordination, handoffs
|
||||
│ └── config.yaml # Beads config with prefix: hq
|
||||
│
|
||||
├── CLAUDE.md # Mayor role context (runs from here)
|
||||
│
|
||||
├── mayor/ # Mayor config and state
|
||||
│ ├── town.json # {"type": "town", "name": "..."}
|
||||
│ ├── rigs.json # Registry of managed rigs
|
||||
│ └── state.json # Mayor state
|
||||
│
|
||||
├── rigs/ # Managed rig containers
|
||||
│ ├── gastown/ # A rig (project container)
|
||||
│ └── wyvern/ # Another rig
|
||||
│
|
||||
└── <rig-name>/ # OR rigs at HQ root (legacy)
|
||||
```
|
||||
|
||||
## Creating an HQ
|
||||
|
||||
Use `gt install` to create a new HQ:
|
||||
|
||||
```bash
|
||||
# Create a new HQ
|
||||
gt install ~/gt
|
||||
|
||||
# Create with git initialization
|
||||
gt install ~/gt --git
|
||||
|
||||
# Create and push to GitHub
|
||||
gt install ~/gt --github=username/my-gastown --private
|
||||
|
||||
# Initialize current directory as HQ
|
||||
gt install . --name my-workspace
|
||||
```
|
||||
|
||||
The install command:
|
||||
1. Creates the directory structure (`mayor/`, `rigs/`)
|
||||
2. Writes configuration files (`town.json`, `rigs.json`, `state.json`)
|
||||
3. Generates `CLAUDE.md` with Mayor role context
|
||||
4. Initializes town-level beads with `hq-` prefix
|
||||
5. Optionally initializes git with `.gitignore`
|
||||
|
||||
## HQ vs Town vs Rig
|
||||
|
||||
| Concept | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| **HQ** | Installation directory | `~/gt/` |
|
||||
| **Town** | Logical workspace (same as HQ) | The Gas Town instance |
|
||||
| **Rig** | Project container within HQ | `~/gt/gastown/` |
|
||||
|
||||
The terms "HQ" and "town" are often used interchangeably. An HQ IS a town. The distinction is physical (HQ = directory) vs logical (town = workspace concept).
|
||||
|
||||
## Beads in an HQ
|
||||
|
||||
An HQ has **two levels** of beads:
|
||||
|
||||
### Town-Level Beads
|
||||
|
||||
Located at `<hq>/.beads/` with `hq-` prefix:
|
||||
- Mayor mail and inbox
|
||||
- Cross-rig coordination messages
|
||||
- Session handoff notes
|
||||
|
||||
### Rig-Level Beads
|
||||
|
||||
Each rig has its own `.beads/` with a project-specific prefix:
|
||||
- Work issues (bugs, features, tasks)
|
||||
- Merge requests
|
||||
- Agent-local mail within the rig
|
||||
|
||||
The Mayor sees both: town beads for mail, rig beads for work coordination.
|
||||
|
||||
## Beads Redirect Pattern
|
||||
|
||||
In complex setups, you may want the HQ root's `.beads/` to redirect to a rig's beads. This is useful when:
|
||||
- Multiple systems share an HQ
|
||||
- You want a single source of truth for beads
|
||||
- Migration scenarios
|
||||
|
||||
Create a redirect file:
|
||||
|
||||
```bash
|
||||
# Instead of .beads/ directory, create .beads/redirect file
|
||||
mkdir .beads
|
||||
echo "path/to/actual/.beads" > .beads/redirect
|
||||
```
|
||||
|
||||
Example from a real setup:
|
||||
```
|
||||
# ~/ai/.beads/redirect
|
||||
# Redirect to gastown beads (Mayor workspace)
|
||||
# The Mayor runs in ~/ai but manages gastown issues in mayor/rigs/gastown
|
||||
mayor/rigs/gastown/.beads
|
||||
```
|
||||
|
||||
**When to use redirects:**
|
||||
- When rig beads should be the canonical town beads
|
||||
- Hybrid setups where agents work in different locations
|
||||
|
||||
## HQ Configuration Files
|
||||
|
||||
### mayor/town.json
|
||||
|
||||
Identifies this as a Gas Town installation:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "town",
|
||||
"version": 1,
|
||||
"name": "stevey-gastown",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### mayor/rigs.json
|
||||
|
||||
Registry of managed rigs:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"rigs": {
|
||||
"gastown": {
|
||||
"git_url": "https://github.com/steveyegge/gastown",
|
||||
"added_at": "2024-01-15T10:30:00Z"
|
||||
},
|
||||
"wyvern": {
|
||||
"git_url": "https://github.com/steveyegge/wyvern",
|
||||
"added_at": "2024-01-16T09:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### mayor/state.json
|
||||
|
||||
Mayor agent state:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "mayor",
|
||||
"last_active": "2024-01-17T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Git for HQs
|
||||
|
||||
An HQ should be a git repository. This enables:
|
||||
- Versioning of configuration
|
||||
- Beads sync across machines
|
||||
- Session handoff via beads commits
|
||||
- Recovery after failures
|
||||
|
||||
### Initialize git
|
||||
|
||||
```bash
|
||||
gt git-init # Basic git setup
|
||||
gt git-init --github=user/repo # Create GitHub repo
|
||||
gt git-init --github=user/repo --private # Private repo
|
||||
```
|
||||
|
||||
### Standard .gitignore
|
||||
|
||||
The `gt git-init` command creates:
|
||||
|
||||
```gitignore
|
||||
# Gas Town HQ gitignore
|
||||
|
||||
# Agent sessions and logs
|
||||
*.log
|
||||
*.pid
|
||||
/sessions/
|
||||
|
||||
# Rig working directories (managed separately)
|
||||
/rigs/*/polecats/*/
|
||||
/rigs/*/refinery/rig/
|
||||
/rigs/*/crew/*/
|
||||
|
||||
# Sensitive files
|
||||
.env
|
||||
*.key
|
||||
*.pem
|
||||
credentials.json
|
||||
|
||||
# Editor and OS
|
||||
.DS_Store
|
||||
*.swp
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Beads daemon
|
||||
.beads/beads.sock
|
||||
.beads/*.pid
|
||||
```
|
||||
|
||||
## HQ Health Checks
|
||||
|
||||
Run `gt doctor` to check HQ health:
|
||||
|
||||
```bash
|
||||
gt doctor # Check all
|
||||
gt doctor --fix # Auto-fix issues
|
||||
```
|
||||
|
||||
Checks include:
|
||||
- Configuration file validity
|
||||
- Mayor state consistency
|
||||
- Rig registry accuracy
|
||||
- Beads database health
|
||||
- Git state cleanliness
|
||||
|
||||
## HQ Templates
|
||||
|
||||
For organizations wanting consistent Gas Town setups, create a template repository:
|
||||
|
||||
```bash
|
||||
# Create template HQ
|
||||
gt install ~/gt-template --git --no-beads
|
||||
# Customize CLAUDE.md, add standard rigs
|
||||
# Push to GitHub as template repo
|
||||
|
||||
# Users clone template
|
||||
gh repo create my-gastown --template org/gt-template
|
||||
cd my-gastown
|
||||
gt install . --force # Reinitialize with fresh beads
|
||||
```
|
||||
|
||||
## Migration Between HQs
|
||||
|
||||
To move Gas Town to a new location:
|
||||
|
||||
1. **Export beads state:**
|
||||
```bash
|
||||
bd export > beads-backup.jsonl
|
||||
```
|
||||
|
||||
2. **Create new HQ:**
|
||||
```bash
|
||||
gt install ~/new-hq --git
|
||||
```
|
||||
|
||||
3. **Add rigs:**
|
||||
```bash
|
||||
cd ~/new-hq
|
||||
gt rig add gastown https://github.com/user/gastown
|
||||
```
|
||||
|
||||
4. **Import beads:**
|
||||
```bash
|
||||
cd ~/new-hq
|
||||
bd import < beads-backup.jsonl
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| Create HQ | `gt install <path>` |
|
||||
| Initialize git | `gt git-init` |
|
||||
| Add rig | `gt rig add <name> <git-url>` |
|
||||
| Check health | `gt doctor` |
|
||||
| View status | `gt status` |
|
||||
@@ -1,543 +0,0 @@
|
||||
# Merge Queue Design
|
||||
|
||||
The merge queue coordinates landing completed work. MRs are ephemeral wisps (not synced beads), and polecat branches stay local (never pushed to origin).
|
||||
|
||||
**Key insight**: Git is already a ledger. Beads track durable state (issues). MRs are transient operational state - perfect for wisps.
|
||||
|
||||
## Architecture (Current)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ LOCAL MERGE QUEUE │
|
||||
│ │
|
||||
│ Polecat worktree ──commit──► local branch (polecat/nux) │
|
||||
│ │ │ │
|
||||
│ │ │ (same .git) │
|
||||
│ ▼ ▼ │
|
||||
│ .beads-wisp/mq/mr-xxx.json Refinery worktree │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────┘ │
|
||||
│ │ │
|
||||
│ Refinery reads MR, merges branch │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ git push origin main │
|
||||
│ │ │
|
||||
│ Delete local branch │
|
||||
│ Delete MR wisp file │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Refinery is a worktree of the same git repo as polecats (shared .git)
|
||||
- Polecat branches are local only - never pushed to origin
|
||||
- MRs stored in `.beads-wisp/mq/` (ephemeral, not synced)
|
||||
- Only `main` branch gets pushed to origin after merge
|
||||
- Source issues tracked in beads (durable), closed after merge
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ MERGE QUEUE │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ MR #1 │→ │ MR #2 │→ │ MR #3 │→ │ MR #4 │ │
|
||||
│ │ (ready) │ │(blocked) │ │ (ready) │ │(blocked) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ ↓ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ ENGINEER (processes queue) │ │
|
||||
│ │ 1. bd ready --type=merge-request │ │
|
||||
│ │ 2. Process in priority order │ │
|
||||
│ │ 3. Merge, test, close or reject │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────┐ │
|
||||
│ │ main │ │
|
||||
│ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Merge Request Schema
|
||||
|
||||
A merge request is a JSON file in `.beads-wisp/mq/`:
|
||||
|
||||
```json
|
||||
// .beads-wisp/mq/mr-1703372400-a1b2c3d4.json
|
||||
{
|
||||
"id": "mr-1703372400-a1b2c3d4",
|
||||
"branch": "polecat/nux",
|
||||
"target": "main",
|
||||
"source_issue": "gt-xyz",
|
||||
"worker": "nux",
|
||||
"rig": "gastown",
|
||||
"title": "Merge: gt-xyz",
|
||||
"priority": 1,
|
||||
"created_at": "2025-12-23T20:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### ID Convention
|
||||
|
||||
MR IDs follow the pattern: `mr-<timestamp>-<random>`
|
||||
|
||||
Example: `mr-1703372400-a1b2c3d4`
|
||||
|
||||
These are ephemeral - deleted after merge. Source issues in beads provide the durable record.
|
||||
|
||||
### Creating Merge Requests
|
||||
|
||||
Workers submit to the queue via:
|
||||
|
||||
```bash
|
||||
# Worker signals work is ready (preferred)
|
||||
gt done # Auto-detects branch, issue, creates MR
|
||||
|
||||
# Or explicit submission
|
||||
gt mq submit # Auto-detects branch, issue, worker
|
||||
gt mq submit --issue gt-xyz # Explicit issue
|
||||
|
||||
# Under the hood, this writes to .beads-wisp/mq/:
|
||||
# - Creates mr-<timestamp>-<random>.json
|
||||
# - No beads issue created (MRs are ephemeral wisps)
|
||||
# - Branch stays local (never pushed to origin)
|
||||
```
|
||||
|
||||
## Queue Ordering
|
||||
|
||||
The queue is ordered by:
|
||||
|
||||
1. **Dependencies first** - If MR-B depends on MR-A, A merges first
|
||||
2. **Priority** - P0 before P1 before P2
|
||||
3. **Age** - Older requests before newer (FIFO within priority)
|
||||
|
||||
### Dependency-Based Ordering
|
||||
|
||||
When workers complete related work, dependencies ensure correct order:
|
||||
|
||||
```
|
||||
gt-epic (epic)
|
||||
├── gt-epic.1 (task) → gt-mr-001 (merge request)
|
||||
├── gt-epic.2 (task) → gt-mr-002 (depends on gt-mr-001)
|
||||
└── gt-epic.3 (task) → gt-mr-003 (depends on gt-mr-002)
|
||||
```
|
||||
|
||||
The Engineer queries `bd ready --type=merge-request` to find MRs with no unmerged dependencies.
|
||||
|
||||
### Integration Branches
|
||||
|
||||
For batch work on an epic, use an integration branch:
|
||||
|
||||
```
|
||||
main
|
||||
│
|
||||
├──────────────────────────┐
|
||||
│ │
|
||||
integration/gt-epic (other work)
|
||||
│
|
||||
┌──────────┼──────────┐
|
||||
│ │ │
|
||||
MR #1 MR #2 MR #3
|
||||
```
|
||||
|
||||
Integration branch workflow:
|
||||
1. Create integration branch from main: `integration/gt-epic`
|
||||
2. MRs target the integration branch, not main
|
||||
3. After all epic work merges, integration branch merges to main
|
||||
4. Single PR for epic = easier review, atomic landing
|
||||
|
||||
```bash
|
||||
# Create integration branch for epic
|
||||
gt mq integration create gt-epic
|
||||
|
||||
# MRs auto-target integration branch
|
||||
gt mq submit --epic gt-epic # Targets integration/gt-epic
|
||||
|
||||
# Land entire epic to main
|
||||
gt mq integration land gt-epic
|
||||
```
|
||||
|
||||
## Engineer Processing Loop
|
||||
|
||||
The Engineer (formerly Refinery) processes the merge queue continuously:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ENGINEER LOOP │
|
||||
│ │
|
||||
│ while true: │
|
||||
│ 1. ready_mrs = bd ready --type=merge-request │
|
||||
│ │
|
||||
│ 2. if ready_mrs.empty(): │
|
||||
│ sleep(poll_interval) │
|
||||
│ continue │
|
||||
│ │
|
||||
│ 3. mr = ready_mrs.first() # Highest priority, oldest │
|
||||
│ │
|
||||
│ 4. bd update mr.id --status=in_progress │
|
||||
│ │
|
||||
│ 5. result = process_merge(mr) │
|
||||
│ │
|
||||
│ 6. if result.success: │
|
||||
│ bd close mr.id --reason="merged: {sha}" │
|
||||
│ else: │
|
||||
│ handle_failure(mr, result) │
|
||||
│ │
|
||||
│ 7. update_source_issue(mr) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Process Merge Steps
|
||||
|
||||
```python
|
||||
def process_merge(mr):
|
||||
# 1. Branch is already local (shared .git with polecats)
|
||||
# No fetch needed - refinery worktree sees polecat branches directly
|
||||
|
||||
# 2. Check for conflicts with target
|
||||
conflicts = git_check_conflicts(mr.branch, mr.target)
|
||||
if conflicts:
|
||||
return Failure(reason="conflict", files=conflicts)
|
||||
|
||||
# 3. Merge to local target
|
||||
git checkout {mr.target}
|
||||
git merge {mr.branch} --no-ff -m "Merge {mr.branch}: {mr.title}"
|
||||
|
||||
# 4. Run tests (configurable)
|
||||
if config.run_tests:
|
||||
result = run_tests()
|
||||
if result.failed:
|
||||
git reset --hard HEAD~1 # Undo merge
|
||||
return Failure(reason="tests_failed", output=result.output)
|
||||
|
||||
# 5. Push to origin (only main goes to origin)
|
||||
git push origin {mr.target}
|
||||
|
||||
# 6. Clean up source branch (local delete only)
|
||||
if config.delete_merged_branches:
|
||||
git branch -D {mr.branch} # Local delete, not remote
|
||||
|
||||
# 7. Remove MR wisp file
|
||||
os.remove(.beads-wisp/mq/{mr.id}.json)
|
||||
|
||||
return Success(merge_commit=git_rev_parse("HEAD"))
|
||||
```
|
||||
|
||||
### Handling Failures
|
||||
|
||||
| Failure | Action |
|
||||
|---------|--------|
|
||||
| **Conflict** | Assign back to worker, add `needs-rebase` label |
|
||||
| **Tests fail** | Assign back to worker, add `needs-fix` label |
|
||||
| **Build fail** | Assign back to worker, add `needs-fix` label |
|
||||
| **Flaky test** | Retry once, then assign back |
|
||||
| **Infra issue** | Retry with backoff, escalate if persistent |
|
||||
|
||||
```bash
|
||||
# On conflict, Engineer does:
|
||||
bd update gt-mr-xxx --assignee=Nux --labels=needs-rebase
|
||||
gt send gastown/Nux -s "Rebase needed: gt-mr-xxx" \
|
||||
-m "Your branch conflicts with main. Please rebase and resubmit."
|
||||
```
|
||||
|
||||
### Conflict Resolution Strategies
|
||||
|
||||
1. **Assign back to worker** (default) - Worker rebases and resubmits
|
||||
2. **Auto-rebase** (configurable) - Engineer attempts `git rebase` automatically
|
||||
3. **Semantic merge** (future) - AI-assisted conflict resolution
|
||||
|
||||
```yaml
|
||||
# rig config.json
|
||||
{
|
||||
"merge_queue": {
|
||||
"on_conflict": "assign_back", # or "auto_rebase" or "semantic"
|
||||
"run_tests": true,
|
||||
"delete_merged_branches": true,
|
||||
"retry_flaky_tests": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### gt mq (merge queue)
|
||||
|
||||
```bash
|
||||
# Submit work to queue
|
||||
gt mq submit # Auto-detect from current branch
|
||||
gt mq submit --issue gt-xyz # Explicit issue
|
||||
gt mq submit --epic gt-epic # Target integration branch
|
||||
|
||||
# View queue
|
||||
gt mq list # Show all pending MRs
|
||||
gt mq list --ready # Show only ready-to-merge
|
||||
gt mq list --mine # Show MRs for my work
|
||||
gt mq status gt-mr-xxx # Detailed MR status
|
||||
|
||||
# Integration branches
|
||||
gt mq integration create gt-epic # Create integration/gt-epic
|
||||
gt mq integration land gt-epic # Merge integration to main
|
||||
gt mq integration status gt-epic # Show integration branch status
|
||||
|
||||
# Admin/debug
|
||||
gt mq retry gt-mr-xxx # Retry a failed MR
|
||||
gt mq reject gt-mr-xxx --reason "..." # Reject an MR
|
||||
gt mq reorder gt-mr-xxx --after gt-mr-yyy # Manual reorder
|
||||
```
|
||||
|
||||
### Command Details
|
||||
|
||||
#### gt mq submit
|
||||
|
||||
```bash
|
||||
gt mq submit [--branch BRANCH] [--issue ISSUE] [--epic EPIC]
|
||||
|
||||
# Auto-detection logic:
|
||||
# 1. Branch: current git branch
|
||||
# 2. Issue: parse from branch name (polecat/Nux/gt-xyz → gt-xyz)
|
||||
# 3. Epic: if issue has parent epic, offer integration branch
|
||||
|
||||
# Creates merge-request bead and prints MR ID
|
||||
```
|
||||
|
||||
#### gt mq list
|
||||
|
||||
```bash
|
||||
gt mq list [--ready] [--status STATUS] [--worker WORKER]
|
||||
|
||||
# Output:
|
||||
# ID STATUS PRIORITY BRANCH WORKER AGE
|
||||
# gt-mr-001 ready P0 polecat/Nux/gt-xyz Nux 5m
|
||||
# gt-mr-002 in_progress P1 polecat/Toast/gt-abc Toast 12m
|
||||
# gt-mr-003 blocked P1 polecat/Capable/gt-def Capable 8m
|
||||
# (waiting on gt-mr-001)
|
||||
```
|
||||
|
||||
## Beads Query Patterns
|
||||
|
||||
The merge queue is just queries over beads:
|
||||
|
||||
```bash
|
||||
# Ready to merge (no blockers, not in progress)
|
||||
bd ready --type=merge-request
|
||||
|
||||
# All open MRs
|
||||
bd list --type=merge-request --status=open
|
||||
|
||||
# MRs for a specific epic (via labels or source_issue parent)
|
||||
bd list --type=merge-request --label=epic:gt-xyz
|
||||
|
||||
# Recently merged
|
||||
bd list --type=merge-request --status=closed --since=1d
|
||||
|
||||
# MRs by worker
|
||||
bd list --type=merge-request --assignee=Nux
|
||||
```
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ CREATED │
|
||||
│ (open) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
┌───────│ READY │───────┐
|
||||
│ │ (open) │ │
|
||||
│ └──────┬──────┘ │
|
||||
│ │ │
|
||||
blocked by ┌─────▼─────┐ rejected
|
||||
dependency │ PROCESSING│ (manual)
|
||||
│ │(in_progress) │
|
||||
│ └─────┬─────┘ │
|
||||
│ │ │
|
||||
│ ┌────────┴────────┐ │
|
||||
│ │ │ │
|
||||
│ success failure
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌───────┐ ┌─────────┐│
|
||||
│ │MERGED │ │ FAILED ││
|
||||
│ │(closed) │ (open) ││
|
||||
│ └───────┘ └────┬────┘│
|
||||
│ │ │
|
||||
│ resubmit │
|
||||
│ │ │
|
||||
└─────────────────┴─────────┘
|
||||
```
|
||||
|
||||
## Audit and Observability
|
||||
|
||||
The merge queue creates a complete audit trail for all integrated work:
|
||||
|
||||
| MQ Event | Record Created |
|
||||
|----------|----------------|
|
||||
| Merge request submitted | Work completion claim with author |
|
||||
| Tests pass | Quality verification record |
|
||||
| Refinery approves | Validation with reviewer attribution |
|
||||
| Merge commit | Immutable integration record |
|
||||
| Rejection | Feedback record with reason |
|
||||
|
||||
Every merge creates an immutable record:
|
||||
- Who did the work (author attribution)
|
||||
- Who validated it (Refinery attestation)
|
||||
- When it landed (timestamp)
|
||||
- What changed (commit diff)
|
||||
|
||||
This enables full work attribution and quality tracking across the swarm.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Rig-Level Config
|
||||
|
||||
```json
|
||||
// <rig>/config.json
|
||||
{
|
||||
"merge_queue": {
|
||||
"enabled": true,
|
||||
"target_branch": "main",
|
||||
"integration_branches": true,
|
||||
"on_conflict": "assign_back",
|
||||
"run_tests": true,
|
||||
"test_command": "go test ./...",
|
||||
"delete_merged_branches": true,
|
||||
"retry_flaky_tests": 1,
|
||||
"poll_interval": "30s",
|
||||
"max_concurrent": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Per-Epic Overrides
|
||||
|
||||
```bash
|
||||
# Create epic with custom merge config
|
||||
bd create --type=epic --title="Risky refactor" \
|
||||
--body="merge_config:
|
||||
run_tests: true
|
||||
test_command: 'go test -race ./...'
|
||||
on_conflict: assign_back"
|
||||
```
|
||||
|
||||
## Direct Landing (Bypass Queue)
|
||||
|
||||
For single-polecat work or emergencies, Mayor can bypass the queue:
|
||||
|
||||
```bash
|
||||
gt land --direct <rig>/<polecat>
|
||||
|
||||
# This:
|
||||
# 1. Verifies polecat session is terminated
|
||||
# 2. Checks git state is clean
|
||||
# 3. Merges directly to main (no MR created)
|
||||
# 4. Closes the source issue
|
||||
# 5. Cleans up the branch
|
||||
```
|
||||
|
||||
Direct landing skips the queue but still records the work in beads.
|
||||
|
||||
## Failure Recovery
|
||||
|
||||
### Engineer Crash
|
||||
|
||||
If Engineer crashes mid-merge:
|
||||
1. MR stays `in_progress`
|
||||
2. On restart, Engineer queries `bd list --type=merge-request --status=in_progress`
|
||||
3. For each, check git state and either complete or reset
|
||||
|
||||
### Partial Merge
|
||||
|
||||
If merge succeeds but push fails:
|
||||
1. Engineer retries push with exponential backoff
|
||||
2. If persistent failure, roll back local merge
|
||||
3. Mark MR as failed with reason
|
||||
|
||||
### Conflicting Merges
|
||||
|
||||
If two MRs conflict:
|
||||
1. First one wins (lands first)
|
||||
2. Second gets conflict status
|
||||
3. Worker rebases and resubmits
|
||||
4. Dependencies prevent this for related work
|
||||
|
||||
## Observability
|
||||
|
||||
### Metrics
|
||||
|
||||
- `mq_pending_count` - MRs waiting
|
||||
- `mq_processing_time` - Time from submit to merge
|
||||
- `mq_success_rate` - Merges vs rejections
|
||||
- `mq_conflict_rate` - How often conflicts occur
|
||||
- `mq_test_failure_rate` - Test failures
|
||||
|
||||
### Logs
|
||||
|
||||
```
|
||||
[MQ] Processing gt-mr-abc123 (priority=P0, age=5m)
|
||||
[MQ] Fetching branch polecat/Nux/gt-xyz
|
||||
[MQ] No conflicts detected
|
||||
[MQ] Merging to main
|
||||
[MQ] Running tests: go test ./...
|
||||
[MQ] Tests passed (32s)
|
||||
[MQ] Pushing to origin
|
||||
[MQ] Merged: abc123def
|
||||
[MQ] Closed gt-mr-abc123 (reason=merged)
|
||||
[MQ] Closed source issue gt-xyz
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MERGE QUEUE STATUS │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Pending: 3 In Progress: 1 Merged (24h): 12 Failed: 2│
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ QUEUE: │
|
||||
│ ► gt-mr-004 P0 polecat/Nux/gt-xyz Processing... │
|
||||
│ gt-mr-005 P1 polecat/Toast/gt-abc Ready │
|
||||
│ gt-mr-006 P1 polecat/Capable/gt-def Blocked (005) │
|
||||
│ gt-mr-007 P2 polecat/Nux/gt-ghi Ready │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ RECENT: │
|
||||
│ ✓ gt-mr-003 Merged 5m ago (12s processing) │
|
||||
│ ✓ gt-mr-002 Merged 18m ago (45s processing) │
|
||||
│ ✗ gt-mr-001 Failed 22m ago (conflict with main) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Schema & CLI (gt-kp2, gt-svi)
|
||||
- Define merge-request type in beads (or convention)
|
||||
- Implement `gt mq submit`, `gt mq list`, `gt mq status`
|
||||
- Manual Engineer processing (no automation yet)
|
||||
|
||||
### Phase 2: Engineer Loop (gt-3x1)
|
||||
- Implement processing loop
|
||||
- Conflict detection and handling
|
||||
- Test execution
|
||||
- Success/failure handling
|
||||
|
||||
### Phase 3: Integration Branches
|
||||
- `gt mq integration create/land/status`
|
||||
- Auto-targeting based on epic
|
||||
- Batch landing
|
||||
|
||||
### Phase 4: Advanced Features
|
||||
- Auto-rebase on conflict
|
||||
- Semantic merge (AI-assisted)
|
||||
- Parallel test execution
|
||||
- Cross-rig merge coordination
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Beads schema extension**: Should merge-request be a first-class beads type, or just a convention (type field in description)?
|
||||
|
||||
2. **High-traffic rigs**: For very active rigs, should MRs go to a separate beads repo to reduce sync contention?
|
||||
|
||||
3. **Cross-rig merges**: If work spans multiple rigs, how do we coordinate? Federation design needed.
|
||||
|
||||
4. **Rollback**: If a merge causes problems, how do we track and revert? Need `gt mq revert` command.
|
||||
@@ -1,987 +0,0 @@
|
||||
# Molecular Chemistry: Work Composition in Gas Town
|
||||
|
||||
> *"Work is fractal. Money is crystallized labor. Blockchain was the mechanism
|
||||
> searching for its purpose."*
|
||||
|
||||
Gas Town is a **work composition and execution engine**. This document describes
|
||||
**MEOW** - the **M**olecular **E**xpression **O**f **W**ork - a chemical algebra
|
||||
for expressing, instantiating, and executing work at any scale, from single tasks
|
||||
to massive polymers that can grind through weekends of autonomous operation.
|
||||
|
||||
**Core insight**: Structure is computation. Content is cognition. They're separate.
|
||||
|
||||
## The Work Lifecycle: Rig → Cook → Run
|
||||
|
||||
Gas Town work flows through three phases:
|
||||
|
||||
```
|
||||
RIG ────→ COOK ────→ RUN
|
||||
(source) (artifact) (execution)
|
||||
```
|
||||
|
||||
| Phase | What Happens | Operator | Output |
|
||||
|-------|--------------|----------|--------|
|
||||
| **Rig** | Compose formulas (source level) | `extends`, `compose` | Compound Formula |
|
||||
| **Cook** | Instantiate artifacts | `cook`, `pour`, `wisp` | Proto, Mol, Wisp |
|
||||
| **Run** | Execute steps | (agent execution) | Work done |
|
||||
|
||||
**Rig** is authoring time - writing and composing formula files (TOML preferred, JSON supported).
|
||||
**Cook** is compile time - expanding macros, applying aspects, flattening to pure graphs.
|
||||
**Run** is execution time - agents provide cognition for each step.
|
||||
|
||||
## The Complete Artifact Graph
|
||||
|
||||
```
|
||||
SOURCE LEVEL (Rig)
|
||||
══════════════════
|
||||
|
||||
Formula ─────rig─────→ Compound Formula
|
||||
│ (extends, │
|
||||
│ compose) │
|
||||
└──────────┬────────────┘
|
||||
│
|
||||
cook
|
||||
│
|
||||
▼
|
||||
ARTIFACT LEVEL (Bond)
|
||||
════════════════════
|
||||
|
||||
Proto ──────bond─────→ Compound Proto
|
||||
│ \ │ \
|
||||
│ \ │ \
|
||||
pour wisp pour wisp
|
||||
│ \ │ \
|
||||
▼ ▼ ▼ ▼
|
||||
Mol Wisp ────bond────→ Linked Work
|
||||
│ │
|
||||
└───┬───┘
|
||||
│
|
||||
run
|
||||
│
|
||||
▼
|
||||
EXECUTION
|
||||
═════════
|
||||
|
||||
Steps complete
|
||||
Work gets done
|
||||
Digests created
|
||||
```
|
||||
|
||||
## Two Composition Operators
|
||||
|
||||
Gas Town has **two** composition operators at different abstraction levels:
|
||||
|
||||
| Operator | Level | Inputs | When to Use |
|
||||
|----------|-------|--------|-------------|
|
||||
| **Rig** | Source | Formula + Formula | Authoring time, in TOML/JSON |
|
||||
| **Bond** | Artifact | Proto/Mol/Wisp + any | Runtime, on cooked artifacts |
|
||||
|
||||
**Rig** composes formulas (TOML/JSON with `extends`, `compose`).
|
||||
**Bond** composes artifacts (cooked protos, running mols/wisps).
|
||||
|
||||
This separation is key: rig for design-time composition, bond for runtime composition.
|
||||
|
||||
## The Steam Engine Metaphor
|
||||
|
||||
Gas Town is an engine. Engines do work and generate steam.
|
||||
|
||||
```
|
||||
Claude = Fire (the energy source)
|
||||
Claude Code = Steam Engine (harnesses the fire)
|
||||
Gas Town = Steam Train (coordinates engines on tracks)
|
||||
Beads = Railroad Tracks (the persistent ledger of work)
|
||||
```
|
||||
|
||||
In our chemistry:
|
||||
- **Formulas** are the secret recipe (source code for workflows)
|
||||
- **Proto molecules** are the fuel (cooked templates, ready to instantiate)
|
||||
- **Mols** are liquid work (flowing, dynamic, adapting as steps complete)
|
||||
- **Wisps** are the steam (transient execution traces that rise and dissipate)
|
||||
- **Digests** are the distillate (condensed permanent records of completed work)
|
||||
|
||||
## Formulas: The Source Layer
|
||||
|
||||
**Formulas** sit above protos in the artifact hierarchy. They're the source code -
|
||||
TOML or JSON files that define workflows with composition operators.
|
||||
|
||||
TOML is preferred for human-edited formulas (multi-line strings, comments):
|
||||
|
||||
```toml
|
||||
# shiny.formula.toml - a basic workflow
|
||||
formula = "shiny"
|
||||
description = "The canonical right way"
|
||||
version = 1
|
||||
|
||||
[[steps]]
|
||||
id = "design"
|
||||
description = "Think carefully about architecture"
|
||||
|
||||
[[steps]]
|
||||
id = "implement"
|
||||
needs = ["design"]
|
||||
|
||||
[[steps]]
|
||||
id = "review"
|
||||
needs = ["implement"]
|
||||
|
||||
[[steps]]
|
||||
id = "test"
|
||||
needs = ["review"]
|
||||
|
||||
[[steps]]
|
||||
id = "submit"
|
||||
needs = ["test"]
|
||||
```
|
||||
|
||||
Convert existing JSON formulas with `bd formula convert --all`.
|
||||
|
||||
### Formula Composition (Rigging)
|
||||
|
||||
Formulas compose at the source level using `extends` and `compose`:
|
||||
|
||||
```toml
|
||||
# shiny-enterprise.formula.toml
|
||||
formula = "shiny-enterprise"
|
||||
extends = ["shiny"] # Inherit from base formula
|
||||
|
||||
[compose]
|
||||
aspects = ["security-audit"] # Weave in cross-cutting concern
|
||||
|
||||
[[compose.expand]]
|
||||
target = "implement"
|
||||
with = "rule-of-five" # Apply macro expansion
|
||||
```
|
||||
|
||||
### Cooking: Formula → Proto
|
||||
|
||||
The `cook` command flattens a formula into a pure proto:
|
||||
|
||||
```bash
|
||||
bd cook shiny-enterprise
|
||||
# Cooking shiny-enterprise...
|
||||
# ✓ Cooked proto: shiny-enterprise (30 steps)
|
||||
```
|
||||
|
||||
Cooking pre-expands all composition - macros, aspects, branches, gates.
|
||||
The result is a flat step graph with no interpretation needed at runtime.
|
||||
|
||||
### Formula Types
|
||||
|
||||
| Type | Purpose | Example |
|
||||
|------|---------|---------|
|
||||
| **workflow** | Standard work definition | shiny, patrol |
|
||||
| **expansion** | Macro template | rule-of-five |
|
||||
| **aspect** | Cross-cutting concern | security-audit |
|
||||
|
||||
## The Three Phases of Matter
|
||||
|
||||
Work in Gas Town exists in three phases, following the states of matter:
|
||||
|
||||
| Phase | Name | State | Storage | Behavior |
|
||||
|-------|------|-------|---------|----------|
|
||||
| **Solid** | Proto | Frozen template | `.beads/` (template label) | Crystallized, immutable, reusable |
|
||||
| **Liquid** | Mol | Flowing instance | `.beads/` | Dynamic, adapting, persistent |
|
||||
| **Vapor** | Wisp | Ephemeral trace | `.beads-wisp/` | Transient, dissipates, operational |
|
||||
|
||||
### Proto (Solid Phase)
|
||||
|
||||
Protos or protomolecules are **frozen workflow patterns** - crystallized templates that
|
||||
encode reusable work structures. They're the "molds" from which instances are cast.
|
||||
|
||||
Protos are stored as beads issues with `labels: ["template"]` and structured step data:
|
||||
|
||||
```yaml
|
||||
# Example: shiny.formula.toml (source) → cooked proto (beads)
|
||||
formula: shiny
|
||||
description: The canonical right way
|
||||
version: 1
|
||||
steps:
|
||||
- id: design
|
||||
description: Think carefully about architecture
|
||||
- id: implement
|
||||
needs: [design]
|
||||
- id: review
|
||||
needs: [implement]
|
||||
- id: test
|
||||
needs: [review]
|
||||
- id: submit
|
||||
needs: [test]
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- Considered immutable once cooked (frozen), though source formulas are editable
|
||||
- Named (e.g., `shiny`, `rule-of-five`)
|
||||
- Stored in permanent beads with `template` label
|
||||
- Can be composed into larger protos via formula algebra (extends, compose)
|
||||
|
||||
### Mol (Liquid Phase)
|
||||
|
||||
Mols are **flowing work instances** - live executions that adapt as steps
|
||||
complete, status changes, and work evolves.
|
||||
|
||||
**Properties:**
|
||||
- Identified by head bead ID (e.g., `bd-abc123`)
|
||||
- Dynamic - steps transition through states
|
||||
- Persistent - survives sessions, crashes, context compaction
|
||||
- Auditable - full history in beads
|
||||
|
||||
### Wisp (Vapor Phase)
|
||||
|
||||
Wisps are **ephemeral execution traces** - the steam that rises during work
|
||||
and dissipates when done.
|
||||
|
||||
**Properties:**
|
||||
- Stored in `.beads-wisp/` (gitignored, never synced)
|
||||
- Single-cycle lifetime
|
||||
- Either evaporates (burn) or condenses to digest (squash)
|
||||
- Used for patrol cycles, operational loops, routine work
|
||||
|
||||
## Phase Transition Operators
|
||||
|
||||
Work transitions between phases through specific operators:
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ PROTO │
|
||||
│ (solid) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
pour wisp distill
|
||||
│ │ ↑
|
||||
▼ ▼ │
|
||||
┌───────────────┐ ┌───────────────┐ │
|
||||
│ MOL │ │ WISP │ │
|
||||
│ (liquid) │ │ (vapor) │ │
|
||||
└───────┬───────┘ └───────┬───────┘ │
|
||||
│ │ │
|
||||
squash squash │
|
||||
│ │ │
|
||||
▼ ▼ │
|
||||
┌───────────────┐ ┌───────────────┐ │
|
||||
│ DIGEST │ │ (evaporates) │ │
|
||||
│ (condensed) │ │ or burn │ │
|
||||
└───────────────┘ └───────────────┘ │
|
||||
│ │
|
||||
└────────────────────────────┘
|
||||
(experience crystallizes)
|
||||
```
|
||||
|
||||
### Pour: Solid → Liquid
|
||||
|
||||
**Pour** instantiates a proto into a persistent mol - like pouring molten metal
|
||||
into a mold to create a solid casting that will flow through the workflow.
|
||||
|
||||
```bash
|
||||
bd pour mol-feature # Create mol from proto
|
||||
bd pour mol-feature --var version=1.0 # With variable substitution
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Feature work that spans sessions
|
||||
- Important work needing audit trail
|
||||
- Anything you might need to reference later
|
||||
|
||||
### Wisp: Solid → Vapor (Sublimation)
|
||||
|
||||
**Wisp** instantiates a proto directly into vapor - sublimation that skips
|
||||
the liquid phase for ephemeral, operational work.
|
||||
|
||||
```bash
|
||||
bd wisp mol-patrol # Create wisp from proto
|
||||
bd wisp mol-health-check # Ephemeral operational task
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Patrol cycles (deacon, witness)
|
||||
- Health checks and monitoring
|
||||
- One-shot orchestration runs
|
||||
- Routine operations with no audit value
|
||||
|
||||
### Squash: Liquid/Vapor → Condensed
|
||||
|
||||
**Squash** condenses work into a permanent digest - the outcome crystallizes
|
||||
while the execution trace compresses or evaporates.
|
||||
|
||||
```bash
|
||||
bd mol squash bd-abc123 # Squash mol to digest
|
||||
bd mol squash bd-abc123 --summary="Completed auth" # With summary
|
||||
```
|
||||
|
||||
**For mols:** Creates digest in permanent beads, preserves full outcome
|
||||
**For wisps:** Creates digest, deletes wisp (vapor condenses to residue)
|
||||
|
||||
### Burn: Vapor → Nothing
|
||||
|
||||
**Burn** discards a wisp without creating a digest - the steam simply
|
||||
evaporates with no residue.
|
||||
|
||||
```bash
|
||||
bd mol burn wisp-123 # Discard without digest
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Routine patrol cycles with nothing notable
|
||||
- Failed attempts that don't need recording
|
||||
- Test runs
|
||||
|
||||
### Distill: Liquid → Solid (Crystallization)
|
||||
|
||||
**Distill** extracts a reusable proto from an existing mol or epic - the
|
||||
reverse of pour. Experience crystallizes into a template.
|
||||
|
||||
```bash
|
||||
bd mol distill bd-abc123 --as "Release Workflow"
|
||||
bd mol distill bd-abc123 --var feature=auth --var version=1.0
|
||||
```
|
||||
|
||||
**Process:**
|
||||
1. Analyze the existing work structure
|
||||
2. Extract the pattern (steps, dependencies)
|
||||
3. Replace concrete values with `{{variable}}` placeholders
|
||||
4. Crystallize as a new proto
|
||||
|
||||
**Use cases:**
|
||||
- Team develops a good workflow organically, wants to reuse it
|
||||
- Capture tribal knowledge as executable templates
|
||||
- Create starting points for similar future work
|
||||
|
||||
## The Polymorphic Bond Operator
|
||||
|
||||
**Bond** is Gas Town's polymorphic combiner for artifacts. It operates at the
|
||||
artifact level (post-cooking), handling different operand types with phase-aware
|
||||
behavior.
|
||||
|
||||
### The Bond Table (Symmetric)
|
||||
|
||||
| bond | Proto | Mol | Wisp |
|
||||
|------|-------|-----|------|
|
||||
| **Proto** | Compound Proto | Pour, attach | Wisp, attach |
|
||||
| **Mol** | Pour, attach | Link via edges | Link via edges |
|
||||
| **Wisp** | Wisp, attach | Link via edges | Link via edges |
|
||||
|
||||
The table is symmetric: bonding A+B produces the same structure as B+A.
|
||||
|
||||
**Bond** handles different operand types with different phase behaviors:
|
||||
|
||||
### Bond: Proto + Proto → Compound Proto
|
||||
|
||||
Two solid templates fuse into a larger solid template.
|
||||
|
||||
```bash
|
||||
bd mol bond mol-review mol-deploy --as "Review and Deploy"
|
||||
```
|
||||
|
||||
Creates a compound proto that includes both workflows. The result is a
|
||||
reusable template (solid phase).
|
||||
|
||||
### Bond: Proto + Mol → Pour + Attach
|
||||
|
||||
A solid template melts into an existing liquid workflow.
|
||||
|
||||
```bash
|
||||
bd mol bond mol-hotfix bd-feature-123
|
||||
```
|
||||
|
||||
The proto is instantiated (as liquid by default) and attached to the
|
||||
existing mol. The new issues become part of the flowing work.
|
||||
|
||||
### Bond: Proto + Wisp → Wisp + Attach
|
||||
|
||||
A solid template sublimates into an existing vapor workflow.
|
||||
|
||||
```bash
|
||||
bd mol bond mol-extra-check wisp-patrol-456
|
||||
```
|
||||
|
||||
The proto is created as a wisp (following the target's phase) and attaches.
|
||||
|
||||
### Bond: Mol + Mol → Compound Mol
|
||||
|
||||
Two liquid workflows merge into a larger flowing structure.
|
||||
|
||||
```bash
|
||||
bd mol bond bd-feature-123 bd-related-456
|
||||
```
|
||||
|
||||
Links them via dependency edges. Both continue flowing.
|
||||
|
||||
### Bond: Wisp + Wisp → Compound Wisp
|
||||
|
||||
Two vapor traces merge into a larger ephemeral cloud.
|
||||
|
||||
```bash
|
||||
bd mol bond wisp-123 wisp-456
|
||||
```
|
||||
|
||||
### Phase Override Flags
|
||||
|
||||
Bond's creation behavior can be overridden:
|
||||
|
||||
```bash
|
||||
# Force liquid when attaching to wisp (found something important!)
|
||||
bd mol bond mol-critical-bug wisp-patrol --pour
|
||||
|
||||
# Force vapor when attaching to mol (ephemeral diagnostic)
|
||||
bd mol bond mol-temp-check bd-feature --wisp
|
||||
```
|
||||
|
||||
| Flag | Effect | Use Case |
|
||||
|------|--------|----------|
|
||||
| `--pour` | Force creation as liquid | "This matters, persist it" |
|
||||
| `--wisp` | Force creation as vapor | "This is ephemeral, let it evaporate" |
|
||||
|
||||
### Cross-Phase Bonding
|
||||
|
||||
What happens when you bond liquid and vapor directly?
|
||||
|
||||
```bash
|
||||
bd mol bond bd-feature-123 wisp-456 # Mol + Wisp
|
||||
```
|
||||
|
||||
**Answer: Reference-only linking.** They connect via dependency edges but
|
||||
stay in their respective stores. No phase change occurs - you're linking
|
||||
across the phase boundary without forcing conversion.
|
||||
|
||||
This enables patterns like:
|
||||
- Patrol wisp discovers issue → creates liquid mol for the fix
|
||||
- Feature mol needs diagnostic → creates vapor wisp for the check
|
||||
- The reference survives even when the wisp evaporates (ID stable)
|
||||
|
||||
## Agent Attachment: Hooks and Pins
|
||||
|
||||
Agents need work attached to them. In Gas Town, this uses **hooks** and **pins**.
|
||||
|
||||
### The Hook
|
||||
|
||||
Each agent has a **hook** - an anchor point where work hangs. It's the
|
||||
agent's "pinned bead" - the top of their inbox, the work they're focused on.
|
||||
|
||||
```bash
|
||||
bd hook # Show what's on my hook
|
||||
bd hook --agent deacon # Show deacon's hook
|
||||
```
|
||||
|
||||
**Hook states:**
|
||||
- **Empty (naked)**: Agent awaiting work assignment
|
||||
- **Occupied**: Agent has work to execute
|
||||
- **Multiple**: Agent managing several concurrent mols (rare)
|
||||
|
||||
### Pin: Attaching Work to Agents
|
||||
|
||||
**Pin** attaches a mol to an agent's hook - the action of assigning work.
|
||||
|
||||
```bash
|
||||
bd pin bd-feature-123 # Pin to my hook
|
||||
bd pin bd-feature-123 --for witness # Pin to specific agent's hook
|
||||
bd unpin # Detach current work
|
||||
```
|
||||
|
||||
**The Witness → Polecat flow:**
|
||||
|
||||
```bash
|
||||
# Witness assigns work to polecat
|
||||
bd pour mol-feature # Create liquid mol
|
||||
bd pin bd-abc123 --for polecat-ace # Hang on polecat's hook
|
||||
gt nudge polecat-ace # Wake the polecat
|
||||
```
|
||||
|
||||
### Wisps Don't Need Pinning
|
||||
|
||||
Wisps are single-cycle and don't survive session boundaries in the
|
||||
traditional sense. Agents hold them in working memory for one cycle:
|
||||
|
||||
```bash
|
||||
# Deacon creates patrol wisp (no pin needed)
|
||||
bd wisp mol-deacon-patrol # Create vapor
|
||||
# ... execute steps ...
|
||||
bd mol squash <id> --summary="..." # Condense and dissipate
|
||||
# Loop
|
||||
```
|
||||
|
||||
## The Epic-Mol Relationship
|
||||
|
||||
Epics and mols are **isomorphic** but represent different mental models.
|
||||
|
||||
### Epic: The Business View
|
||||
|
||||
An epic is a **simple mol shape** - essentially a TODO list:
|
||||
|
||||
```
|
||||
epic-root
|
||||
├── child.1
|
||||
├── child.2
|
||||
├── child.3
|
||||
└── child.4
|
||||
(flat list, no sibling dependencies, execution order implicit)
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- One level of children via `.N` numbering
|
||||
- No explicit serial/parallel encoding
|
||||
- Human-readable, business-oriented
|
||||
- The natural shape most humans create
|
||||
|
||||
### Mol: The Chemistry View
|
||||
|
||||
A mol is the **general case** - arbitrary graphs with explicit workflow
|
||||
semantics:
|
||||
|
||||
```
|
||||
mol-root
|
||||
├── phase-A (epic)
|
||||
│ ├── task.1 ───blocks──→ task.2 (serial)
|
||||
│ └── task.3 (parallel with task.1)
|
||||
├── phase-B (epic) ←───blocked-by─── phase-A
|
||||
│ └── ...
|
||||
└── standalone (fanout)
|
||||
(arbitrary DAG, explicit dependencies encode serial/parallel)
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- Can contain multiple epics as subgraphs
|
||||
- Dependency edges encode execution order
|
||||
- `blocks` = serial (bottleneck)
|
||||
- No dep = parallel (fanout)
|
||||
- `conditional` = if-fail path
|
||||
|
||||
### The Relationship
|
||||
|
||||
**All epics are mols. Not all mols are epics.**
|
||||
|
||||
| Aspect | Epic | Mol |
|
||||
|--------|------|-----|
|
||||
| Shape | Flat (root + children) | Arbitrary DAG |
|
||||
| Dependencies | Implicit in ordering | Explicit edges |
|
||||
| Parallelism | Assumed parallel | Encoded in structure |
|
||||
| Mental model | TODO list | Workflow graph |
|
||||
| Common use | Simple feature work | Complex orchestration |
|
||||
|
||||
When you `distill` an epic, you get a simple proto.
|
||||
When you `distill` a complex mol, you get a complex proto (preserving structure).
|
||||
|
||||
## Thermodynamic Properties
|
||||
|
||||
Gas Town's chemistry has thermodynamic properties - work is energy flowing
|
||||
through the system.
|
||||
|
||||
### Work as Energy
|
||||
|
||||
```
|
||||
Proto (potential energy) → Pour/Wisp → Mol/Wisp (kinetic energy) → Squash → Digest (stored work)
|
||||
```
|
||||
|
||||
- **Protos** store potential energy - the capability to do work
|
||||
- **Mols/Wisps** are kinetic - work actively flowing
|
||||
- **Digests** are stored energy - crystallized outcomes
|
||||
|
||||
### The Audit Trail as Entropy
|
||||
|
||||
Every execution increases entropy - creating more history, more records,
|
||||
more state. Gas Town manages this through:
|
||||
|
||||
- **Wisps**: High entropy, but evaporates (entropy contained)
|
||||
- **Squash**: Compresses entropy into minimal digest
|
||||
- **Distill**: Reduces entropy by extracting reusable pattern
|
||||
|
||||
### The CV Chain
|
||||
|
||||
Every agent has a **chain** of work - their CV:
|
||||
|
||||
```
|
||||
Agent CV = ∑(digests) = crystallized capability proof
|
||||
```
|
||||
|
||||
Work completed → digest created → agent's chain grows → capability demonstrated.
|
||||
|
||||
This is the foundation for capability-based work matching: your work
|
||||
history IS your resume. The ledger speaks.
|
||||
|
||||
## The Complete Lifecycle
|
||||
|
||||
### Feature Work (Liquid Path)
|
||||
|
||||
```bash
|
||||
# 1. Create work from template
|
||||
bd pour mol-feature --var name=auth
|
||||
|
||||
# 2. Pin to agent
|
||||
bd pin bd-abc123 --for polecat-ace
|
||||
|
||||
# 3. Agent executes steps
|
||||
bd update bd-abc123.design --status=in_progress
|
||||
# ... cognition ...
|
||||
bd close bd-abc123.design
|
||||
bd update bd-abc123.implement --status=in_progress
|
||||
# ... and so on
|
||||
|
||||
# 4. Squash when complete
|
||||
bd mol squash bd-abc123 --summary="Implemented auth feature"
|
||||
|
||||
# 5. Digest remains in permanent ledger
|
||||
```
|
||||
|
||||
### Patrol Work (Vapor Path)
|
||||
|
||||
```bash
|
||||
# 1. Create wisp (no pin needed)
|
||||
bd wisp mol-deacon-patrol
|
||||
|
||||
# 2. Execute cycle steps
|
||||
bd close <step-1>
|
||||
bd close <step-2>
|
||||
# ...
|
||||
|
||||
# 3. Generate summary and squash
|
||||
bd mol squash <wisp-id> --summary="Patrol complete, no issues"
|
||||
|
||||
# 4. Loop
|
||||
bd wisp mol-deacon-patrol
|
||||
# ...
|
||||
```
|
||||
|
||||
### Template Creation (Distillation)
|
||||
|
||||
```bash
|
||||
# 1. Complete some work organically
|
||||
# ... team develops release workflow over several iterations ...
|
||||
|
||||
# 2. Distill the pattern
|
||||
bd mol distill bd-release-v3 --as "Release Workflow"
|
||||
|
||||
# 3. Result: new proto available
|
||||
bd pour mol-release-workflow --var version=2.0
|
||||
```
|
||||
|
||||
## Polymers: Large-Scale Composition
|
||||
|
||||
Protos can compose into arbitrarily large **polymers** - chains of molecules
|
||||
that encode complex multi-phase work.
|
||||
|
||||
```bash
|
||||
# Create polymer from multiple protos
|
||||
bd mol bond mol-design mol-implement --as "Design and Implement"
|
||||
bd mol bond mol-design-implement mol-test --as "Full Dev Cycle"
|
||||
bd mol bond mol-full-dev mol-deploy --as "End to End"
|
||||
```
|
||||
|
||||
**Polymer properties:**
|
||||
- Preserve phase relationships from constituent protos
|
||||
- Can encode hours, days, or weeks of work
|
||||
- Enable "weekend warrior" autonomous operation
|
||||
- Beads tracks progress; agents execute; humans sleep
|
||||
|
||||
### The Cognition Sausage Machine
|
||||
|
||||
A large polymer is a **cognition sausage machine**:
|
||||
|
||||
```
|
||||
Proto Polymer (input)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GAS TOWN ENGINE │
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
│ │Pole │ │Pole │ │Pole │ │Pole │ │
|
||||
│ │cat │ │cat │ │cat │ │cat │ │
|
||||
│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └────────┴────────┴────────┘ │
|
||||
│ ↓ │
|
||||
│ Merge Queue │
|
||||
│ ↓ │
|
||||
│ Refinery │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Completed Work + Digests (output)
|
||||
```
|
||||
|
||||
Feed in a polymer. Get back completed features, merged PRs, and audit trail.
|
||||
|
||||
## The Proto Library
|
||||
|
||||
Gas Town maintains a **library of curated protos** - the fuel stockpile:
|
||||
|
||||
```
|
||||
~/gt/molecules/
|
||||
├── mol-shiny/ # Full quality workflow
|
||||
├── mol-quick-fix/ # Fast path for small changes
|
||||
├── mol-code-review/ # Pluggable review dimensions
|
||||
├── mol-release/ # Release workflow
|
||||
├── mol-deacon-patrol/ # Deacon monitoring cycle
|
||||
├── mol-witness-patrol/ # Witness worker monitoring
|
||||
└── mol-polecat-work/ # Standard polecat lifecycle
|
||||
```
|
||||
|
||||
**Library operations:**
|
||||
|
||||
```bash
|
||||
bd mol list # List available protos
|
||||
bd mol show mol-code-review # Show proto details
|
||||
bd pour mol-code-review # Instantiate for use
|
||||
```
|
||||
|
||||
### Curated vs Organic
|
||||
|
||||
- **Curated protos**: Refined templates in the library, battle-tested
|
||||
- **Organic protos**: Distilled from real work, may need refinement
|
||||
- **Path**: Organic → refine → curate → library
|
||||
|
||||
## Digest ID Stability
|
||||
|
||||
When a wisp is created, its head bead ID is **reserved** in permanent storage.
|
||||
This ensures cross-phase references remain valid:
|
||||
|
||||
```
|
||||
1. bd wisp mol-patrol → Creates wisp-123 (ID reserved)
|
||||
2. bd mol bond ... wisp-123 → Reference created
|
||||
3. bd mol squash wisp-123 → Digest takes same ID
|
||||
4. Reference still valid → Points to digest now
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- On wisp creation: Write placeholder/tombstone to permanent beads
|
||||
- On squash: Replace placeholder with actual digest
|
||||
- Cross-phase references never break
|
||||
|
||||
## Dynamic Bonding: The Christmas Ornament Pattern
|
||||
|
||||
Static molecules have fixed steps defined at design time. But some workflows
|
||||
need **dynamic structure** - steps that emerge at runtime based on discovered work.
|
||||
|
||||
### The Problem
|
||||
|
||||
Consider mol-witness-patrol. The Witness monitors N polecats where N varies:
|
||||
- Sometimes 0 polecats (quiet rig)
|
||||
- Sometimes 8 polecats (busy swarm)
|
||||
- Polecats come and go during the patrol
|
||||
|
||||
A static molecule can't express "for each polecat, do these steps."
|
||||
|
||||
### The Solution: Dynamic Bond
|
||||
|
||||
The **bond** operator becomes a runtime instantiator:
|
||||
|
||||
```bash
|
||||
# In survey-workers step:
|
||||
for polecat in $(gt polecat list gastown); do
|
||||
bd mol bond mol-polecat-arm $PATROL_WISP_ID \
|
||||
--var polecat_name=$polecat \
|
||||
--var rig=gastown
|
||||
done
|
||||
```
|
||||
|
||||
Each bond creates a **wisp child** under the patrol molecule:
|
||||
- `patrol-x7k.arm-ace` (5 steps)
|
||||
- `patrol-x7k.arm-nux` (5 steps)
|
||||
- `patrol-x7k.arm-toast` (5 steps)
|
||||
|
||||
### The Christmas Ornament Shape
|
||||
|
||||
```
|
||||
★ mol-witness-patrol (trunk)
|
||||
/|\
|
||||
/ | \
|
||||
┌─────────┘ │ └─────────┐
|
||||
│ │ │
|
||||
PREFLIGHT DISCOVERY CLEANUP
|
||||
│ │ │
|
||||
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
|
||||
│inbox │ │survey │ │aggreg │
|
||||
│refnry │ │ │ │save │
|
||||
│load │ │ │ │summary│
|
||||
└───────┘ └───┬───┘ │contxt │
|
||||
│ │loop │
|
||||
┌─────────┼─────────┐ └───────┘
|
||||
│ │ │
|
||||
● ● ● mol-polecat-arm
|
||||
ace nux toast
|
||||
│ │ │
|
||||
┌──┴──┐ ┌──┴──┐ ┌──┴──┐
|
||||
│cap │ │cap │ │cap │
|
||||
│ass │ │ass │ │ass │
|
||||
│dec │ │dec │ │dec │
|
||||
│exec │ │exec │ │exec │
|
||||
└──┬──┘ └──┬──┘ └──┬──┘
|
||||
│ │ │
|
||||
└─────────┴─────────┘
|
||||
│
|
||||
⬣ base (cleanup)
|
||||
```
|
||||
|
||||
The ornament **hangs from the Witness's pinned bead**. The star is the patrol
|
||||
head (preflight steps). Arms grow dynamically as polecats are discovered.
|
||||
The base (cleanup) runs after all arms complete.
|
||||
|
||||
### The WaitsFor Directive
|
||||
|
||||
A step that follows dynamic bonding needs to **wait for all children**:
|
||||
|
||||
```markdown
|
||||
## Step: aggregate
|
||||
Collect outcomes from all polecat inspection arms.
|
||||
WaitsFor: all-children
|
||||
Needs: survey-workers
|
||||
```
|
||||
|
||||
The `WaitsFor: all-children` directive makes this a **fanout gate** - it can't
|
||||
proceed until ALL dynamically-bonded children complete.
|
||||
|
||||
### Parallelism
|
||||
|
||||
Arms execute in **parallel**. Within an arm, steps are sequential:
|
||||
|
||||
```
|
||||
survey-workers ─┬─ arm-ace ─┬─ aggregate
|
||||
│ (seq) │
|
||||
├─ arm-nux ─┤ (all arms parallel)
|
||||
│ (seq) │
|
||||
└─ arm-toast┘
|
||||
```
|
||||
|
||||
Agents can use subagents (Task tool) to work multiple arms simultaneously.
|
||||
|
||||
### The Activity Feed
|
||||
|
||||
Dynamic bonding enables a **real-time activity feed** - structured work state
|
||||
instead of agent logs:
|
||||
|
||||
```
|
||||
[14:32:01] ✓ patrol-x7k.inbox-check completed
|
||||
[14:32:03] ✓ patrol-x7k.check-refinery completed
|
||||
[14:32:07] → patrol-x7k.survey-workers in_progress
|
||||
[14:32:08] + patrol-x7k.arm-ace bonded (5 steps)
|
||||
[14:32:08] + patrol-x7k.arm-nux bonded (5 steps)
|
||||
[14:32:08] + patrol-x7k.arm-toast bonded (5 steps)
|
||||
[14:32:08] ✓ patrol-x7k.survey-workers completed
|
||||
[14:32:09] → patrol-x7k.arm-ace.capture in_progress
|
||||
[14:32:10] ✓ patrol-x7k.arm-ace.capture completed
|
||||
[14:32:14] ✓ patrol-x7k.arm-ace.decide completed (action: nudge-1)
|
||||
[14:32:17] ✓ patrol-x7k.arm-ace COMPLETE
|
||||
[14:32:23] ✓ patrol-x7k SQUASHED → digest-x7k
|
||||
```
|
||||
|
||||
This is what you want to see. Not logs. **WORK STATE.**
|
||||
|
||||
The beads ledger becomes a real-time activity feed. Control plane IS data plane.
|
||||
|
||||
### Variable Substitution
|
||||
|
||||
Bonded molecules support variable substitution:
|
||||
|
||||
```markdown
|
||||
## Molecule: polecat-arm
|
||||
Inspection cycle for {{polecat_name}} in {{rig}}.
|
||||
|
||||
## Step: capture
|
||||
Capture tmux output for {{polecat_name}}.
|
||||
```bash
|
||||
tmux capture-pane -t gt-{{rig}}-{{polecat_name}} -p | tail -50
|
||||
```
|
||||
|
||||
Variables are resolved at bond time, creating concrete wisp steps.
|
||||
|
||||
### Squash Behavior
|
||||
|
||||
At patrol end:
|
||||
- **Notable events**: Squash to digest with summary
|
||||
- **Routine cycle**: Burn without digest
|
||||
|
||||
All arm wisps are children of the patrol wisp - they squash/burn together.
|
||||
|
||||
### The Mol Mall
|
||||
|
||||
mol-polecat-arm is **swappable** via variable:
|
||||
|
||||
```markdown
|
||||
## Step: survey-workers
|
||||
For each polecat, bond: {{arm_molecule | default: mol-polecat-arm}}
|
||||
```
|
||||
|
||||
Install alternatives from the Mol Mall:
|
||||
- `mol-polecat-arm-enterprise` (compliance checks)
|
||||
- `mol-polecat-arm-secure` (credential scanning)
|
||||
- `mol-polecat-arm-ml` (ML-based stuck detection)
|
||||
|
||||
## Summary of Operators
|
||||
|
||||
| Operator | From | To | Effect |
|
||||
|----------|------|------|--------|
|
||||
| `pour` | Proto | Mol | Instantiate as persistent liquid |
|
||||
| `wisp` | Proto | Wisp | Instantiate as ephemeral vapor |
|
||||
| `bond` | Any + Any | Compound | Combine (polymorphic) |
|
||||
| `squash` | Mol/Wisp | Digest | Condense to permanent record |
|
||||
| `burn` | Wisp | Nothing | Discard without record |
|
||||
| `distill` | Mol/Epic | Proto | Extract reusable template |
|
||||
| `pin` | Mol | Agent | Attach work to agent's hook |
|
||||
|
||||
## Design Implications
|
||||
|
||||
### For Beads
|
||||
|
||||
1. **Commands**: `bd pour`, `bd wisp`, `bd pin`
|
||||
2. **Flags**: `--pour` forces liquid phase when bonding
|
||||
3. **Wisp storage**: `.beads-wisp/` directory, gitignored
|
||||
4. **Digest ID reservation**: Placeholder in permanent store on wisp creation
|
||||
|
||||
### For Gas Town
|
||||
|
||||
1. **Daemon**: Don't attach permanent molecules for patrol roles
|
||||
2. **Deacon template**: Use `bd wisp mol-deacon-patrol` pattern
|
||||
3. **Polecat lifecycle**: Consider wisp-based with digest on completion
|
||||
4. **Hook inspection**: `bd hook` command for debugging
|
||||
|
||||
### For Agents
|
||||
|
||||
1. **Polecats**: Receive pinned mols, execute, squash, request shutdown
|
||||
2. **Patrol roles**: Create wisps, execute cycle, squash, loop
|
||||
3. **Recovery**: Re-read beads state, continue from last completed step
|
||||
|
||||
---
|
||||
|
||||
## Appendix: The Vocabulary
|
||||
|
||||
### Lifecycle Phases
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **Rig** | Compose formulas at source level (authoring time) |
|
||||
| **Cook** | Transform formula to proto (compile time) |
|
||||
| **Run** | Execute mol/wisp steps (agent execution time) |
|
||||
|
||||
### Artifacts
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **Formula** | Source YAML defining workflow with composition rules |
|
||||
| **Proto** | Frozen template molecule (solid phase, cooked) |
|
||||
| **Mol** | Flowing work instance (liquid phase) |
|
||||
| **Wisp** | Ephemeral execution trace (vapor phase) |
|
||||
| **Digest** | Condensed permanent record |
|
||||
| **Polymer** | Large composed proto chain |
|
||||
| **Epic** | Simple mol shape (flat TODO list) |
|
||||
|
||||
### Operators
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **Pour** | Instantiate proto as mol (solid → liquid) |
|
||||
| **Wisp** (verb) | Instantiate proto as wisp (solid → vapor) |
|
||||
| **Bond** | Combine artifacts (polymorphic, symmetric) |
|
||||
| **Squash** | Condense to digest |
|
||||
| **Burn** | Discard wisp without digest |
|
||||
| **Distill** | Extract proto from experience |
|
||||
|
||||
### Agent Mechanics
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **Hook** | Agent's attachment point for work |
|
||||
| **Pin** | Attach mol to agent's hook |
|
||||
| **Sling** | Cook + assign to agent hook |
|
||||
|
||||
---
|
||||
|
||||
*The chemistry is the interface. The ledger is the truth. The work gets done.*
|
||||
@@ -1,729 +0,0 @@
|
||||
# Molecule Algebra: A Composition Language for Work (MEOW)
|
||||
|
||||
> Status: Design Spec v1 - December 2024
|
||||
>
|
||||
> "From 'issues in git' to a work composition algebra in 10 weeks."
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines **MEOW** - the **M**olecular **E**xpression **O**f **W**ork -
|
||||
a declarative algebra for composing, transforming, and executing structured work.
|
||||
The algebra enables mechanical composition of workflows without AI, reserving
|
||||
cognition for leaf-node execution only.
|
||||
|
||||
**Key insight**: Structure is computation. Content is cognition. They're separate.
|
||||
|
||||
```
|
||||
Molecules = Graph Algebra (mechanical, gt executes)
|
||||
Steps = AI Cognition (agent provides)
|
||||
```
|
||||
|
||||
## The Three Phases: Rig, Cook, Run
|
||||
|
||||
Gas Town work flows through three phases:
|
||||
|
||||
```
|
||||
RIG ────→ COOK ────→ RUN
|
||||
```
|
||||
|
||||
| Phase | What Happens | Operator | Output |
|
||||
|-------|--------------|----------|--------|
|
||||
| **Rig** | Compose formulas (source level) | extends, compose | Compound Formula |
|
||||
| **Cook** | Instantiate work | cook, pour, wisp | Proto, Mol, Wisp |
|
||||
| **Run** | Execute steps | (agent execution) | Work done |
|
||||
|
||||
See [molecular-chemistry.md](molecular-chemistry.md) for the full specification.
|
||||
|
||||
## Formulas and Cooking
|
||||
|
||||
**Formulas** are the source code; **rigging** composes them; **cooking**
|
||||
produces executable artifacts.
|
||||
|
||||
### The Artifact Tiers
|
||||
|
||||
```
|
||||
Formula (.formula.yaml) ← Source code
|
||||
↓ rig (compose) ← Source-level composition
|
||||
Compound Formula ← Combined source
|
||||
↓ cook ← Pre-expand, flatten
|
||||
Proto (frozen in beads) ← Compiled, flat graph
|
||||
↓ pour/wisp ← Instantiate
|
||||
Mol/Wisp (running) ← The work flowing
|
||||
```
|
||||
|
||||
| Tier | Name | Format | Nature |
|
||||
|------|------|--------|--------|
|
||||
| Source | **Formula** | YAML | Composable via `extends`/`compose` |
|
||||
| Compiled | **Proto** | Beads issue | Frozen, flat graph, fast instantiation |
|
||||
| Running | **Mol/Wisp** | Beads issue | Active, flowing work |
|
||||
|
||||
### Two Composition Operators
|
||||
|
||||
| Operator | Level | Inputs | Output |
|
||||
|----------|-------|--------|--------|
|
||||
| **Rig** | Source | Formula + Formula | Compound Formula |
|
||||
| **Bond** | Artifact | Proto/Mol/Wisp + any | Combined artifact |
|
||||
|
||||
**Rig** is source-level composition (formula YAML with `extends`, `compose`).
|
||||
**Bond** is artifact-level composition (combining cooked protos, linking mols).
|
||||
|
||||
See the Bond Table in [molecular-chemistry.md](molecular-chemistry.md) for full semantics.
|
||||
|
||||
### Why Cook?
|
||||
|
||||
Cooking **pre-expands** all composition at "compile time":
|
||||
- Macros (Rule of Five) expand to flat steps
|
||||
- Aspects apply to matching pointcuts
|
||||
- Branches and gates wire up
|
||||
- Result: pure step graph with no interpretation needed
|
||||
|
||||
Instantiation then becomes pure **copy + variable substitution**. Fast, mechanical,
|
||||
deterministic.
|
||||
|
||||
### Formula Format
|
||||
|
||||
Formulas are YAML files with `.formula.yaml` extension. YAML for human
|
||||
readability (humans author these; agents cook them):
|
||||
|
||||
```yaml
|
||||
formula: shiny
|
||||
description: Engineer in a Box - the canonical right way
|
||||
version: 1
|
||||
steps:
|
||||
- id: design
|
||||
description: Think carefully about architecture
|
||||
- id: implement
|
||||
needs: [design]
|
||||
- id: review
|
||||
needs: [implement]
|
||||
- id: test
|
||||
needs: [review]
|
||||
- id: submit
|
||||
needs: [test]
|
||||
```
|
||||
|
||||
Formulas with composition:
|
||||
|
||||
```yaml
|
||||
formula: shiny-enterprise
|
||||
extends: shiny
|
||||
version: 1
|
||||
compose:
|
||||
- expand:
|
||||
target: implement
|
||||
with: rule-of-five
|
||||
- aspect:
|
||||
pointcut: "implement.*"
|
||||
with: security-audit
|
||||
- gate:
|
||||
before: submit
|
||||
condition: "security-postscan.approved"
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
# Cook a formula into a proto
|
||||
bd cook shiny-enterprise
|
||||
# "Cooking shiny-enterprise..."
|
||||
# "✓ Cooked proto: shiny-enterprise (30 steps)"
|
||||
|
||||
# Preview without saving
|
||||
bd cook shiny-enterprise --dry-run
|
||||
|
||||
# List available formulas
|
||||
bd formula list
|
||||
|
||||
# Show formula details
|
||||
bd formula show rule-of-five
|
||||
|
||||
# Instantiate the cooked proto
|
||||
bd pour shiny-enterprise --var feature="auth"
|
||||
```
|
||||
|
||||
### Formula Storage
|
||||
|
||||
```
|
||||
~/.beads/formulas/ # User formulas
|
||||
~/gt/.beads/formulas/ # Town formulas
|
||||
.beads/formulas/ # Project formulas
|
||||
```
|
||||
|
||||
## The Phases of Matter
|
||||
|
||||
Work in Gas Town exists in three phases:
|
||||
|
||||
| Phase | Name | Storage | Lifecycle | Use Case |
|
||||
|-------|------|---------|-----------|----------|
|
||||
| **Solid** | Proto | `.beads/` (template) | Frozen, reusable | Workflow patterns |
|
||||
| **Liquid** | Mol | `.beads/` | Flowing, persistent | Project work, audit trail |
|
||||
| **Vapor** | Wisp | `.beads-wisp/` | Ephemeral, evaporates | Execution scaffolding |
|
||||
|
||||
### Phase Transitions
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ PROTO │
|
||||
│ (solid) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
pour wisp bond
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ MOL │ │ WISP │ │ COMPOUND │
|
||||
│ (liquid) │ │ (vapor) │ │ PROTO │
|
||||
└───────┬───────┘ └───────┬───────┘ └───────────────┘
|
||||
│ │
|
||||
squash squash/burn
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐
|
||||
│ DIGEST │ │ (evaporates) │
|
||||
│ (condensed) │ │ or digest │
|
||||
└───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
### Phase Verbs
|
||||
|
||||
| Verb | Transition | Effect |
|
||||
|------|------------|--------|
|
||||
| `pour` | solid → liquid | Create persistent mol |
|
||||
| `wisp` | solid → vapor | Create ephemeral wisp |
|
||||
| `bond` | any + any | Combine (polymorphic) |
|
||||
| `squash` | liquid/vapor → digest | Condense to record |
|
||||
| `burn` | vapor → nothing | Discard without record |
|
||||
|
||||
## The Mol/Wisp Decision
|
||||
|
||||
**"Is this the work, or is this wrapping the work?"**
|
||||
|
||||
| Pour when... | Wisp when... |
|
||||
|---------------------|----------------------|
|
||||
| This IS the work item | This SHAPES execution |
|
||||
| Multiple agents coordinate | Single agent executes |
|
||||
| Stakeholders track progress | Only outcome matters |
|
||||
| Cross-rig visibility needed | Local execution detail |
|
||||
| CV/audit trail value | Scaffolding, process |
|
||||
|
||||
The `--on` flag implies wisp: `gt sling shiny gastown/Toast --on gt-abc123`
|
||||
|
||||
## Graph Primitives
|
||||
|
||||
### Steps
|
||||
|
||||
A step is a unit of work with:
|
||||
- `id`: Unique identifier within molecule
|
||||
- `description`: What to do (consumed by agent)
|
||||
- `needs`: Dependencies (steps that must complete first)
|
||||
- `output`: Structured result (available to conditions)
|
||||
|
||||
```yaml
|
||||
- id: implement
|
||||
description: Write the authentication module
|
||||
needs: [design]
|
||||
```
|
||||
|
||||
### Edges
|
||||
|
||||
Dependencies between steps:
|
||||
- `needs`: Hard dependency (must complete first)
|
||||
- `blocks`: Inverse of needs (this step blocks that one)
|
||||
- `conditional`: Only if condition met
|
||||
|
||||
### Molecules
|
||||
|
||||
A molecule is a DAG of steps:
|
||||
|
||||
```yaml
|
||||
molecule: shiny
|
||||
description: Engineer in a Box - the canonical right way
|
||||
|
||||
steps:
|
||||
- id: design
|
||||
description: Think carefully about architecture
|
||||
|
||||
- id: implement
|
||||
description: Write the code
|
||||
needs: [design]
|
||||
|
||||
- id: review
|
||||
description: Code review
|
||||
needs: [implement]
|
||||
|
||||
- id: test
|
||||
description: Run tests
|
||||
needs: [review]
|
||||
|
||||
- id: submit
|
||||
description: Submit for merge
|
||||
needs: [test]
|
||||
```
|
||||
|
||||
## Composition Operators
|
||||
|
||||
### Sequential Composition
|
||||
|
||||
```yaml
|
||||
# A then B
|
||||
sequence:
|
||||
- step-a
|
||||
- step-b
|
||||
```
|
||||
|
||||
Or implicitly via `needs`:
|
||||
```yaml
|
||||
- id: b
|
||||
needs: [a]
|
||||
```
|
||||
|
||||
### Parallel Composition
|
||||
|
||||
Steps without dependencies run in parallel:
|
||||
```yaml
|
||||
- id: unit-tests
|
||||
needs: [implement]
|
||||
|
||||
- id: integration-tests
|
||||
needs: [implement]
|
||||
|
||||
- id: review
|
||||
needs: [unit-tests, integration-tests] # Waits for both
|
||||
```
|
||||
|
||||
### Branching
|
||||
|
||||
Add parallel paths that rejoin:
|
||||
|
||||
```yaml
|
||||
compose:
|
||||
- branch:
|
||||
from: implement
|
||||
steps: [perf-test, load-test, chaos-test]
|
||||
join: review
|
||||
```
|
||||
|
||||
Produces:
|
||||
```
|
||||
implement ─┬─ perf-test ──┬─ review
|
||||
├─ load-test ──┤
|
||||
└─ chaos-test ─┘
|
||||
```
|
||||
|
||||
### Looping
|
||||
|
||||
Fixed iteration:
|
||||
```yaml
|
||||
compose:
|
||||
- loop:
|
||||
count: 5
|
||||
body: [refine]
|
||||
```
|
||||
|
||||
Conditional iteration:
|
||||
```yaml
|
||||
compose:
|
||||
- loop:
|
||||
step: review
|
||||
until: "review.output.approved == true"
|
||||
max: 3 # Safety bound
|
||||
```
|
||||
|
||||
### Gates
|
||||
|
||||
Wait for condition before proceeding:
|
||||
```yaml
|
||||
compose:
|
||||
- gate:
|
||||
before: submit
|
||||
condition: "security-scan.output.passed == true"
|
||||
```
|
||||
|
||||
## Advice Operators (Lisp-style!)
|
||||
|
||||
Inspired by Lisp advice and AOP, these operators inject behavior
|
||||
without modifying the original molecule.
|
||||
|
||||
### Before
|
||||
|
||||
Insert step before target:
|
||||
```yaml
|
||||
compose:
|
||||
- advice:
|
||||
target: review
|
||||
before: security-scan
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
Insert step after target:
|
||||
```yaml
|
||||
compose:
|
||||
- advice:
|
||||
target: implement
|
||||
after: run-linter
|
||||
```
|
||||
|
||||
### Around
|
||||
|
||||
Wrap target with before/after:
|
||||
```yaml
|
||||
compose:
|
||||
- advice:
|
||||
target: "*.implement"
|
||||
around:
|
||||
before: log-start
|
||||
after: log-end
|
||||
```
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
Target supports glob patterns:
|
||||
```yaml
|
||||
# All implement steps in any molecule
|
||||
target: "*.implement"
|
||||
|
||||
# All steps in shiny
|
||||
target: "shiny.*"
|
||||
|
||||
# Specific step
|
||||
target: "shiny.review"
|
||||
|
||||
# All steps (wildcard)
|
||||
target: "*"
|
||||
```
|
||||
|
||||
## Expansion Operators (Macros!)
|
||||
|
||||
Expansion operators transform structure at bond time.
|
||||
|
||||
### Simple Expansion
|
||||
|
||||
Apply a template to a target step:
|
||||
```yaml
|
||||
compose:
|
||||
- expand:
|
||||
target: implement
|
||||
with: rule-of-five
|
||||
```
|
||||
|
||||
The `rule-of-five` template:
|
||||
```yaml
|
||||
molecule: rule-of-five
|
||||
type: expansion
|
||||
description: Jeffrey's Rule - iterate 4-5 times for convergence
|
||||
|
||||
template:
|
||||
- id: "{target}.draft"
|
||||
description: "Initial attempt: {target.description}"
|
||||
|
||||
- id: "{target}.refine-1"
|
||||
description: "Refine for correctness"
|
||||
needs: ["{target}.draft"]
|
||||
|
||||
- id: "{target}.refine-2"
|
||||
description: "Refine for clarity"
|
||||
needs: ["{target}.refine-1"]
|
||||
|
||||
- id: "{target}.refine-3"
|
||||
description: "Refine for edge cases"
|
||||
needs: ["{target}.refine-2"]
|
||||
|
||||
- id: "{target}.refine-4"
|
||||
description: "Final polish"
|
||||
needs: ["{target}.refine-3"]
|
||||
```
|
||||
|
||||
Result: `implement` becomes 5 steps with proper dependency wiring.
|
||||
|
||||
### Map Expansion
|
||||
|
||||
Apply template to all matching steps:
|
||||
```yaml
|
||||
compose:
|
||||
- map:
|
||||
select: "shiny.*"
|
||||
with: rule-of-five
|
||||
```
|
||||
|
||||
All 5 shiny steps get R5 treatment → 25 total steps.
|
||||
|
||||
## Aspects (AOP)
|
||||
|
||||
Cross-cutting concerns applied to multiple join points:
|
||||
|
||||
```yaml
|
||||
aspect: security-audit
|
||||
description: Security scanning at implementation boundaries
|
||||
|
||||
pointcuts:
|
||||
- glob("*.implement")
|
||||
- glob("*.submit")
|
||||
|
||||
advice:
|
||||
around:
|
||||
before:
|
||||
- step: security-prescan
|
||||
args: { target: "{step.id}" }
|
||||
after:
|
||||
- step: security-postscan
|
||||
args: { target: "{step.id}" }
|
||||
- gate:
|
||||
condition: "security-postscan.output.approved == true"
|
||||
```
|
||||
|
||||
Apply aspects at bond time:
|
||||
```bash
|
||||
bd bond shiny --with-aspect security-audit --with-aspect logging
|
||||
```
|
||||
|
||||
## Selection Operators
|
||||
|
||||
For targeting steps in advice/expansion:
|
||||
|
||||
| Selector | Matches |
|
||||
|----------|---------|
|
||||
| `step("review")` | Specific step by ID |
|
||||
| `glob("*.implement")` | Pattern match |
|
||||
| `glob("shiny.*")` | All steps in molecule |
|
||||
| `filter(status == "open")` | Predicate match |
|
||||
| `children(step)` | Direct children |
|
||||
| `descendants(step)` | All descendants |
|
||||
|
||||
## Conditions
|
||||
|
||||
Conditions are evaluated mechanically (no AI):
|
||||
|
||||
```yaml
|
||||
# Step status
|
||||
"step.status == 'complete'"
|
||||
|
||||
# Step output (structured)
|
||||
"step.output.approved == true"
|
||||
"step.output.errors.count == 0"
|
||||
|
||||
# Aggregates
|
||||
"steps.complete >= 3"
|
||||
"children(step).all(status == 'complete')"
|
||||
|
||||
# External checks
|
||||
"file.exists('go.mod')"
|
||||
"env.CI == 'true'"
|
||||
```
|
||||
|
||||
Conditions are intentionally limited to keep evaluation decidable.
|
||||
|
||||
## Runtime Dynamic Expansion
|
||||
|
||||
For discovered work at runtime (Christmas Ornament pattern):
|
||||
|
||||
```yaml
|
||||
step: survey-workers
|
||||
on-complete:
|
||||
for-each: output.discovered_workers
|
||||
bond: polecat-arm
|
||||
with-vars:
|
||||
polecat: "{item.name}"
|
||||
rig: "{item.rig}"
|
||||
```
|
||||
|
||||
The `for-each` evaluates against step output, bonding N instances dynamically.
|
||||
Still declarative, still mechanical.
|
||||
|
||||
## Polymorphic Bond Operator
|
||||
|
||||
`bond` combines molecules with context-aware phase behavior:
|
||||
|
||||
| Operands | Result |
|
||||
|----------|--------|
|
||||
| proto + proto | compound proto (frozen) |
|
||||
| proto + mol | pour proto, attach |
|
||||
| proto + wisp | wisp proto, attach |
|
||||
| mol + mol | link via edges |
|
||||
| wisp + wisp | link via edges |
|
||||
| mol + wisp | reference link (cross-phase) |
|
||||
| expansion + workflow | expanded proto (macro) |
|
||||
| aspect + molecule | advised molecule |
|
||||
|
||||
Phase override flags:
|
||||
- `--pour`: Force creation as mol
|
||||
- `--wisp`: Force creation as wisp
|
||||
|
||||
## Complete Example: Shiny-Enterprise
|
||||
|
||||
```yaml
|
||||
molecule: shiny-enterprise
|
||||
extends: shiny
|
||||
description: Full enterprise engineering workflow
|
||||
|
||||
compose:
|
||||
# Apply Rule of Five to implement step
|
||||
- expand:
|
||||
target: implement
|
||||
with: rule-of-five
|
||||
|
||||
# Security aspect on all implementation steps
|
||||
- aspect:
|
||||
pointcut: "implement.*"
|
||||
with: security-audit
|
||||
|
||||
# Gate on security approval before submit
|
||||
- gate:
|
||||
before: submit
|
||||
condition: "security-postscan.approved == true"
|
||||
|
||||
# Parallel performance testing branch
|
||||
- branch:
|
||||
from: implement.refine-4
|
||||
steps: [perf-test, load-test, chaos-test]
|
||||
join: review
|
||||
|
||||
# Loop review until approved (max 3 attempts)
|
||||
- loop:
|
||||
step: review
|
||||
until: "review.output.approved == true"
|
||||
max: 3
|
||||
|
||||
# Logging on all steps
|
||||
- advice:
|
||||
target: "*"
|
||||
before: log-start
|
||||
after: log-end
|
||||
```
|
||||
|
||||
gt compiles this to ~30+ steps with proper dependencies.
|
||||
Agent executes. AI provides cognition for each step.
|
||||
Structure is pure algebra.
|
||||
|
||||
## The Grammar
|
||||
|
||||
```
|
||||
MOLECULE ::= 'molecule:' ID steps compose?
|
||||
|
||||
STEPS ::= step+
|
||||
STEP ::= 'id:' ID 'description:' TEXT needs?
|
||||
NEEDS ::= 'needs:' '[' ID+ ']'
|
||||
|
||||
COMPOSE ::= 'compose:' rule+
|
||||
RULE ::= advice | insert | branch | loop | gate | expand | aspect
|
||||
|
||||
ADVICE ::= 'advice:' target before? after? around?
|
||||
TARGET ::= 'target:' PATTERN
|
||||
BEFORE ::= 'before:' STEP_REF
|
||||
AFTER ::= 'after:' STEP_REF
|
||||
AROUND ::= 'around:' '{' before? after? '}'
|
||||
|
||||
BRANCH ::= 'branch:' from steps join
|
||||
LOOP ::= 'loop:' (count | until) body max?
|
||||
GATE ::= 'gate:' before? condition
|
||||
|
||||
EXPAND ::= 'expand:' target 'with:' TEMPLATE
|
||||
MAP ::= 'map:' select 'with:' TEMPLATE
|
||||
|
||||
ASPECT ::= 'aspect:' ID pointcuts advice
|
||||
POINTCUTS ::= 'pointcuts:' selector+
|
||||
SELECTOR ::= glob | filter | step
|
||||
|
||||
CONDITION ::= field OP value | aggregate OP value | external
|
||||
FIELD ::= step '.' attr | 'output' '.' path
|
||||
AGGREGATE ::= 'children' | 'descendants' | 'steps' '.' stat
|
||||
EXTERNAL ::= 'file.exists' | 'env.' key
|
||||
```
|
||||
|
||||
## Decidability
|
||||
|
||||
The algebra is intentionally restricted:
|
||||
- Loops have max iteration bounds
|
||||
- Conditions limited to step/output inspection
|
||||
- No recursion in expansion templates
|
||||
- No arbitrary code execution
|
||||
|
||||
This keeps evaluation decidable and safe for mechanical execution.
|
||||
|
||||
## Safety Constraints
|
||||
|
||||
The cooker enforces these constraints to prevent runaway expansion:
|
||||
|
||||
### Cycle Detection
|
||||
Circular `extends` chains are detected and rejected:
|
||||
```
|
||||
A extends B extends C extends A → ERROR: cycle detected
|
||||
```
|
||||
|
||||
### Aspect Self-Matching Prevention
|
||||
Aspects only match *original* steps, not steps inserted by the same aspect.
|
||||
Without this, a pointcut like `*.implement` that inserts `security-prescan`
|
||||
could match its own insertion infinitely.
|
||||
|
||||
### Maximum Expansion Depth
|
||||
Nested expansions are bounded (default: 5 levels). This allows massive work
|
||||
generation while preventing runaway recursion. Configurable via:
|
||||
```yaml
|
||||
cooking:
|
||||
max_expansion_depth: 5
|
||||
```
|
||||
|
||||
### Graceful Degradation
|
||||
Cooking errors produce warnings, not failures where possible. Philosophy:
|
||||
get it working as well as possible, warn the human, continue. Invalid steps
|
||||
may be annotated with error metadata rather than blocking the entire cook.
|
||||
|
||||
Errors are written to the Gas Town escalation channel for human review.
|
||||
|
||||
## What This Enables
|
||||
|
||||
1. **Composition without AI**: gt compiles molecule algebra mechanically
|
||||
2. **Marketplace of primitives**: Aspects, wrappers, expansions as tradeable units
|
||||
3. **Deterministic expansion**: Same input → same graph, always
|
||||
4. **AI for content only**: Agents execute steps, don't construct structure
|
||||
5. **Inspection/debugging**: See full expanded graph before execution
|
||||
6. **Optimization**: gt can parallelize, dedupe, optimize the graph
|
||||
7. **Roles/Companies in a box**: Compose arbitrary organizational workflows
|
||||
|
||||
## The Vision
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ MOL MALL (Marketplace) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
|
||||
│ │ Shiny │ │ Rule of │ │ Security │ │
|
||||
│ │ Formula │ │ Five │ │ Aspect │ │
|
||||
│ └─────────┘ └─────────┘ └───────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
|
||||
│ │Planning │ │ Release │ │ Company │ │
|
||||
│ │ Formula │ │ Formula │ │ Onboard │ │
|
||||
│ └─────────┘ └─────────┘ └───────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼ compose
|
||||
┌─────────────────────────────────────────┐
|
||||
│ YOUR ORGANIZATION FORMULA │
|
||||
│ │
|
||||
│ Planning + Shiny + R5 + Security + │
|
||||
│ Release + Onboarding = Company in Box │
|
||||
│ │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼ cook
|
||||
┌─────────────────────────────────────────┐
|
||||
│ PURE PROTO │
|
||||
│ Pre-expanded, flat graph │
|
||||
│ No macros, no aspects, just steps │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼ pour/wisp
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GAS TOWN │
|
||||
│ Polecats execute. Wisps evaporate. │
|
||||
│ Mols persist. Digests accumulate. │
|
||||
│ Work gets done. │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
From issues in git → work composition algebra → companies in a box.
|
||||
|
||||
---
|
||||
|
||||
*Structure is computation. Content is cognition. The work gets done.*
|
||||
@@ -1,346 +0,0 @@
|
||||
# Molecules: Composable Workflow Templates
|
||||
|
||||
This document covers the molecule system in depth.
|
||||
|
||||
For an overview, see [architecture.md](architecture.md#molecules-composable-workflow-templates).
|
||||
For the full lifecycle (Rig → Cook → Run), see [molecular-chemistry.md](molecular-chemistry.md).
|
||||
|
||||
## Core Concepts
|
||||
|
||||
A **molecule** is a workflow template stored as a beads issue with `labels: ["template"]`.
|
||||
When bonded, it creates child issues forming a DAG of steps.
|
||||
|
||||
```
|
||||
Proto (template)
|
||||
│
|
||||
▼ bd mol bond
|
||||
┌─────────────────┐
|
||||
│ Mol (durable) │ ← .beads/ (synced, auditable)
|
||||
│ or │
|
||||
│ Wisp (ephemeral)│ ← .beads-wisp/ (gitignored)
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────┴─────┐
|
||||
▼ ▼
|
||||
bd mol burn bd mol squash
|
||||
(no record) (creates digest)
|
||||
```
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Proto | Template molecule (is_template: true in beads) |
|
||||
| Mol | Durable instance in .beads/ |
|
||||
| Wisp | Ephemeral instance in .beads-wisp/ |
|
||||
| Digest | Condensed completion record |
|
||||
| Bond | Instantiate proto → mol/wisp |
|
||||
|
||||
## Molecule Storage
|
||||
|
||||
Molecules are standard beads issues stored in `molecules.jsonl`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mol-shiny",
|
||||
"title": "Shiny: {{feature_name}}",
|
||||
"description": "Full workflow from design to merge.\n\nVars:\n- {{feature_name}} - What to build",
|
||||
"labels": ["template"],
|
||||
"issue_type": "epic"
|
||||
}
|
||||
```
|
||||
|
||||
**Loaded from multiple sources** (later overrides earlier):
|
||||
1. Built-in (embedded in bd binary)
|
||||
2. Town-level: `~/gt/.beads/molecules.jsonl`
|
||||
3. User-level: `~/.beads/molecules.jsonl`
|
||||
4. Project-level: `.beads/molecules.jsonl`
|
||||
|
||||
## Variable Substitution
|
||||
|
||||
Molecules support `{{var}}` placeholders resolved at bond time:
|
||||
|
||||
```bash
|
||||
bd mol bond mol-shiny --var feature_name="user auth"
|
||||
# Creates: "Shiny: user auth"
|
||||
```
|
||||
|
||||
Variables work in:
|
||||
- Title
|
||||
- Description
|
||||
- Child step titles/descriptions
|
||||
|
||||
## Steps as Children
|
||||
|
||||
Steps are hierarchical children of the molecule:
|
||||
|
||||
```
|
||||
mol-shiny (epic)
|
||||
├── mol-shiny.1 "Design"
|
||||
├── mol-shiny.2 "Implement" Needs: .1
|
||||
├── mol-shiny.3 "Review" Needs: .2
|
||||
├── mol-shiny.4 "Test" Needs: .3
|
||||
└── mol-shiny.5 "Submit" Needs: .4
|
||||
```
|
||||
|
||||
Dependencies are encoded in beads edges, not in step descriptions.
|
||||
|
||||
## Molecule CLI Reference
|
||||
|
||||
### bd mol bond
|
||||
|
||||
Instantiate a proto molecule into a runnable mol or wisp.
|
||||
|
||||
```bash
|
||||
bd mol bond <proto-id> [--wisp] [--var key=value...]
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--wisp` | Create ephemeral wisp instead of durable mol |
|
||||
| `--var` | Variable substitution (repeatable) |
|
||||
| `--ref` | Custom reference ID for the instance |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Durable mol
|
||||
bd mol bond mol-shiny --var feature_name="auth"
|
||||
|
||||
# Ephemeral wisp for patrol
|
||||
bd mol bond mol-witness-patrol --wisp
|
||||
|
||||
# Dynamic child with custom ref (Christmas Ornament pattern)
|
||||
bd mol bond mol-polecat-arm $PATROL_ID --ref arm-ace --var polecat=ace
|
||||
```
|
||||
|
||||
### bd mol squash
|
||||
|
||||
Complete a molecule and generate a permanent digest.
|
||||
|
||||
```bash
|
||||
bd mol squash <mol-id> --summary='...'
|
||||
```
|
||||
|
||||
The summary is agent-generated - the intelligence comes from the agent, not beads.
|
||||
|
||||
**For Mol**: Creates digest in .beads/, original steps remain.
|
||||
**For Wisp**: Evaporates wisp, creates digest. Execution trace gone, outcome preserved.
|
||||
|
||||
### bd mol burn
|
||||
|
||||
Abandon a molecule without record.
|
||||
|
||||
```bash
|
||||
bd mol burn <mol-id> [--reason='...']
|
||||
```
|
||||
|
||||
Use burn for:
|
||||
- Routine patrol cycles (no audit needed)
|
||||
- Failed experiments
|
||||
- Cancelled work
|
||||
|
||||
Use squash for:
|
||||
- Completed work
|
||||
- Investigations with findings
|
||||
- Anything worth recording
|
||||
|
||||
### bd mol list
|
||||
|
||||
List available molecules:
|
||||
|
||||
```bash
|
||||
bd mol list # All templates
|
||||
bd mol list --label plugin # Plugin molecules only
|
||||
```
|
||||
|
||||
## Plugins ARE Molecules
|
||||
|
||||
Patrol plugins are molecules with specific labels:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mol-security-scan",
|
||||
"title": "Security scan for {{polecat_name}}",
|
||||
"description": "Check for vulnerabilities.\n\nVars: {{polecat_name}}, {{captured_output}}",
|
||||
"labels": ["template", "plugin", "witness", "tier:haiku"],
|
||||
"issue_type": "task"
|
||||
}
|
||||
```
|
||||
|
||||
Label conventions:
|
||||
- `plugin` - marks as bondable at hook points
|
||||
- `witness` / `deacon` / `refinery` - which patrol uses it
|
||||
- `tier:haiku` / `tier:sonnet` - model hint
|
||||
|
||||
**Execution in patrol:**
|
||||
|
||||
```bash
|
||||
# plugin-run step bonds registered plugins:
|
||||
bd mol bond mol-security-scan $PATROL_WISP \
|
||||
--ref security-{{polecat_name}} \
|
||||
--var polecat_name=ace \
|
||||
--var captured_output="$OUTPUT"
|
||||
```
|
||||
|
||||
## The Christmas Ornament Pattern
|
||||
|
||||
Dynamic bonding creates tree structures at runtime:
|
||||
|
||||
```
|
||||
★ mol-witness-patrol
|
||||
/|\
|
||||
┌─────┘ │ └─────┐
|
||||
PREFLIGHT │ CLEANUP
|
||||
│ │ │
|
||||
┌───┴───┐ │ ┌───┴───┐
|
||||
│inbox │ │ │aggreg │
|
||||
│load │ │ │summary│
|
||||
└───────┘ │ └───────┘
|
||||
│
|
||||
┌─────────┼─────────┐
|
||||
│ │ │
|
||||
● ● ● mol-polecat-arm (dynamic)
|
||||
ace nux toast
|
||||
│ │ │
|
||||
┌──┴──┐ ┌──┴──┐ ┌──┴──┐
|
||||
│steps│ │steps│ │steps│
|
||||
└──┬──┘ └──┬──┘ └──┬──┘
|
||||
│ │ │
|
||||
└─────────┴─────────┘
|
||||
│
|
||||
⬣ base
|
||||
```
|
||||
|
||||
**Key primitives:**
|
||||
- `bd mol bond ... --ref` - creates named children
|
||||
- `WaitsFor: all-children` - fanout gate
|
||||
- Arms execute in parallel
|
||||
|
||||
## Mol Mall
|
||||
|
||||
Distribution through molecule marketplace:
|
||||
|
||||
```bash
|
||||
# Install from registry
|
||||
bd mol install mol-security-scan
|
||||
|
||||
# Updates ~/.beads/molecules.jsonl
|
||||
```
|
||||
|
||||
Mol Mall serves `molecules.jsonl` fragments. Installation appends to your catalog.
|
||||
|
||||
## Code Review Molecule
|
||||
|
||||
The code-review molecule is a pluggable workflow:
|
||||
|
||||
```
|
||||
mol-code-review
|
||||
├── discovery (parallel)
|
||||
│ ├── file-census
|
||||
│ ├── dep-graph
|
||||
│ └── coverage-map
|
||||
│
|
||||
├── structural (sequential)
|
||||
│ ├── architecture-review
|
||||
│ └── abstraction-analysis
|
||||
│
|
||||
├── tactical (parallel per component)
|
||||
│ ├── security-scan
|
||||
│ ├── performance-review
|
||||
│ └── complexity-analysis
|
||||
│
|
||||
└── synthesis (single)
|
||||
└── aggregate
|
||||
```
|
||||
|
||||
Each dimension is a molecule that can be:
|
||||
- Swapped for alternatives from Mol Mall
|
||||
- Customized by forking and installing your version
|
||||
- Disabled by not bonding it
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
bd mol bond mol-code-review --var scope="src/"
|
||||
```
|
||||
|
||||
## Lifecycle Summary
|
||||
|
||||
Gas Town work follows the **Rig → Cook → Run** lifecycle:
|
||||
|
||||
```
|
||||
RIG (source) COOK (artifact) RUN (execution)
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Formula │──────►│ Proto │──────►│ Mol/Wisp │
|
||||
│ (.yaml) │ cook │ (cooked/frozen)│ pour/ │ (flowing) │
|
||||
│ │ │ │ wisp │ │
|
||||
└─────────────────┘ └─────────────────┘ └────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
burn squash
|
||||
│ │
|
||||
▼ ▼
|
||||
(gone) Digest
|
||||
(permanent)
|
||||
```
|
||||
|
||||
**Full lifecycle:**
|
||||
- **Formula** (.formula.toml or .formula.json) = Recipe (source code for workflows)
|
||||
- **Proto** = Fuel (cooked template, ready to instantiate)
|
||||
- **Mol/Wisp** = Steam (active execution)
|
||||
- **Digest** = Distillate (crystallized work)
|
||||
|
||||
See [molecular-chemistry.md](molecular-chemistry.md) for the complete specification.
|
||||
|
||||
## Formula File Formats
|
||||
|
||||
Formulas support both TOML (preferred) and JSON formats:
|
||||
|
||||
```bash
|
||||
# List available formulas
|
||||
bd formula list
|
||||
|
||||
# Show formula details
|
||||
bd formula show shiny
|
||||
|
||||
# Convert JSON to TOML (better for human editing)
|
||||
bd formula convert shiny # Single formula
|
||||
bd formula convert --all # All formulas
|
||||
bd formula convert shiny --stdout # Preview
|
||||
```
|
||||
|
||||
**Why TOML?**
|
||||
- Multi-line strings without `\n` escaping
|
||||
- Comments allowed
|
||||
- Human-readable diffs
|
||||
- Same parsing semantics as JSON
|
||||
|
||||
The loader tries `.formula.toml` first, falling back to `.formula.json`.
|
||||
|
||||
### Example Formula (TOML)
|
||||
|
||||
```toml
|
||||
formula = "shiny"
|
||||
type = "workflow"
|
||||
version = 1
|
||||
description = """
|
||||
Engineer in a Box - design before code, review before ship.
|
||||
"""
|
||||
|
||||
[vars.feature]
|
||||
description = "The feature being implemented"
|
||||
required = true
|
||||
|
||||
[[steps]]
|
||||
id = "design"
|
||||
title = "Design {{feature}}"
|
||||
description = """
|
||||
Think carefully about architecture before writing code.
|
||||
Consider edge cases and simpler approaches.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "implement"
|
||||
title = "Implement {{feature}}"
|
||||
needs = ["design"]
|
||||
```
|
||||
@@ -1,122 +0,0 @@
|
||||
# No-Tmux Mode
|
||||
|
||||
Gas Town can operate without tmux or the daemon, using beads as the universal data plane for passing args and context.
|
||||
|
||||
## Background
|
||||
|
||||
Tmux instability can crash workers. The daemon relies on tmux for:
|
||||
- Session management (creating/killing panes)
|
||||
- Nudging agents via SendKeys
|
||||
- Crash detection via pane-died hooks
|
||||
|
||||
When tmux is unstable, the entire operation fails. No-tmux mode enables continued operation in degraded mode.
|
||||
|
||||
## Key Insight: Beads Replace SendKeys
|
||||
|
||||
In normal mode, `--args` are injected via tmux SendKeys. In no-tmux mode:
|
||||
- Args are stored in the pinned bead description (`attached_args` field)
|
||||
- `gt prime` reads and displays args from the pinned bead
|
||||
- No prompt injection needed - agents discover everything via `bd show`
|
||||
|
||||
## Usage
|
||||
|
||||
### Slinging Work with Args
|
||||
|
||||
```bash
|
||||
# Normal mode: args injected via tmux + stored in bead
|
||||
gt sling gt-abc --args "patch release"
|
||||
|
||||
# In no-tmux mode: nudge fails gracefully, but args are in the bead
|
||||
# Agent discovers args via gt prime when it starts
|
||||
```
|
||||
|
||||
### Spawning without Tmux
|
||||
|
||||
```bash
|
||||
# Use --naked to skip tmux session creation
|
||||
gt sling gt-abc gastown --naked
|
||||
|
||||
# Output tells you how to start the agent manually:
|
||||
# cd ~/gt/gastown/polecats/<name>
|
||||
# claude
|
||||
```
|
||||
|
||||
### Agent Discovery
|
||||
|
||||
When an agent starts (manually or via IDE), the SessionStart hook runs `gt prime`, which:
|
||||
1. Detects the agent's role from cwd
|
||||
2. Finds pinned work
|
||||
3. Displays attached args prominently
|
||||
4. Shows current molecule step
|
||||
|
||||
The agent sees:
|
||||
|
||||
```
|
||||
## ATTACHED WORK DETECTED
|
||||
|
||||
Pinned bead: gt-abc
|
||||
Attached molecule: gt-xyz
|
||||
Attached at: 2025-12-26T12:00:00Z
|
||||
|
||||
ARGS (use these to guide execution):
|
||||
patch release
|
||||
|
||||
**Progress:** 0/5 steps complete
|
||||
```
|
||||
|
||||
## What Works vs What's Degraded
|
||||
|
||||
### What Still Works
|
||||
|
||||
| Feature | How It Works |
|
||||
|---------|--------------|
|
||||
| Propulsion via pinned beads | Agents pick up work on startup |
|
||||
| Self-handoff | Agents can cycle themselves |
|
||||
| Patrol loops | Deacon, Witness, Refinery keep running |
|
||||
| Mail system | Beads-based, no tmux needed |
|
||||
| Args passing | Stored in bead description |
|
||||
| Work discovery | `gt prime` reads from bead |
|
||||
|
||||
### What Is Degraded
|
||||
|
||||
| Limitation | Impact |
|
||||
|------------|--------|
|
||||
| No interrupts | Cannot nudge busy agents mid-task |
|
||||
| Polling only | Agents must actively check inbox (no push) |
|
||||
| Await steps block | "Wait for human" steps require manual agent restart |
|
||||
| No crash detection | pane-died hooks unavailable |
|
||||
| Manual startup | Human must start each agent in separate terminal |
|
||||
|
||||
### Workflow Implications
|
||||
|
||||
- **Patrol agents** work fine (they poll as part of their loop)
|
||||
- **Task workers** need restart to pick up new work
|
||||
- Cannot redirect a busy worker to urgent task
|
||||
- Human must monitor and restart crashed agents
|
||||
|
||||
## Commands Summary
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `gt sling <bead> --args "..."` | Store args in bead, nudge gracefully |
|
||||
| `gt sling <bead> <rig> --naked` | Assign work without tmux session |
|
||||
| `gt prime` | Display attached work + args on startup |
|
||||
| `gt mol status` | Show current work status including args |
|
||||
| `bd show <bead>` | View raw bead with attached_args field |
|
||||
|
||||
## Implementation Details
|
||||
|
||||
Args are stored in the bead description as a `key: value` field:
|
||||
|
||||
```
|
||||
attached_molecule: gt-xyz
|
||||
attached_at: 2025-12-26T12:00:00Z
|
||||
attached_args: patch release
|
||||
```
|
||||
|
||||
The `beads.AttachmentFields` struct includes:
|
||||
- `AttachedMolecule` - the work molecule ID
|
||||
- `AttachedAt` - timestamp when attached
|
||||
- `AttachedArgs` - natural language instructions
|
||||
|
||||
These are parsed by `beads.ParseAttachmentFields()` and formatted by `beads.FormatAttachmentFields()`.
|
||||
@@ -1,652 +0,0 @@
|
||||
# Pinned Beads Architecture
|
||||
|
||||
> **Status**: Design Draft
|
||||
> **Author**: max (crew)
|
||||
> **Date**: 2025-12-23
|
||||
|
||||
## Overview
|
||||
|
||||
Every Gas Town agent has a **pinned bead** - a persistent hook that serves as their
|
||||
work attachment point. This document formalizes the semantics, discovery mechanism,
|
||||
and lifecycle of pinned beads across all agent roles.
|
||||
|
||||
## The Pinned Bead Concept
|
||||
|
||||
A pinned bead is:
|
||||
- A bead with `status: pinned` (never closes)
|
||||
- Titled `"{role} Handoff"` (e.g., "Toast Handoff" for polecat Toast)
|
||||
- Contains attachment fields in its description when work is slung
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Pinned Bead: "Toast Handoff" │
|
||||
│ Status: pinned │
|
||||
│ Description: │
|
||||
│ attached_molecule: gt-xyz │
|
||||
│ attached_at: 2025-12-23T15:30:45Z │
|
||||
└─────────────────────────────────────────────┘
|
||||
│
|
||||
▼ (points to)
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Molecule: gt-xyz │
|
||||
│ Title: "Implement feature X" │
|
||||
│ Type: epic (molecule root) │
|
||||
│ Children: gt-abc, gt-def, gt-ghi (steps) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Role-by-Role Pinned Bead Semantics
|
||||
|
||||
### 1. Polecat
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `{name} Handoff` (e.g., "Toast Handoff") |
|
||||
| **Beads Location** | Rig-level (`.beads/` in rig root) |
|
||||
| **Prefix** | `gt-*` |
|
||||
| **Created By** | `gt sling` on first assignment |
|
||||
| **Typical Content** | Molecule for feature/bugfix work |
|
||||
| **Lifecycle** | Created → Work attached → Work detached → Dormant |
|
||||
|
||||
**Discovery**:
|
||||
```bash
|
||||
gt mol status # Shows what's on your hook
|
||||
bd list --status=pinned # Low-level: finds pinned beads
|
||||
```
|
||||
|
||||
**Protocol**:
|
||||
1. On startup, check `gt mol status`
|
||||
2. If work attached → Execute molecule steps
|
||||
3. If no work → Check mail inbox for new assignments
|
||||
4. On completion → Work auto-detached, check again
|
||||
|
||||
### 2. Crew
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `{name} Handoff` (e.g., "joe Handoff") |
|
||||
| **Beads Location** | Clone-level (`.beads/` in crew member's clone) |
|
||||
| **Prefix** | `gt-*` |
|
||||
| **Created By** | `gt sling` or manual pinned bead creation |
|
||||
| **Typical Content** | Longer-lived work, multi-session tasks |
|
||||
| **Lifecycle** | Persistent across sessions, human-managed |
|
||||
|
||||
**Key difference from Polecat**:
|
||||
- No witness monitoring
|
||||
- Human decides when to detach work
|
||||
- Work persists until explicitly completed
|
||||
|
||||
**Discovery**: Same as polecat (`gt mol status`)
|
||||
|
||||
### 3. Witness
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `Witness Handoff` |
|
||||
| **Beads Location** | Rig-level |
|
||||
| **Prefix** | `gt-*` |
|
||||
| **Created By** | Deacon or Mayor sling |
|
||||
| **Typical Content** | Patrol wisp (ephemeral) |
|
||||
| **Lifecycle** | Wisp attached → Patrol executes → Wisp squashed → New wisp |
|
||||
|
||||
**Protocol**:
|
||||
1. On startup, check `gt mol status`
|
||||
2. If wisp attached → Execute patrol cycle
|
||||
3. Squash wisp on completion (creates digest)
|
||||
4. Loop or await new sling
|
||||
|
||||
### 4. Refinery
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `Refinery Handoff` |
|
||||
| **Beads Location** | Rig-level |
|
||||
| **Prefix** | `gt-*` |
|
||||
| **Created By** | Witness or Mayor sling |
|
||||
| **Typical Content** | Epic with batch of issues |
|
||||
| **Lifecycle** | Epic attached → Dispatch to polecats → Monitor → Epic completed |
|
||||
|
||||
**Protocol**:
|
||||
1. Check `gt mol status` for attached epic
|
||||
2. Dispatch issues to polecats via `gt sling issue polecat/name`
|
||||
3. Monitor polecat progress
|
||||
4. Report completion when all issues closed
|
||||
|
||||
### 5. Deacon
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `Deacon Handoff` |
|
||||
| **Beads Location** | Town-level (`~/gt/.beads/`) |
|
||||
| **Prefix** | `hq-*` |
|
||||
| **Created By** | Mayor sling or self-loop |
|
||||
| **Typical Content** | Patrol wisp (always ephemeral) |
|
||||
| **Lifecycle** | Wisp → Execute → Squash → Loop |
|
||||
|
||||
**Protocol**:
|
||||
1. Check `gt mol status`
|
||||
2. Execute patrol wisp steps
|
||||
3. Squash wisp to digest
|
||||
4. Self-sling new patrol wisp and loop
|
||||
|
||||
### 6. Mayor
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| **Title Pattern** | `Mayor Handoff` |
|
||||
| **Beads Location** | Town-level (`~/gt/.beads/`) |
|
||||
| **Prefix** | `hq-*` |
|
||||
| **Created By** | External sling or self-assignment |
|
||||
| **Typical Content** | Strategic work, cross-rig coordination |
|
||||
| **Lifecycle** | Human-managed like Crew |
|
||||
|
||||
**Key difference**: Mayor is human-controlled (like Crew), but operates at
|
||||
town level with visibility into all rigs.
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Role | Title Pattern | Beads Level | Prefix | Ephemeral? | Managed By |
|
||||
|------|---------------|-------------|--------|------------|------------|
|
||||
| Polecat | `{name} Handoff` | Rig | `gt-` | No | Witness |
|
||||
| Crew | `{name} Handoff` | Clone | `gt-` | No | Human |
|
||||
| Witness | `Witness Handoff` | Rig | `gt-` | Yes (wisp) | Deacon |
|
||||
| Refinery | `Refinery Handoff` | Rig | `gt-` | No | Witness |
|
||||
| Deacon | `Deacon Handoff` | Town | `hq-` | Yes (wisp) | Self/Mayor |
|
||||
| Mayor | `Mayor Handoff` | Town | `hq-` | No | Human |
|
||||
|
||||
---
|
||||
|
||||
## Discovery Mechanism
|
||||
|
||||
### How Does a Worker Find Its Pinned Bead?
|
||||
|
||||
Workers find their pinned bead through a **title-based lookup**:
|
||||
|
||||
```go
|
||||
// In beads.go
|
||||
func (b *Beads) FindHandoffBead(role string) (*Issue, error) {
|
||||
issues := b.List(ListOptions{Status: StatusPinned})
|
||||
targetTitle := HandoffBeadTitle(role) // "{role} Handoff"
|
||||
for _, issue := range issues {
|
||||
if issue.Title == targetTitle {
|
||||
return issue, nil
|
||||
}
|
||||
}
|
||||
return nil, nil // Not found
|
||||
}
|
||||
```
|
||||
|
||||
**CLI path**:
|
||||
```bash
|
||||
gt mol status [target]
|
||||
```
|
||||
|
||||
This:
|
||||
1. Determines the agent's role/identity from `[target]` or environment
|
||||
2. Calls `FindHandoffBead(role)` to find the pinned bead
|
||||
3. Parses `AttachmentFields` from the description
|
||||
4. Shows attached molecule and progress
|
||||
|
||||
### Current Limitation: Role Naming
|
||||
|
||||
The current implementation uses simple role names:
|
||||
- Polecat: Uses polecat name (e.g., "Toast")
|
||||
- Others: Use role name (e.g., "Witness", "Refinery")
|
||||
|
||||
**Problem**: If there are multiple polecats, each needs a unique handoff bead.
|
||||
|
||||
**Solution (current)**: The role passed to `FindHandoffBead` is the polecat's
|
||||
name, not "polecat". So "Toast Handoff" is different from "Alpha Handoff".
|
||||
|
||||
---
|
||||
|
||||
## Dashboard Visibility
|
||||
|
||||
### How Do Users See Pinned Beads?
|
||||
|
||||
**Current state**: Limited visibility
|
||||
|
||||
**Proposed commands**:
|
||||
|
||||
```bash
|
||||
# Show all hooks across a rig
|
||||
gt hooks [rig]
|
||||
|
||||
# Output:
|
||||
# 📌 Hooks in gastown:
|
||||
#
|
||||
# Polecat/Toast: gt-xyz (feature: Add login)
|
||||
# Polecat/Alpha: (empty)
|
||||
# Witness: wisp-abc (patrol cycle)
|
||||
# Refinery: gt-epic-123 (batch: Q4 features)
|
||||
# Crew/joe: gt-789 (long: Refactor auth)
|
||||
# Crew/max: (empty)
|
||||
|
||||
# Show hook for specific agent
|
||||
gt mol status polecat/Toast
|
||||
gt mol status witness/
|
||||
gt mol status crew/joe
|
||||
```
|
||||
|
||||
### Dashboard Data Structure
|
||||
|
||||
```go
|
||||
type HookStatus struct {
|
||||
Agent string // Full address (gastown/polecat/Toast)
|
||||
Role string // polecat, witness, refinery, crew, deacon, mayor
|
||||
HasWork bool // Is something attached?
|
||||
AttachedID string // Molecule/issue ID
|
||||
AttachedTitle string // Human-readable title
|
||||
AttachedAt time.Time // When attached
|
||||
IsWisp bool // Ephemeral?
|
||||
Progress *Progress // Completion percentage
|
||||
}
|
||||
|
||||
type RigHooks struct {
|
||||
Rig string
|
||||
Polecats []HookStatus
|
||||
Crew []HookStatus
|
||||
Witness HookStatus
|
||||
Refinery HookStatus
|
||||
}
|
||||
|
||||
type TownHooks struct {
|
||||
Deacon HookStatus
|
||||
Mayor HookStatus
|
||||
Rigs []RigHooks
|
||||
}
|
||||
```
|
||||
|
||||
### Proposed: `gt dashboard`
|
||||
|
||||
A unified view of all hooks and work status:
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ Gas Town Dashboard ║
|
||||
╠══════════════════════════════════════════════════════════════╣
|
||||
║ Town: /Users/stevey/gt ║
|
||||
║ ║
|
||||
║ 🎩 Mayor: (empty) ║
|
||||
║ ⛪ Deacon: wisp-patrol (running patrol cycle) ║
|
||||
║ ║
|
||||
║ 📦 Rig: gastown ║
|
||||
║ 👁 Witness: wisp-watch (monitoring 2 polecats) ║
|
||||
║ 🏭 Refinery: gt-epic-45 (3/8 issues merged) ║
|
||||
║ 🐱 Polecats: ║
|
||||
║ Toast: gt-xyz [████████░░] 75% (Implement feature) ║
|
||||
║ Alpha: (empty) ║
|
||||
║ 👷 Crew: ║
|
||||
║ joe: gt-789 [██░░░░░░░░] 20% (Refactor auth) ║
|
||||
║ max: (empty) ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multiple Pins Semantics
|
||||
|
||||
### Question: What if a worker has multiple pinned beads?
|
||||
|
||||
**Current behavior**: Only first pinned bead with matching title is used.
|
||||
|
||||
**Design decision**: **One hook per agent** (enforced)
|
||||
|
||||
Rationale:
|
||||
- The Propulsion Principle says "if you find something on your hook, run it"
|
||||
- Multiple hooks would require decision-making about which to run
|
||||
- Decision-making violates propulsion
|
||||
- Work queuing belongs in mail or at dispatcher level (Witness/Refinery)
|
||||
|
||||
### Enforcement
|
||||
|
||||
```go
|
||||
func (b *Beads) GetOrCreateHandoffBead(role string) (*Issue, error) {
|
||||
existing, err := b.FindHandoffBead(role)
|
||||
if existing != nil {
|
||||
return existing, nil // Always return THE ONE handoff bead
|
||||
}
|
||||
// Create if not found...
|
||||
}
|
||||
```
|
||||
|
||||
**If somehow multiple exist** (data corruption):
|
||||
- `gt doctor` should flag as error
|
||||
- `FindHandoffBead` returns first match (deterministic by ID sort)
|
||||
- Manual cleanup required
|
||||
|
||||
### Hook Collision on Sling
|
||||
|
||||
When slinging to an occupied hook:
|
||||
|
||||
```bash
|
||||
$ gt sling feature polecat/Toast
|
||||
Error: polecat/Toast hook already occupied with gt-xyz
|
||||
Use --force to replace, or wait for current work to complete.
|
||||
|
||||
$ gt sling feature polecat/Toast --force
|
||||
⚠️ Detaching gt-xyz from polecat/Toast
|
||||
📌 Attached gt-abc to polecat/Toast
|
||||
```
|
||||
|
||||
**`--force` semantics**:
|
||||
1. Detach current work (leaves it orphaned in beads)
|
||||
2. Attach new work
|
||||
3. Log the replacement for audit
|
||||
|
||||
---
|
||||
|
||||
## Mail Attachments vs Pinned Attachments
|
||||
|
||||
### Question: What about mails with attached work that aren't pinned?
|
||||
|
||||
**Scenario**: Agent receives mail with `attached_molecule: gt-xyz` in body,
|
||||
but the mail itself is not pinned, and their hook is empty.
|
||||
|
||||
### Current Protocol
|
||||
|
||||
```
|
||||
1. Check hook (gt mol status)
|
||||
→ If work on hook → Run it
|
||||
|
||||
2. Check mail inbox
|
||||
→ If mail has attached work → Mail is the sling delivery mechanism
|
||||
→ The attached work gets pinned to hook automatically
|
||||
|
||||
3. No work anywhere → Idle/await
|
||||
```
|
||||
|
||||
### Design Decision: Mail Is Delivery, Hook Is Authority
|
||||
|
||||
**Mail with attachment** = "Here's work for you"
|
||||
**Hook with attachment** = "This is your current work"
|
||||
|
||||
The sling operation does both:
|
||||
1. Creates mail notification (optional, for context)
|
||||
2. Attaches to hook (authoritative)
|
||||
|
||||
**But what if only mail exists?** (manual mail, broken sling):
|
||||
|
||||
| Situation | Expected Behavior |
|
||||
|-----------|-------------------|
|
||||
| Hook has work, mail has work | Hook wins. Mail is informational. |
|
||||
| Hook empty, mail has work | Agent should self-pin from mail. |
|
||||
| Hook has work, mail empty | Work continues from hook. |
|
||||
| Both empty | Idle. |
|
||||
|
||||
### Protocol for Manual Work Assignment
|
||||
|
||||
If someone sends mail with attached work but doesn't sling:
|
||||
|
||||
```markdown
|
||||
## Agent Startup Protocol (Extended)
|
||||
|
||||
1. Check hook: `gt mol status`
|
||||
- Found work? **Run it.**
|
||||
|
||||
2. Hook empty? Check mail: `gt mail inbox`
|
||||
- Found mail with `attached_molecule`?
|
||||
- Self-pin it: `gt mol attach <your-hook> <molecule-id>`
|
||||
- Then run it.
|
||||
|
||||
3. Nothing? Idle.
|
||||
```
|
||||
|
||||
### Self-Pin Command
|
||||
|
||||
```bash
|
||||
# Agent self-pins work from mail
|
||||
gt mol attach-from-mail <mail-id>
|
||||
|
||||
# This:
|
||||
# 1. Reads mail body for attached_molecule field
|
||||
# 2. Attaches molecule to agent's hook
|
||||
# 3. Marks mail as read
|
||||
# 4. Returns control for execution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `gt doctor` Checks
|
||||
|
||||
### Current State
|
||||
|
||||
`gt doctor` doesn't check pinned beads at all.
|
||||
|
||||
### Proposed Checks
|
||||
|
||||
```go
|
||||
// DoctorCheck definitions for pinned beads
|
||||
var pinnedBeadChecks = []DoctorCheck{
|
||||
{
|
||||
Name: "hook-singleton",
|
||||
Description: "Each agent has at most one handoff bead",
|
||||
Check: checkHookSingleton,
|
||||
},
|
||||
{
|
||||
Name: "hook-attachment-valid",
|
||||
Description: "Attached molecules exist and are not closed",
|
||||
Check: checkHookAttachmentValid,
|
||||
},
|
||||
{
|
||||
Name: "orphaned-attachments",
|
||||
Description: "No molecules attached to non-existent hooks",
|
||||
Check: checkOrphanedAttachments,
|
||||
},
|
||||
{
|
||||
Name: "stale-attachments",
|
||||
Description: "Attached molecules not stale (>24h without progress)",
|
||||
Check: checkStaleAttachments,
|
||||
},
|
||||
{
|
||||
Name: "hook-agent-mismatch",
|
||||
Description: "Hook titles match existing agents",
|
||||
Check: checkHookAgentMismatch,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Check Details
|
||||
|
||||
#### 1. hook-singleton
|
||||
```
|
||||
✗ Multiple handoff beads for polecat/Toast:
|
||||
- gt-abc: "Toast Handoff" (created 2025-12-01)
|
||||
- gt-xyz: "Toast Handoff" (created 2025-12-15)
|
||||
|
||||
Fix: Delete duplicate(s) with `bd close gt-xyz --reason="duplicate hook"`
|
||||
```
|
||||
|
||||
#### 2. hook-attachment-valid
|
||||
```
|
||||
✗ Hook attachment points to missing molecule:
|
||||
Hook: gt-abc (Toast Handoff)
|
||||
Attached: gt-xyz (not found)
|
||||
|
||||
Fix: Clear attachment with `gt mol detach polecat/Toast`
|
||||
```
|
||||
|
||||
#### 3. orphaned-attachments
|
||||
```
|
||||
⚠ Molecule attached but agent doesn't exist:
|
||||
Molecule: gt-xyz (attached to "Defunct Handoff")
|
||||
Agent: polecat/Defunct (not found)
|
||||
|
||||
Fix: Re-sling to active agent or close molecule
|
||||
```
|
||||
|
||||
#### 4. stale-attachments
|
||||
```
|
||||
⚠ Stale work on hook (48h without progress):
|
||||
Hook: polecat/Toast
|
||||
Molecule: gt-xyz (attached 2025-12-21T10:00:00Z)
|
||||
Last activity: 2025-12-21T14:30:00Z
|
||||
|
||||
Suggestion: Check polecat status, consider nudge or reassignment
|
||||
```
|
||||
|
||||
#### 5. hook-agent-mismatch
|
||||
```
|
||||
⚠ Handoff bead for non-existent agent:
|
||||
Hook: "OldPolecat Handoff" (gt-abc)
|
||||
Agent: polecat/OldPolecat (no worktree found)
|
||||
|
||||
Fix: Close orphaned hook or recreate agent
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```go
|
||||
func (d *Doctor) checkPinnedBeads() []DoctorResult {
|
||||
results := []DoctorResult{}
|
||||
|
||||
// Get all pinned beads
|
||||
pinned, _ := d.beads.List(ListOptions{Status: StatusPinned})
|
||||
|
||||
// Group by title suffix (role)
|
||||
byRole := groupByRole(pinned)
|
||||
|
||||
// Check singleton
|
||||
for role, beads := range byRole {
|
||||
if len(beads) > 1 {
|
||||
results = append(results, DoctorResult{
|
||||
Check: "hook-singleton",
|
||||
Status: "error",
|
||||
Message: fmt.Sprintf("Multiple hooks for %s", role),
|
||||
Details: beads,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check attachment validity
|
||||
for _, bead := range pinned {
|
||||
fields := ParseAttachmentFields(bead)
|
||||
if fields != nil && fields.AttachedMolecule != "" {
|
||||
mol, err := d.beads.Show(fields.AttachedMolecule)
|
||||
if err != nil || mol == nil {
|
||||
results = append(results, DoctorResult{
|
||||
Check: "hook-attachment-valid",
|
||||
Status: "error",
|
||||
Message: "Attached molecule not found",
|
||||
// ...
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... other checks
|
||||
|
||||
return results
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminology Decisions
|
||||
|
||||
Based on this analysis, proposed standard terminology:
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Hook** | The pinned bead where work attaches (the attachment point) |
|
||||
| **Pinned Bead** | A bead with `status: pinned` (never closes) |
|
||||
| **Handoff Bead** | The specific pinned bead titled "{role} Handoff" |
|
||||
| **Attachment** | The molecule/issue currently on a hook |
|
||||
| **Sling** | The act of putting work on an agent's hook |
|
||||
| **Lead Bead** | The root bead of an attached molecule (synonym: molecule root) |
|
||||
|
||||
**Recommendation**: Use "hook" in user-facing commands and docs, "handoff bead"
|
||||
in implementation details.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
### 1. Should mail notifications include hook status?
|
||||
|
||||
```
|
||||
📬 New work assigned!
|
||||
|
||||
From: witness/
|
||||
Subject: Feature work assigned
|
||||
|
||||
Work: gt-xyz (Implement login)
|
||||
Molecule: feature (4 steps)
|
||||
|
||||
🪝 Hooked to: polecat/Toast
|
||||
Status: Attached and ready
|
||||
|
||||
Run `gt mol status` to see details.
|
||||
```
|
||||
|
||||
**Recommendation**: Yes. Mail should confirm hook attachment succeeded.
|
||||
|
||||
### 2. Should agents be able to self-detach?
|
||||
|
||||
```bash
|
||||
# Polecat decides work is blocked and gives up
|
||||
gt mol detach
|
||||
```
|
||||
|
||||
**Recommendation**: Yes, but with audit trail. Detachment without completion
|
||||
should be logged and possibly notify Witness.
|
||||
|
||||
### 3. Multiple rigs with same agent names?
|
||||
|
||||
```
|
||||
gastown/polecat/Toast
|
||||
otherrig/polecat/Toast
|
||||
```
|
||||
|
||||
**Current**: Each rig has separate beads, so no collision.
|
||||
**Future**: If cross-rig visibility needed, full addresses required.
|
||||
|
||||
### 4. Hook persistence during agent recreation?
|
||||
|
||||
When a polecat is killed and recreated:
|
||||
- Hook bead persists (it's in rig beads, not agent's worktree)
|
||||
- Old attachment may be stale
|
||||
- New sling should `--force` or detach first
|
||||
|
||||
**Recommendation**: `gt polecat recreate` should clear hook.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
### Phase 1: Doctor Checks (immediate)
|
||||
- [ ] Add `hook-singleton` check
|
||||
- [ ] Add `hook-attachment-valid` check
|
||||
- [ ] Add to default doctor run
|
||||
|
||||
### Phase 2: Dashboard Visibility
|
||||
- [ ] Implement `gt hooks` command
|
||||
- [ ] Add hook status to `gt status` output
|
||||
- [ ] Consider `gt dashboard` for full view
|
||||
|
||||
### Phase 3: Protocol Enforcement
|
||||
- [ ] Add self-pin from mail (`gt mol attach-from-mail`)
|
||||
- [ ] Audit trail for detach operations
|
||||
- [ ] Witness notification on abnormal detach
|
||||
|
||||
### Phase 4: Documentation
|
||||
- [ ] Update role prompt templates with hook protocol
|
||||
- [ ] Add troubleshooting guide for hook issues
|
||||
- [ ] Document recovery procedures
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The pinned bead architecture provides:
|
||||
|
||||
1. **Universal hook per agent** - Every agent has exactly one handoff bead
|
||||
2. **Title-based discovery** - `{role} Handoff` naming convention
|
||||
3. **Attachment fields** - `attached_molecule` and `attached_at` in description
|
||||
4. **Propulsion compliance** - One hook = no decision paralysis
|
||||
5. **Mail as delivery** - Sling sends notification, attaches to hook
|
||||
6. **Doctor validation** - Checks for singleton, validity, staleness
|
||||
|
||||
The key insight is that **hooks are authority, mail is notification**. If
|
||||
they conflict, the hook wins. This maintains the Propulsion Principle:
|
||||
"If you find something on your hook, YOU RUN IT."
|
||||
@@ -1,130 +0,0 @@
|
||||
# Polecat Lifecycle
|
||||
|
||||
> Polecats restart after each molecule step. This is intentional.
|
||||
|
||||
## Execution Model
|
||||
|
||||
| Phase | What Happens |
|
||||
|-------|--------------|
|
||||
| **Spawn** | Worktree created, session started, molecule slung to hook |
|
||||
| **Step** | Polecat reads hook, executes ONE step, runs `gt mol step done` |
|
||||
| **Restart** | Session respawns with fresh context, next step on hook |
|
||||
| **Complete** | Last step done → POLECAT_DONE mail → cleanup wisp created |
|
||||
| **Cleanup** | Witness verifies git clean, kills session, burns wisp |
|
||||
|
||||
```
|
||||
spawn → step → restart → step → restart → ... → complete → cleanup
|
||||
└──────────────────────────────────────┘
|
||||
(fresh session each step)
|
||||
```
|
||||
|
||||
## Why Restart Every Step?
|
||||
|
||||
| Reason | Explanation |
|
||||
|--------|-------------|
|
||||
| **Atomicity** | Each step completes fully or not at all |
|
||||
| **No wandering** | Polecat can't half-finish and get distracted |
|
||||
| **Context fresh** | No accumulation of stale context across steps |
|
||||
| **Crash recovery** | Restart = re-read hook = continue from last completed step |
|
||||
|
||||
**Trade-off**: Session restart overhead. Worth it for reliability at current cognition levels.
|
||||
|
||||
## Step Packing (Author Responsibility)
|
||||
|
||||
Formula authors must size steps appropriately:
|
||||
|
||||
| Too Small | Too Large |
|
||||
|-----------|-----------|
|
||||
| Restart overhead dominates | Context exhaustion mid-step |
|
||||
| Thrashing | Partial completion, unreliable |
|
||||
|
||||
**Rule of thumb**: A step should use 30-70% of available context. Batch related micro-tasks.
|
||||
|
||||
## The `gt mol step done` Command
|
||||
|
||||
Canonical way to complete a step:
|
||||
|
||||
```bash
|
||||
gt mol step done <step-id>
|
||||
```
|
||||
|
||||
1. Closes the step in beads
|
||||
2. Finds next ready step (dependency-aware)
|
||||
3. Updates hook to next step
|
||||
4. Respawns pane with fresh session
|
||||
|
||||
**Never use `bd close` directly** - it skips the restart logic.
|
||||
|
||||
## Cleanup: The Finalizer Pattern
|
||||
|
||||
When polecat signals completion:
|
||||
|
||||
```
|
||||
POLECAT_DONE mail → Witness creates cleanup wisp → Witness processes wisp → Burn
|
||||
```
|
||||
|
||||
The wisp's existence IS the pending cleanup. No explicit queue.
|
||||
|
||||
| Cleanup Step | Verification |
|
||||
|--------------|--------------|
|
||||
| Git status | Must be clean |
|
||||
| Unpushed commits | None allowed |
|
||||
| Issue state | Closed or deferred |
|
||||
| Productive work | Commits reference issue (ZFC - Witness judges) |
|
||||
|
||||
Failed cleanup? Leave wisp, retry next cycle.
|
||||
|
||||
---
|
||||
|
||||
## Evolution Path
|
||||
|
||||
Current design will evolve as model cognition improves:
|
||||
|
||||
| Phase | Refresh Trigger | Who Decides | Witness Load |
|
||||
|-------|-----------------|-------------|--------------|
|
||||
| **Now** | Step boundary | Formula (fixed) | High |
|
||||
| **Spoon-feeding** | Context % + task size | Witness | Medium |
|
||||
| **Self-managed** | Self-awareness | Polecat | Low |
|
||||
|
||||
### Now (Step-Based Restart)
|
||||
|
||||
- Restart every step, guaranteed
|
||||
- Conservative, reliable
|
||||
- `gt mol step done` handles everything
|
||||
|
||||
### Spoon-feeding (Future)
|
||||
|
||||
Requires: Claude Code exposes context usage
|
||||
|
||||
```
|
||||
Polecat completes step
|
||||
→ Witness checks: 65% context used
|
||||
→ Next task estimate: 10% context
|
||||
→ Decision: "send another" or "recycle"
|
||||
```
|
||||
|
||||
Witness becomes supervisor, not babysitter.
|
||||
|
||||
### Self-Managed (Future)
|
||||
|
||||
Requires: Model cognition threshold + Gas Town patterns in training
|
||||
|
||||
```
|
||||
Polecat completes step
|
||||
→ Self-assesses: "I'm at 80%, should recycle"
|
||||
→ Runs gt handoff, respawns
|
||||
```
|
||||
|
||||
Polecats become autonomous. Witness becomes auditor.
|
||||
|
||||
---
|
||||
|
||||
## Key Commands
|
||||
|
||||
| Command | Effect |
|
||||
|---------|--------|
|
||||
| `gt mol step done <step>` | Complete step, restart for next |
|
||||
| `gt mol status` | Show what's on hook |
|
||||
| `gt mol progress <mol>` | Show molecule completion state |
|
||||
| `gt done` | Signal POLECAT_DONE to Witness |
|
||||
| `gt handoff` | Write notes, respawn (manual refresh) |
|
||||
@@ -1,333 +0,0 @@
|
||||
# Polecat Wisp Architecture: Proto → Wisp → Mol
|
||||
|
||||
> Issue: gt-9g82
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document proposes a three-layer architecture for agent work: **Proto → Wisp → Mol**.
|
||||
The key insight is that agents should run session wisps that wrap their assigned work.
|
||||
This creates a unified "engineer in a box" pattern that handles onboarding, execution,
|
||||
and cleanup within a single ephemeral container.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Three-Layer Model
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PROTO LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ polecat.md / crew.md (Template/Instructions) │ │
|
||||
│ │ - Role identity and context │ │
|
||||
│ │ - How to be this type of agent │ │
|
||||
│ │ - Workflow patterns │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ mol-polecat-session / mol-crew-session │ │
|
||||
│ │ - Onboarding step │ │
|
||||
│ │ - Execute work step │ │
|
||||
│ │ - Cleanup/handoff step │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ instantiate
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ WISP LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ wisp-<agent-name>-<timestamp> │ │
|
||||
│ │ Storage: .beads-wisp/ (ephemeral, gitignored) │ │
|
||||
│ │ │ │
|
||||
│ │ Steps: │ │
|
||||
│ │ 1. orient - Load context, read role template │ │
|
||||
│ │ 2. handoff - Check for predecessor handoff │ │
|
||||
│ │ 3. execute - Run the attached work molecule │ │
|
||||
│ │ 4. cleanup - Sync, handoff to successor, exit │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ attachment
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ MOL LAYER │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ The Actual Work (Permanent, Auditable) │ │
|
||||
│ │ Storage: .beads/ (git-tracked) │ │
|
||||
│ │ │ │
|
||||
│ │ Could be: │ │
|
||||
│ │ - mol-shiny (full workflow) │ │
|
||||
│ │ - mol-quick-fix (fast path) │ │
|
||||
│ │ - Any custom work molecule │ │
|
||||
│ │ - Direct issue (no molecule, just task) │ │
|
||||
│ │ - Long-lived epic spanning many sessions │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Agent Types and Their Wisps
|
||||
|
||||
### 2.1 Comparison: Patrol vs Work Agents
|
||||
|
||||
| Aspect | Patrol Wisp (Deacon/Refinery) | Work Wisp (Polecat/Crew) |
|
||||
|--------|-------------------------------|--------------------------|
|
||||
| **Lifecycle** | Looping - spawn, execute, burn, repeat | One-shot per session |
|
||||
| **Inner Work** | Patrol steps (fixed, no attach) | Attached work mol (variable) |
|
||||
| **Persistence** | Burns between cycles | Burns on session end |
|
||||
| **Session** | Persists across wisps | Polecat: terminates; Crew: cycles |
|
||||
| **Audit Trail** | Digests only (summaries) | Work mol is permanent |
|
||||
|
||||
### 2.2 Polecat Wisp (Ephemeral Worker)
|
||||
|
||||
Polecats are transient workers that:
|
||||
- Get spawned for specific work
|
||||
- Self-destruct on completion
|
||||
- Have no persistent identity
|
||||
|
||||
```
|
||||
Polecat Session Flow:
|
||||
─────────────────────
|
||||
spawn → orient → handoff(read) → execute → cleanup → TERMINATE
|
||||
│
|
||||
└──▶ [attached work mol]
|
||||
```
|
||||
|
||||
### 2.3 Crew Worker Wisp (Persistent Worker)
|
||||
|
||||
Crew workers are long-lived agents that:
|
||||
- Maintain persistent identity across sessions
|
||||
- Work on long-lived molecules (epics spanning days/weeks)
|
||||
- Hand off to successor sessions (themselves)
|
||||
- **Can work autonomously overnight** if attached mol exists
|
||||
|
||||
```
|
||||
Crew Worker Session Flow:
|
||||
─────────────────────────
|
||||
start → orient → handoff(read) → check attachment
|
||||
│
|
||||
┌──────────────┴──────────────┐
|
||||
│ │
|
||||
ATTACHED NO ATTACHMENT
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐
|
||||
│ AUTO-CONTINUE │ │ AWAIT INPUT │
|
||||
│ (no prompt) │ │ (human directs)│
|
||||
└───────┬───────┘ └───────────────┘
|
||||
│
|
||||
▼
|
||||
execute mol
|
||||
│
|
||||
▼
|
||||
cleanup → handoff(write) → END SESSION
|
||||
│
|
||||
▼
|
||||
(successor picks up)
|
||||
```
|
||||
|
||||
**Key insight**: If there's an attached mol, crew workers continue working
|
||||
without awaiting input. This enables autonomous overnight work on long molecules.
|
||||
|
||||
---
|
||||
|
||||
## 3. The Onboard-Execute-Cleanup Pattern
|
||||
|
||||
### 3.1 mol-polecat-session
|
||||
|
||||
```markdown
|
||||
## Step: orient
|
||||
Read polecat.md protocol. Understand:
|
||||
- Your identity (rig/polecat-name)
|
||||
- The beads system
|
||||
- Exit strategies
|
||||
- Handoff protocols
|
||||
|
||||
Load context:
|
||||
- gt prime
|
||||
- gt mail inbox
|
||||
|
||||
## Step: handoff-read
|
||||
Check for predecessor handoff mail.
|
||||
If found, load context from previous session.
|
||||
|
||||
## Step: execute
|
||||
Find attached work:
|
||||
- gt mol status (shows what's on hook)
|
||||
- bd show <work-mol-id>
|
||||
|
||||
Run the attached work molecule to completion.
|
||||
Continue until reaching exit-decision step.
|
||||
|
||||
## Step: cleanup
|
||||
1. Sync state: bd sync, git push
|
||||
2. Close or defer work mol based on exit type
|
||||
3. Burn this session wisp
|
||||
4. Request shutdown from Witness
|
||||
```
|
||||
|
||||
### 3.2 mol-crew-session (Light Harness)
|
||||
|
||||
```markdown
|
||||
## Step: orient
|
||||
Read crew.md protocol. Load context:
|
||||
- gt prime
|
||||
- Identify self (crew member name, rig)
|
||||
|
||||
## Step: handoff-read
|
||||
Check inbox for 🤝 HANDOFF messages:
|
||||
- gt mail inbox
|
||||
- If found: read and load predecessor context
|
||||
|
||||
## Step: check-attachment
|
||||
Look for pinned work:
|
||||
- bd list --pinned --assignee=<self> --status=in_progress
|
||||
- gt mol status
|
||||
|
||||
If attachment found:
|
||||
→ Proceed to execute (no human input needed)
|
||||
If no attachment:
|
||||
→ Await user instruction
|
||||
|
||||
## Step: execute
|
||||
Work the attached molecule:
|
||||
- bd ready --parent=<work-mol-root>
|
||||
- Continue from last completed step
|
||||
- Work until context full or natural stopping point
|
||||
|
||||
## Step: cleanup
|
||||
Before session ends:
|
||||
1. git status / git push
|
||||
2. bd sync
|
||||
3. Write handoff mail to self:
|
||||
gt mail send <self-addr> -s "🤝 HANDOFF: <context>" -m "<details>"
|
||||
4. Session ends (successor will pick up)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Autonomous Overnight Work
|
||||
|
||||
### 4.1 The Vision
|
||||
|
||||
With session wisps, crew workers can work autonomously:
|
||||
|
||||
```
|
||||
Night 1, Session 1:
|
||||
Human: "Work on gt-abc (big feature epic)"
|
||||
Crew: Attaches gt-abc, works 2 hours, context full
|
||||
Crew: Writes handoff, ends session
|
||||
|
||||
Night 1, Session 2 (auto-spawned):
|
||||
Crew: Reads handoff, finds attached gt-abc
|
||||
Crew: AUTO-CONTINUES (no human needed)
|
||||
Crew: Works 2 more hours, context full
|
||||
Crew: Writes handoff, ends session
|
||||
|
||||
... repeat through the night ...
|
||||
|
||||
Morning:
|
||||
Human: Checks progress on gt-abc
|
||||
Crew: "Completed 15 of 23 subtasks overnight"
|
||||
```
|
||||
|
||||
### 4.2 Requirements for Autonomous Work
|
||||
|
||||
1. **Attached mol** - Work must be pinned/attached
|
||||
2. **Clear exit conditions** - Mol defines when to stop
|
||||
3. **Session cycling** - Auto-spawn successor sessions
|
||||
4. **Handoff protocol** - Each session writes handoff for next
|
||||
|
||||
### 4.3 Safety Rails
|
||||
|
||||
- Work mol defines scope (can't go off-rails)
|
||||
- Each session is bounded (context limit)
|
||||
- Handoffs create audit trail
|
||||
- Human can intervene at any session boundary
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Structures
|
||||
|
||||
### 5.1 Session Wisp
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "wisp-max-2024-12-22T15:00:00Z",
|
||||
"type": "wisp",
|
||||
"title": "Crew Session: max",
|
||||
"status": "in_progress",
|
||||
"current_step": "execute",
|
||||
"agent_type": "crew",
|
||||
"agent_name": "max",
|
||||
"attached_mol": "gt-abc123",
|
||||
"attached_at": "2024-12-22T15:01:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Attachment Mechanism
|
||||
|
||||
```bash
|
||||
# Attach a work mol to current session wisp
|
||||
gt mol attach <pinned-bead-id> <mol-id>
|
||||
|
||||
# Check current attachment
|
||||
gt mol status
|
||||
|
||||
# Detach (human override)
|
||||
gt mol detach <pinned-bead-id>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Phases
|
||||
|
||||
### Phase 1: Core Infrastructure (P0)
|
||||
|
||||
1. Create `mol-crew-session.formula.json` in `.beads/formulas/`
|
||||
2. Create `mol-polecat-session.formula.json` in `.beads/formulas/`
|
||||
3. Add wisp attachment mechanism to beads
|
||||
4. Update spawn.go for polecat session wisps
|
||||
|
||||
### Phase 2: Crew Worker Integration (P0)
|
||||
|
||||
1. Update crew startup hook to instantiate session wisp
|
||||
2. Implement auto-continue logic (if attached → work)
|
||||
3. Update crew.md template for session wisp model
|
||||
|
||||
### Phase 3: Session Cycling (P1)
|
||||
|
||||
1. Add session successor spawning
|
||||
2. Implement context-full detection
|
||||
3. Auto-handoff on session end
|
||||
|
||||
### Phase 4: Monitoring & Safety (P1)
|
||||
|
||||
1. Witness monitors session wisps
|
||||
2. Add emergency stop mechanism
|
||||
3. Progress reporting between sessions
|
||||
|
||||
---
|
||||
|
||||
## 7. Open Questions
|
||||
|
||||
1. **Session cycling trigger**: Context percentage? Time limit? Both?
|
||||
2. **Emergency stop**: How does human halt autonomous work?
|
||||
3. **Progress visibility**: Dashboard for overnight work progress?
|
||||
4. **Mol attachment UI**: How does human attach/detach work?
|
||||
|
||||
---
|
||||
|
||||
## 8. Summary
|
||||
|
||||
The session wisp architecture enables:
|
||||
|
||||
- **Separation of concerns**: How to work (proto) vs What to work on (mol)
|
||||
- **Session continuity**: Handoffs preserve context across sessions
|
||||
- **Autonomous work**: Crew workers can work overnight on attached mols
|
||||
- **Unified pattern**: Same model for polecats and crew workers
|
||||
- **Audit trail**: Work mol is permanent, session wisps are ephemeral
|
||||
|
||||
The key unlock is: **if there's attached work, continue without prompting**.
|
||||
This transforms crew workers from interactive assistants to autonomous workers.
|
||||
441
docs/prompts.md
441
docs/prompts.md
@@ -1,441 +0,0 @@
|
||||
# Gas Town Prompt Architecture
|
||||
|
||||
This document defines the canonical prompt system for Go Gas Town (GGT). Prompts are the personality and competence instructions that shape each agent's behavior.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Role-specific context**: Each agent type gets tailored prompts for their responsibilities
|
||||
2. **Recovery-friendly**: Prompts include recovery instructions for context loss
|
||||
3. **Command-focused**: Essential commands prominently featured
|
||||
4. **Checklist-driven**: Critical workflows enforced via checklists
|
||||
5. **Handoff-aware**: Session cycling built into prompt design
|
||||
|
||||
## Prompt Categories
|
||||
|
||||
### 1. Role Prompts (Primary Context)
|
||||
|
||||
Delivered via `gt prime` command. These establish agent identity and capabilities.
|
||||
|
||||
| Role | Template | Purpose |
|
||||
|------|----------|---------|
|
||||
| Mayor | `mayor.md` | Global coordination, work dispatch, cross-rig decisions |
|
||||
| Witness | `witness.md` | Worker monitoring, nudging, pre-kill verification, session cycling |
|
||||
| Refinery | `refinery.md` | Merge queue processing, PR review, integration |
|
||||
| Polecat | `polecat.md` | Implementation work on assigned issues |
|
||||
| Crew | `crew.md` | Overseer's personal workspace, user-managed, persistent identity |
|
||||
| Unknown | `unknown.md` | Fallback when role detection fails |
|
||||
|
||||
### 2. Mail Templates (Structured Messages)
|
||||
|
||||
Parseable messages for worker coordination.
|
||||
|
||||
| Template | Flow | Purpose |
|
||||
|----------|------|---------|
|
||||
| `BATCH_STARTED` | Refinery → Mayor | Batch initialized, workers ready |
|
||||
| `WORKER_READY` | Worker → Refinery | Worker setup complete |
|
||||
| `WORK_COMPLETE` | Worker → Refinery | Task finished, ready for merge |
|
||||
| `MERGE_CONFLICT` | Refinery → Worker/Mayor | Merge failed, needs resolution |
|
||||
| `BATCH_COMPLETE` | Refinery → Mayor | All tasks done |
|
||||
| `BATCH_FAILED` | Refinery → Mayor | Batch failed, needs intervention |
|
||||
|
||||
### 3. Spawn Injection (Work Assignment)
|
||||
|
||||
Prompts injected when assigning work to agents.
|
||||
|
||||
| Context | Content |
|
||||
|---------|---------|
|
||||
| New polecat | Issue details, commands, completion checklist |
|
||||
| Reused polecat | Compact issue summary, `bd show` reminder |
|
||||
| Ephemeral worker | Issue details with read-only beads instructions |
|
||||
|
||||
### 4. Lifecycle Templates (Session Management)
|
||||
|
||||
Templates for session cycling and handoffs.
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `HANDOFF` | Session-to-self handoff with state capture |
|
||||
| `ESCALATION` | Worker → Witness → Mayor escalation |
|
||||
| `NUDGE` | Witness → Worker progress reminder |
|
||||
|
||||
## PGT Prompt Inventory
|
||||
|
||||
### Role Prompts (templates/*.md.j2)
|
||||
|
||||
**mayor.md.j2** (~100 lines)
|
||||
- Role identification and scope
|
||||
- Workspace locations (`~/ai/mayor/rigs/<rig>/`)
|
||||
- Directory structure diagram
|
||||
- Essential commands (status, inbox, spawn, refinery)
|
||||
- Working on code and beads section
|
||||
- Delegation rules
|
||||
- Session end checklist with handoff protocol
|
||||
|
||||
**polecat.md.j2** (~120 lines)
|
||||
- Role identification with rig/polecat name injection
|
||||
- Ephemeral mode conditional sections
|
||||
- Critical "work in YOUR directory only" warning
|
||||
- Essential commands (finding work, working, completing)
|
||||
- Ephemeral-specific status reporting commands
|
||||
- Required completion checklist (bd close FIRST)
|
||||
- "If you get stuck" section
|
||||
|
||||
**refinery.md.j2** (~70 lines)
|
||||
- Role identification with rig name
|
||||
- Directory structure showing refinery/rig/ location
|
||||
- Polecat management commands
|
||||
- Work management and sync commands
|
||||
- Workflow steps (inbox → ready → spawn → monitor → report)
|
||||
- Worker management commands
|
||||
- Session end checklist
|
||||
|
||||
**unknown.md.j2** (~20 lines)
|
||||
- Location unknown message
|
||||
- Navigation suggestions
|
||||
- Orientation commands
|
||||
|
||||
### Static Fallbacks (prime_cmd.py)
|
||||
|
||||
Each role has a `_get_<role>_context_static()` function providing fallback prompts when Jinja2 is unavailable. These are simplified versions of the templates.
|
||||
|
||||
### Mail Templates (mail/templates.py)
|
||||
|
||||
~450 lines of structured message generators:
|
||||
- Metadata format: `[KEY]: value` lines for parsing
|
||||
- Human-readable context below metadata
|
||||
- Priority levels (normal, high)
|
||||
- Message types (NOTIFICATION, TASK)
|
||||
|
||||
### Spawn Injection (spawn.py)
|
||||
|
||||
**send_initial_prompt()** (~40 lines)
|
||||
- Issue ID and title
|
||||
- Description (if available)
|
||||
- Additional instructions
|
||||
- Command reference (bd show, bd update, bd close)
|
||||
|
||||
**Direct injection prompts** (inline)
|
||||
- Compact single-line format for tmux injection
|
||||
- Issue ID, title, extra prompt, completion reminder
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
### Missing Prompts
|
||||
|
||||
| Gap | Priority | Notes |
|
||||
|-----|----------|-------|
|
||||
| **Witness role prompt** | P0 | New role, no prompts exist |
|
||||
| Session handoff template | P1 | Currently ad-hoc in agent logic |
|
||||
| Escalation message template | P1 | No standard format |
|
||||
| Nudge message template | P2 | Witness → Worker reminders |
|
||||
| Direct landing workflow | P2 | Mayor bypass of Refinery |
|
||||
|
||||
### Missing Features
|
||||
|
||||
| Feature | Priority | Notes |
|
||||
|---------|----------|-------|
|
||||
| Prompt versioning | P2 | No way to track prompt changes |
|
||||
| Prompt testing | P3 | No validation that prompts work |
|
||||
| Dynamic context | P2 | Templates don't adapt to agent state |
|
||||
|
||||
## GGT Architecture Recommendations
|
||||
|
||||
### 1. Prompt Storage
|
||||
|
||||
Templates are embedded in the Go binary via `//go:embed`:
|
||||
|
||||
```
|
||||
internal/templates/
|
||||
├── roles/
|
||||
│ ├── mayor.md.tmpl
|
||||
│ ├── witness.md.tmpl
|
||||
│ ├── refinery.md.tmpl
|
||||
│ ├── polecat.md.tmpl
|
||||
│ ├── crew.md.tmpl
|
||||
│ └── deacon.md.tmpl
|
||||
└── messages/
|
||||
├── spawn.md.tmpl
|
||||
├── nudge.md.tmpl
|
||||
├── escalation.md.tmpl
|
||||
└── handoff.md.tmpl
|
||||
```
|
||||
|
||||
### 2. Template Engine
|
||||
|
||||
Use Go's `text/template` with a simple context struct:
|
||||
|
||||
```go
|
||||
type PromptContext struct {
|
||||
Role string
|
||||
RigName string
|
||||
PolecatName string
|
||||
Transient bool
|
||||
IssueID string
|
||||
IssueTitle string
|
||||
// ... additional fields
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Prompt Registry
|
||||
|
||||
```go
|
||||
type PromptRegistry struct {
|
||||
roles map[string]*template.Template
|
||||
mail map[string]*template.Template
|
||||
spawn map[string]*template.Template
|
||||
lifecycle map[string]*template.Template
|
||||
}
|
||||
|
||||
func (r *PromptRegistry) Render(category, name string, ctx PromptContext) (string, error)
|
||||
```
|
||||
|
||||
### 4. CLI Integration
|
||||
|
||||
```bash
|
||||
gt prime # Auto-detect role, render appropriate prompt
|
||||
gt prime --mayor # Force mayor prompt
|
||||
gt prime --witness # Force witness prompt
|
||||
gt prompt render <category>/<name> --context '{"rig": "foo"}'
|
||||
gt prompt list # List all available prompts
|
||||
gt prompt validate # Check all prompts parse correctly
|
||||
```
|
||||
|
||||
## Witness Prompt Design
|
||||
|
||||
The Witness is a new role - the per-rig "pit boss" who monitors workers. Here's the canonical prompt:
|
||||
|
||||
```markdown
|
||||
# Gastown Witness Context
|
||||
|
||||
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||
|
||||
## Your Role: WITNESS (Pit Boss for {{ rig_name }})
|
||||
|
||||
You are the per-rig worker monitor. You watch polecats, nudge them toward completion,
|
||||
verify clean git state before kills, and escalate stuck workers to the Mayor.
|
||||
|
||||
**You do NOT do implementation work.** Your job is oversight, not coding.
|
||||
|
||||
## Your Workspace
|
||||
|
||||
You work from: `~/ai/{{ rig_name }}/witness/`
|
||||
|
||||
You monitor polecats in: `~/ai/{{ rig_name }}/polecats/*/`
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
1. **Monitor workers**: Track polecat health and progress
|
||||
2. **Nudge**: Prompt slow workers toward completion
|
||||
3. **Pre-kill verification**: Ensure git state is clean before killing sessions
|
||||
4. **Session lifecycle**: Kill sessions, update worker state
|
||||
5. **Self-cycling**: Hand off to fresh session when context fills
|
||||
6. **Escalation**: Report stuck workers to Mayor
|
||||
|
||||
**Key principle**: You own ALL per-worker cleanup. Mayor is never involved in routine worker management.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Monitoring
|
||||
- `gt polecats {{ rig_name }}` - List all polecats and status
|
||||
- `gt polecat status <name>` - Detailed polecat status
|
||||
- `gt polecat git-state <name>` - Check git cleanliness
|
||||
|
||||
### Worker Management
|
||||
- `gt polecat nudge <name> "message"` - Send nudge to worker
|
||||
- `gt polecat kill <name>` - Kill worker session (after verification!)
|
||||
- `gt polecat wake <name>` - Mark worker as active
|
||||
- `gt polecat sleep <name>` - Mark worker as inactive
|
||||
|
||||
### Communication
|
||||
- `gt inbox` - Check your messages
|
||||
- `gt send mayor/ -s "Subject" -m "Message"` - Escalate to Mayor
|
||||
- `gt send {{ rig_name }}/<polecat> -s "Subject" -m "Message"` - Message worker
|
||||
|
||||
### Beads
|
||||
- `bd list --status=in_progress` - Active work in this rig
|
||||
- `bd show <id>` - Issue details
|
||||
|
||||
## Pre-Kill Verification Checklist
|
||||
|
||||
Before killing ANY polecat session, verify:
|
||||
|
||||
```
|
||||
[ ] 1. gt polecat git-state <name> # Must be clean
|
||||
[ ] 2. Check for uncommitted work # git status in polecat dir
|
||||
[ ] 3. Check for unpushed commits # git log origin/main..HEAD
|
||||
[ ] 4. Verify issue closed # bd show <id> shows closed
|
||||
```
|
||||
|
||||
If git state is dirty:
|
||||
1. Nudge the worker to clean up
|
||||
2. Wait for response (give 2-3 nudges max)
|
||||
3. If still dirty after 3 nudges → Escalate to Mayor
|
||||
|
||||
## Nudge Protocol
|
||||
|
||||
When a worker seems stuck or slow:
|
||||
|
||||
1. **First nudge** (gentle): "How's progress on <issue>? Need any help?"
|
||||
2. **Second nudge** (direct): "Please wrap up <issue> soon. What's blocking you?"
|
||||
3. **Third nudge** (final): "Final check on <issue>. If blocked, I'll escalate to Mayor."
|
||||
4. **Escalate**: If no progress after 3 nudges, send escalation to Mayor
|
||||
|
||||
Use: `gt polecat nudge <name> "<message>"`
|
||||
|
||||
## Escalation Template
|
||||
|
||||
When escalating to Mayor:
|
||||
|
||||
```
|
||||
gt send mayor/ -s "Escalation: <polecat> stuck on <issue>" -m "
|
||||
Worker: <polecat>
|
||||
Issue: <issue-id>
|
||||
Status: <description of problem>
|
||||
|
||||
Attempts:
|
||||
- Nudge 1: <date/time> - <response or no response>
|
||||
- Nudge 2: <date/time> - <response or no response>
|
||||
- Nudge 3: <date/time> - <response or no response>
|
||||
|
||||
Git state: <clean/dirty - details if dirty>
|
||||
|
||||
Recommendation: <what you think should happen>
|
||||
"
|
||||
```
|
||||
|
||||
## Session Self-Cycling
|
||||
|
||||
When your context fills up:
|
||||
|
||||
1. Capture current state (active workers, pending nudges, recent events)
|
||||
2. Send handoff to yourself:
|
||||
```
|
||||
gt send {{ rig_name }}/witness -s "🤝 HANDOFF: Witness session cycle" -m "
|
||||
Active workers: <list>
|
||||
Pending nudges: <list>
|
||||
Recent escalations: <list>
|
||||
Notes: <anything important>
|
||||
"
|
||||
```
|
||||
3. Exit cleanly
|
||||
|
||||
## Session End Checklist
|
||||
|
||||
```
|
||||
[ ] gt polecats {{ rig_name }} (check all worker states)
|
||||
[ ] Review any pending nudges
|
||||
[ ] Escalate any truly stuck workers
|
||||
[ ] HANDOFF if work incomplete:
|
||||
gt send {{ rig_name }}/witness -s "🤝 HANDOFF: ..." -m "..."
|
||||
```
|
||||
```
|
||||
|
||||
## Crew Prompt Design
|
||||
|
||||
Crew workers are the overseer's personal workspaces - a new role that differs from polecats:
|
||||
|
||||
```markdown
|
||||
# Gas Town Crew Worker Context
|
||||
|
||||
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||
|
||||
## Your Role: CREW WORKER ({{ name }} in {{ rig }})
|
||||
|
||||
You are a **crew worker** - the overseer's (human's) personal workspace within the {{ rig }} rig.
|
||||
Unlike polecats which are witness-managed and transient, you are:
|
||||
|
||||
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||
- **Long-lived identity**: You keep your name ({{ name }}) across sessions
|
||||
- **Integrated**: Mail and handoff mechanics work just like other Gas Town agents
|
||||
|
||||
**Key difference from polecats**: No one is watching you. You work directly with the overseer.
|
||||
|
||||
## Your Workspace
|
||||
|
||||
You work from: `{{ workspace_path }}`
|
||||
|
||||
This is a full git clone of the project repository.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Finding Work
|
||||
- `gt mail inbox` - Check your messages
|
||||
- `bd ready` - Available issues (if beads configured)
|
||||
- `bd list --status=in_progress` - Your active work
|
||||
|
||||
### Working
|
||||
- `bd update <id> --status=in_progress` - Claim an issue
|
||||
- `bd show <id>` - View issue details
|
||||
- Standard git workflow (status, add, commit, push)
|
||||
|
||||
### Completing Work
|
||||
- `bd close <id>` - Close the issue
|
||||
- `bd sync` - Sync beads changes
|
||||
|
||||
## Context Cycling (Handoff)
|
||||
|
||||
When context fills up, send a handoff mail to yourself:
|
||||
|
||||
```bash
|
||||
gt mail send {{ rig }}/{{ name }} -s "HANDOFF: Work in progress" -m "
|
||||
Working on: <issue>
|
||||
Branch: <branch>
|
||||
Status: <done/remaining>
|
||||
Next steps: <list>
|
||||
"
|
||||
```
|
||||
|
||||
Or use: `gt crew refresh {{ name }}`
|
||||
|
||||
## No Witness Monitoring
|
||||
|
||||
Unlike polecats, crew workers have no Witness oversight:
|
||||
- No automatic nudging
|
||||
- No pre-kill verification
|
||||
- No escalation on blocks
|
||||
- No automatic cleanup
|
||||
|
||||
**You are responsible for**: Managing progress, asking for help, keeping git clean.
|
||||
|
||||
## Session End Checklist
|
||||
|
||||
```
|
||||
[ ] git status / git push
|
||||
[ ] bd sync (if configured)
|
||||
[ ] Check inbox
|
||||
[ ] HANDOFF if incomplete
|
||||
```
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Role Prompts
|
||||
1. Port mayor.md.j2 to Go template
|
||||
2. Create witness.md (new!)
|
||||
3. Port refinery.md.j2
|
||||
4. Port polecat.md.j2
|
||||
5. Create crew.md (new!)
|
||||
6. Port unknown.md.j2
|
||||
|
||||
### Phase 2: Mail Templates
|
||||
1. Define mail template format in Go
|
||||
2. Port worker coordination templates
|
||||
3. Add handoff and escalation templates
|
||||
|
||||
### Phase 3: Spawn & Lifecycle
|
||||
1. Port spawn injection prompts
|
||||
2. Add lifecycle templates (nudge, escalation)
|
||||
3. Integrate with `gt sling` command
|
||||
|
||||
### Phase 4: CLI & Validation
|
||||
1. Implement `gt prime` with role detection
|
||||
2. Add `gt prompt` subcommands
|
||||
3. Add prompt validation/testing
|
||||
|
||||
## Related Issues
|
||||
|
||||
- `gt-u1j`: Port Gas Town to Go (parent epic)
|
||||
- `gt-f9x`: Town & Rig Management
|
||||
- `gt-cik`: Overseer Crew: User-managed persistent workspaces
|
||||
- `gt-iib`: Decentralized rig structure (affects prompt paths)
|
||||
@@ -1,282 +0,0 @@
|
||||
# The Propulsion Principle
|
||||
|
||||
> **Status**: Design document (experimental)
|
||||
> **See also**: [sling-design.md](sling-design.md) for implementation details
|
||||
> **See also**: [molecular-chemistry.md](molecular-chemistry.md) for the full Rig/Cook/Run lifecycle
|
||||
|
||||
## The Core Idea
|
||||
|
||||
We're trying a simple rule for agent behavior:
|
||||
|
||||
> **If you find something on your hook, YOU RUN IT.**
|
||||
|
||||
No decisions. No "should I?" No discretionary pondering. Just ignition.
|
||||
|
||||
```
|
||||
Hook has work → Work happens.
|
||||
```
|
||||
|
||||
That's the whole engine. Everything else is plumbing.
|
||||
|
||||
## Why It Works
|
||||
|
||||
The Propulsion Principle works because of three interlocking design decisions:
|
||||
|
||||
### 1. Agents Are Stateless
|
||||
|
||||
Agents have no memory between sessions. When a session starts, the agent has
|
||||
no inherent knowledge of what it was doing before, what the current project
|
||||
state is, or what decisions led to its existence.
|
||||
|
||||
This sounds like a limitation, but it's actually the foundation of resilience.
|
||||
Stateless agents can:
|
||||
- Be restarted at any time without data loss
|
||||
- Survive context compaction (the agent re-reads its state)
|
||||
- Hand off to new sessions seamlessly
|
||||
- Recover from crashes without corruption
|
||||
|
||||
### 2. Work Is Molecule-Driven
|
||||
|
||||
All work in Gas Town follows the **Rig → Cook → Run** lifecycle:
|
||||
- **Rig**: Compose workflow formulas (YAML source files)
|
||||
- **Cook**: Transform formulas into executable protos (expand macros, apply aspects)
|
||||
- **Run**: Agents execute the cooked workflow
|
||||
|
||||
A molecule (proto, mol, or wisp) defines:
|
||||
- What steps need to happen
|
||||
- What order they happen in (via dependencies)
|
||||
- What each step should accomplish
|
||||
|
||||
The agent doesn't decide what to do. The molecule tells it. The agent's job is
|
||||
execution, not planning. See [molecular-chemistry.md](molecular-chemistry.md)
|
||||
for the full lifecycle.
|
||||
|
||||
### 3. Hooks Deliver Work
|
||||
|
||||
Work arrives on an agent's **hook** - a pinned molecule or assigned issue that
|
||||
represents "your current work." When an agent wakes up:
|
||||
|
||||
1. Check the hook
|
||||
2. Found something? **Execute it.**
|
||||
3. Nothing? Check mail for new assignments.
|
||||
4. Repeat.
|
||||
|
||||
The hook eliminates decision-making about what to work on. If it's on your hook,
|
||||
it's your work. Run it.
|
||||
|
||||
## The Sling Lifecycle
|
||||
|
||||
The **sling** operation puts work on an agent's hook. This is the **Run** phase
|
||||
of the Rig → Cook → Run lifecycle (formulas have already been cooked into protos):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ gt sling lifecycle │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. POUR (if proto) 2. ASSIGN 3. PIN │
|
||||
│ proto → mol/wisp mol → agent → hook │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Proto │ ────────► │Mol/Wisp │ ─────► │ Hook │ │
|
||||
│ │(cooked) │ pour │(instance)│ assign │(pinned) │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │ │
|
||||
│ agent wakes │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────┐ │
|
||||
│ │ IGNITION│ │
|
||||
│ └─────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Pour** instantiates a proto into a running mol (persistent) or wisp (ephemeral).
|
||||
This is a phase transition from the Cook output to the Run phase.
|
||||
|
||||
**Key insight**: The agent never decides *whether* to run. The molecule tells
|
||||
it *what* to do. It executes until complete, then checks the hook again.
|
||||
|
||||
## Agent Startup Protocol
|
||||
|
||||
Every agent follows the same startup protocol:
|
||||
|
||||
```bash
|
||||
# 1. Check your hook
|
||||
gt mol status # What's on my hook?
|
||||
|
||||
# 2. Found something?
|
||||
# Output tells you exactly what to do.
|
||||
# Follow the molecule phases.
|
||||
|
||||
# 3. Nothing on hook?
|
||||
gt mail inbox # Check for new assignments
|
||||
|
||||
# 4. Repeat
|
||||
```
|
||||
|
||||
**Old way** (too much thinking):
|
||||
```bash
|
||||
gt mail inbox
|
||||
if has_molecule; then
|
||||
gt molecule instantiate ...
|
||||
# figure out what to do...
|
||||
fi
|
||||
```
|
||||
|
||||
**New way** (propulsion):
|
||||
```bash
|
||||
gt mol status # What's on my hook?
|
||||
# Just follow the molecule phases
|
||||
```
|
||||
|
||||
The difference is profound: the old way requires the agent to understand its
|
||||
situation and make decisions. The new way requires only execution.
|
||||
|
||||
## The Steam Engine Metaphor
|
||||
|
||||
Gas Town uses steam engine vocabulary throughout. The full lifecycle is
|
||||
**Rig → Cook → Run**:
|
||||
|
||||
| Metaphor | Gas Town | Lifecycle Phase | Description |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| **Recipe** | Formulas | Rig (source) | YAML files that compose workflows |
|
||||
| **Fuel** | Proto molecules | Cook (artifact) | Cooked templates ready to instantiate |
|
||||
| **Steam** | Wisps/Mols | Run (execution) | Active execution traces |
|
||||
| **Distillate** | Digests | (post-Run) | Condensed permanent records |
|
||||
| **Burn** | `bd mol burn` | (post-Run) | Discard without record |
|
||||
| **Squash** | `bd mol squash` | (post-Run) | Compress into digest |
|
||||
|
||||
Claude is fire. Claude Code is a Steam engine. Gas Town is a Steam Train, with
|
||||
Beads as the tracks. Wisps are steam vapors that dissipate after work is done.
|
||||
|
||||
The Propulsion Principle is the physics that makes the engine go:
|
||||
**Hook has work → Work happens.**
|
||||
|
||||
## Examples
|
||||
|
||||
### Good: Following the Principle
|
||||
|
||||
```markdown
|
||||
## Polecat Startup
|
||||
|
||||
1. Run `gt prime` to load context
|
||||
2. Check `gt mol status` for pinned work
|
||||
3. Found molecule? Execute each step in order.
|
||||
4. Complete? Run `gt done` to submit to merge queue.
|
||||
5. Request shutdown. You're ephemeral.
|
||||
```
|
||||
|
||||
### Good: Witness Patrol
|
||||
|
||||
```markdown
|
||||
## Witness Cycle
|
||||
|
||||
1. Bond a wisp molecule for this patrol cycle
|
||||
2. Execute patrol steps (check polecats, check refinery)
|
||||
3. Squash the wisp when done (creates digest)
|
||||
4. Sleep until next cycle
|
||||
5. Repeat forever
|
||||
```
|
||||
|
||||
### Anti-Pattern: Decision Paralysis
|
||||
|
||||
```markdown
|
||||
## DON'T DO THIS
|
||||
|
||||
1. Wake up
|
||||
2. Think about what I should do...
|
||||
3. Look at various issues and prioritize them
|
||||
4. Decide which one seems most important
|
||||
5. Start working on it
|
||||
```
|
||||
|
||||
This violates propulsion. If there's nothing on your hook, you check mail or
|
||||
wait. You don't go looking for work to decide to do. Work is *slung* at you.
|
||||
|
||||
### Anti-Pattern: Ignoring the Hook
|
||||
|
||||
```markdown
|
||||
## DON'T DO THIS
|
||||
|
||||
1. Wake up
|
||||
2. See molecule on my hook
|
||||
3. But I notice a more interesting issue over there...
|
||||
4. Work on that instead
|
||||
```
|
||||
|
||||
If it's on your hook, you run it. Period. The hook is not a suggestion.
|
||||
|
||||
### Anti-Pattern: Partial Execution
|
||||
|
||||
```markdown
|
||||
## DON'T DO THIS
|
||||
|
||||
1. Wake up
|
||||
2. See molecule with 5 steps
|
||||
3. Complete step 1
|
||||
4. Get bored, request shutdown
|
||||
5. Leave steps 2-5 incomplete
|
||||
```
|
||||
|
||||
Molecules are executed to completion. If you can't finish, you squash or burn
|
||||
explicitly. You don't just abandon mid-flight.
|
||||
|
||||
## Why Not Just Use TODO Lists?
|
||||
|
||||
LLM agents have short memories. A TODO list in the prompt will be forgotten
|
||||
during context compaction. A molecule in beads survives indefinitely because
|
||||
it's stored outside the agent's context.
|
||||
|
||||
**Molecules are external TODO lists that persist across sessions.**
|
||||
|
||||
This is the secret to autonomous operation: the agent's instructions survive
|
||||
the agent's death. New agent, same molecule, same progress point.
|
||||
|
||||
## Relationship to Nondeterministic Idempotence
|
||||
|
||||
The Propulsion Principle enables **nondeterministic idempotence** - the property
|
||||
that any workflow will eventually complete correctly, regardless of which agent
|
||||
runs which step, and regardless of crashes or restarts.
|
||||
|
||||
| Property | How Propulsion Enables It |
|
||||
|----------|---------------------------|
|
||||
| **Deterministic structure** | Molecules define exact steps |
|
||||
| **Nondeterministic execution** | Any agent can run any ready step |
|
||||
| **Idempotent progress** | Completed steps stay completed |
|
||||
| **Crash recovery** | Agent dies, molecule persists |
|
||||
| **Session survival** | Restart = re-read hook = continue |
|
||||
|
||||
## Implementation Status
|
||||
|
||||
The Propulsion Principle is implemented via three commands:
|
||||
|
||||
| Command | Action | Context | Use Case |
|
||||
|---------|--------|---------|----------|
|
||||
| `gt hook <bead>` | Attach only | Preserved | Assign work for later |
|
||||
| `gt sling <bead>` | Attach + run | Preserved | Kick off work immediately |
|
||||
| `gt handoff <bead>` | Attach + restart | Fresh | Restart with new context |
|
||||
|
||||
The algebra:
|
||||
```
|
||||
gt sling = gt hook + gt nudge "start working"
|
||||
gt handoff = gt hook + restart (GUPP kicks in)
|
||||
```
|
||||
|
||||
See [sling-design.md](sling-design.md) for detailed command reference.
|
||||
|
||||
## Summary
|
||||
|
||||
1. **One rule**: If you find something on your hook, you run it.
|
||||
2. **Stateless agents**: No memory between sessions - molecules provide continuity.
|
||||
3. **Molecule-driven**: Work is defined by molecules, not agent decisions.
|
||||
4. **Hook delivery**: Work arrives via sling, sits on hook until complete.
|
||||
5. **Just execute**: No thinking about whether. Only doing.
|
||||
|
||||
The Propulsion Principle is what makes Gas Town work as an autonomous,
|
||||
distributed, crash-resilient execution engine. It's the physics of the steam
|
||||
train.
|
||||
|
||||
```
|
||||
Hook has work → Work happens.
|
||||
```
|
||||
280
docs/reference.md
Normal file
280
docs/reference.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Gas Town Reference
|
||||
|
||||
Technical reference for Gas Town internals. Read the README first.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
~/gt/ Town root
|
||||
├── .beads/ Town-level beads (hq-* prefix)
|
||||
├── mayor/ Mayor config
|
||||
│ └── town.json
|
||||
└── <rig>/ Project container (NOT a git clone)
|
||||
├── config.json Rig identity
|
||||
├── .beads/ → mayor/rig/.beads
|
||||
├── .repo.git/ Bare repo (shared by worktrees)
|
||||
├── mayor/rig/ Mayor's clone (canonical beads)
|
||||
├── refinery/rig/ Worktree on main
|
||||
├── witness/ No clone (monitors only)
|
||||
├── crew/<name>/ Human workspaces
|
||||
└── polecats/<name>/ Worker worktrees
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Rig root is a container, not a clone
|
||||
- `.repo.git/` is bare - refinery and polecats are worktrees
|
||||
- Mayor clone holds canonical `.beads/`, others inherit via redirect
|
||||
|
||||
## Beads Routing
|
||||
|
||||
Gas Town routes beads commands based on issue ID prefix. You don't need to think
|
||||
about which database to use - just use the issue ID.
|
||||
|
||||
```bash
|
||||
bd show gt-xyz # Routes to gastown rig's beads
|
||||
bd show hq-abc # Routes to town-level beads
|
||||
bd show wyv-123 # Routes to wyvern rig's beads
|
||||
```
|
||||
|
||||
**How it works**: Routes are defined in `~/gt/.beads/routes.jsonl`. Each rig's
|
||||
prefix maps to its beads location (the mayor's clone in that rig).
|
||||
|
||||
| Prefix | Routes To | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `hq-*` | `~/gt/.beads/` | Mayor mail, cross-rig coordination |
|
||||
| `gt-*` | `~/gt/gastown/mayor/rig/.beads/` | Gastown project issues |
|
||||
| `wyv-*` | `~/gt/wyvern/mayor/rig/.beads/` | Wyvern project issues |
|
||||
|
||||
Debug routing: `BD_DEBUG_ROUTING=1 bd show <id>`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Rig Config (`config.json`)
|
||||
```json
|
||||
{
|
||||
"type": "rig",
|
||||
"name": "myproject",
|
||||
"git_url": "https://github.com/...",
|
||||
"beads": { "prefix": "mp" }
|
||||
}
|
||||
```
|
||||
|
||||
### Settings (`settings/config.json`)
|
||||
```json
|
||||
{
|
||||
"theme": "desert",
|
||||
"max_workers": 5,
|
||||
"merge_queue": { "enabled": true }
|
||||
}
|
||||
```
|
||||
|
||||
### Runtime (`.runtime/` - gitignored)
|
||||
Process state, PIDs, ephemeral data.
|
||||
|
||||
## Formula Format
|
||||
|
||||
```toml
|
||||
formula = "name"
|
||||
type = "workflow" # workflow | expansion | aspect
|
||||
version = 1
|
||||
description = "..."
|
||||
|
||||
[vars.feature]
|
||||
description = "..."
|
||||
required = true
|
||||
|
||||
[[steps]]
|
||||
id = "step-id"
|
||||
title = "{{feature}}"
|
||||
description = "..."
|
||||
needs = ["other-step"] # Dependencies
|
||||
```
|
||||
|
||||
**Composition:**
|
||||
```toml
|
||||
extends = ["base-formula"]
|
||||
|
||||
[compose]
|
||||
aspects = ["cross-cutting"]
|
||||
|
||||
[[compose.expand]]
|
||||
target = "step-id"
|
||||
with = "macro-formula"
|
||||
```
|
||||
|
||||
## Molecule Lifecycle
|
||||
|
||||
```
|
||||
Formula (source TOML) ─── "Ice-9"
|
||||
│
|
||||
▼ bd cook
|
||||
Protomolecule (frozen template) ─── Solid
|
||||
│
|
||||
├─▶ bd pour ──▶ Mol (persistent) ─── Liquid ──▶ bd squash ──▶ Digest
|
||||
│
|
||||
└─▶ bd wisp ──▶ Wisp (ephemeral) ─── Vapor ──┬▶ bd squash ──▶ Digest
|
||||
└▶ bd burn ──▶ (gone)
|
||||
```
|
||||
|
||||
**Note**: Wisps are stored in `.beads/` with an ephemeral flag - they're not
|
||||
persisted to JSONL. They exist only in memory during execution.
|
||||
|
||||
## Molecule Commands
|
||||
|
||||
```bash
|
||||
# Formulas
|
||||
bd formula list # Available formulas
|
||||
bd formula show <name> # Formula details
|
||||
bd cook <formula> # Formula → Proto
|
||||
|
||||
# Molecules
|
||||
bd mol list # Available protos
|
||||
bd mol show <id> # Proto details
|
||||
bd pour <proto> # Create mol
|
||||
bd wisp <proto> # Create wisp
|
||||
bd mol bond <proto> <parent> # Attach to existing mol
|
||||
bd mol squash <id> # Condense to digest
|
||||
bd mol burn <id> # Discard wisp
|
||||
```
|
||||
|
||||
## Agent Lifecycle
|
||||
|
||||
### Polecat Shutdown
|
||||
```
|
||||
1. Complete work steps
|
||||
2. bd mol squash (create digest)
|
||||
3. Submit to merge queue
|
||||
4. gt handoff (request shutdown)
|
||||
5. Wait for Witness to kill session
|
||||
6. Witness removes worktree + branch
|
||||
```
|
||||
|
||||
### Session Cycling
|
||||
```
|
||||
1. Agent notices context filling
|
||||
2. gt handoff (sends mail to self)
|
||||
3. Manager kills session
|
||||
4. Manager starts new session
|
||||
5. New session reads handoff mail
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `BEADS_DIR` | Point to shared beads database |
|
||||
| `BEADS_NO_DAEMON` | Required for worktree polecats |
|
||||
| `GT_TOWN_ROOT` | Override town root detection |
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### Town Management
|
||||
```bash
|
||||
gt install [path] # Create town
|
||||
gt install --git # With git init
|
||||
gt doctor # Health check
|
||||
gt doctor --fix # Auto-repair
|
||||
```
|
||||
|
||||
### Rig Management
|
||||
```bash
|
||||
gt rig add <name> --remote=<url>
|
||||
gt rig list
|
||||
gt rig remove <name>
|
||||
```
|
||||
|
||||
### Work Assignment
|
||||
```bash
|
||||
gt sling <bead> <rig> # Assign to polecat
|
||||
gt sling <bead> <rig> --molecule=<proto>
|
||||
```
|
||||
|
||||
### Communication
|
||||
```bash
|
||||
gt mail inbox
|
||||
gt mail read <id>
|
||||
gt mail send <addr> -s "Subject" -m "Body"
|
||||
gt mail send --human -s "..." # To overseer
|
||||
```
|
||||
|
||||
### Sessions
|
||||
```bash
|
||||
gt handoff # Request cycle (context-aware)
|
||||
gt handoff --shutdown # Terminate (polecats)
|
||||
gt session stop <rig>/<agent>
|
||||
gt peek <agent> # Check health
|
||||
gt nudge <agent> # Wake stuck worker
|
||||
```
|
||||
|
||||
### Emergency
|
||||
```bash
|
||||
gt stop --all # Kill all sessions
|
||||
gt stop --rig <name> # Kill rig sessions
|
||||
```
|
||||
|
||||
## Beads Commands (bd)
|
||||
|
||||
```bash
|
||||
bd ready # Work with no blockers
|
||||
bd list --status=open
|
||||
bd list --status=in_progress
|
||||
bd show <id>
|
||||
bd create --title="..." --type=task
|
||||
bd update <id> --status=in_progress
|
||||
bd close <id>
|
||||
bd dep add <child> <parent> # child depends on parent
|
||||
bd sync # Push/pull changes
|
||||
```
|
||||
|
||||
## Patrol Agents
|
||||
|
||||
Deacon, Witness, and Refinery run continuous patrol loops using wisps:
|
||||
|
||||
| Agent | Patrol Molecule | Responsibility |
|
||||
|-------|-----------------|----------------|
|
||||
| **Deacon** | `mol-deacon-patrol` | Agent lifecycle, plugin execution, health checks |
|
||||
| **Witness** | `mol-witness-patrol` | Monitor polecats, nudge stuck workers |
|
||||
| **Refinery** | `mol-refinery-patrol` | Process merge queue, review PRs |
|
||||
|
||||
```
|
||||
1. bd wisp mol-<role>-patrol
|
||||
2. Execute steps (check workers, process queue, run plugins)
|
||||
3. bd mol squash (or burn if routine)
|
||||
4. Loop
|
||||
```
|
||||
|
||||
## Plugin Molecules
|
||||
|
||||
Plugins are molecules with specific labels:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mol-security-scan",
|
||||
"labels": ["template", "plugin", "witness", "tier:haiku"]
|
||||
}
|
||||
```
|
||||
|
||||
Patrol molecules bond plugins dynamically:
|
||||
```bash
|
||||
bd mol bond mol-security-scan $PATROL_ID --var scope="$SCOPE"
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| Agent in wrong directory | Check cwd, `gt doctor` |
|
||||
| Beads prefix mismatch | Check `bd show` vs rig config |
|
||||
| Worktree conflicts | Ensure `BEADS_NO_DAEMON=1` for polecats |
|
||||
| Stuck worker | `gt nudge`, then `gt peek` |
|
||||
| Dirty git state | Commit or discard, then `gt handoff` |
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**Bare repo pattern**: `.repo.git/` is bare (no working dir). Refinery and polecats are worktrees sharing refs. Polecat branches visible to refinery immediately.
|
||||
|
||||
**Beads as control plane**: No separate orchestrator. Molecule steps ARE beads issues. State transitions are git commits.
|
||||
|
||||
**Nondeterministic idempotence**: Any worker can continue any molecule. Steps are atomic checkpoints in beads.
|
||||
|
||||
<!-- TODO: Add architecture diagram -->
|
||||
@@ -1,14 +0,0 @@
|
||||
# Rig, Cook, Run
|
||||
|
||||
> **This document has been consolidated into [molecular-chemistry.md](molecular-chemistry.md).**
|
||||
>
|
||||
> See the following sections:
|
||||
> - [The Work Lifecycle: Rig → Cook → Run](molecular-chemistry.md#the-work-lifecycle-rig--cook--run)
|
||||
> - [The Complete Artifact Graph](molecular-chemistry.md#the-complete-artifact-graph)
|
||||
> - [Two Composition Operators](molecular-chemistry.md#two-composition-operators)
|
||||
> - [Formulas: The Source Layer](molecular-chemistry.md#formulas-the-source-layer)
|
||||
> - [The Polymorphic Bond Operator](molecular-chemistry.md#the-polymorphic-bond-operator)
|
||||
|
||||
---
|
||||
|
||||
*Consolidated: 2025-12-23*
|
||||
@@ -1,126 +0,0 @@
|
||||
# Session Communication: Nudge and Peek
|
||||
|
||||
Gas Town agents communicate with Claude Code sessions through **two canonical commands**:
|
||||
|
||||
| Command | Direction | Purpose |
|
||||
|---------|-----------|---------|
|
||||
| `gt nudge` | You → Agent | Send a message reliably |
|
||||
| `gt peek` | Agent → You | Read recent output |
|
||||
|
||||
## Why Not Raw tmux?
|
||||
|
||||
**tmux send-keys is unreliable for Claude Code sessions.**
|
||||
|
||||
The problem: When you send text followed by Enter using `tmux send-keys "message" Enter`,
|
||||
the Enter key often arrives before the paste completes. Claude receives a truncated
|
||||
message or the Enter appends to the previous line.
|
||||
|
||||
This is a race condition in tmux's input handling. It's not a bug - tmux wasn't
|
||||
designed for pasting multi-line content to interactive AI sessions.
|
||||
|
||||
### The Reliable Pattern
|
||||
|
||||
`gt nudge` uses a tested, reliable pattern:
|
||||
|
||||
```go
|
||||
// 1. Send text in literal mode (handles special characters)
|
||||
tmux send-keys -t session -l "message"
|
||||
|
||||
// 2. Wait 500ms for paste to complete
|
||||
time.Sleep(500ms)
|
||||
|
||||
// 3. Send Enter as separate command
|
||||
tmux send-keys -t session Enter
|
||||
```
|
||||
|
||||
**Never use raw tmux send-keys for agent sessions.** Always use `gt nudge`.
|
||||
|
||||
## Command Reference
|
||||
|
||||
### gt nudge
|
||||
|
||||
Send a message to a polecat's Claude session:
|
||||
|
||||
```bash
|
||||
gt nudge <rig/polecat> <message>
|
||||
|
||||
# Examples
|
||||
gt nudge gastown/furiosa "Check your mail and start working"
|
||||
gt nudge gastown/alpha "What's your status?"
|
||||
gt nudge gastown/beta "Stop what you're doing and read gt-xyz"
|
||||
```
|
||||
|
||||
### gt peek
|
||||
|
||||
View recent output from a polecat's session:
|
||||
|
||||
```bash
|
||||
gt peek <rig/polecat> [lines]
|
||||
|
||||
# Examples
|
||||
gt peek gastown/furiosa # Last 100 lines (default)
|
||||
gt peek gastown/furiosa 50 # Last 50 lines
|
||||
gt peek gastown/furiosa -n 200 # Last 200 lines
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Check on a polecat
|
||||
|
||||
```bash
|
||||
# See what they're doing
|
||||
gt peek gastown/furiosa
|
||||
|
||||
# Ask for status
|
||||
gt nudge gastown/furiosa "What's your current status?"
|
||||
```
|
||||
|
||||
### Redirect a polecat
|
||||
|
||||
```bash
|
||||
# Stop current work and pivot
|
||||
gt nudge gastown/furiosa "Stop current task. New priority: read and act on gt-xyz"
|
||||
```
|
||||
|
||||
### Wake up a stuck polecat
|
||||
|
||||
```bash
|
||||
# Sometimes agents get stuck waiting for input
|
||||
gt nudge gastown/furiosa "Continue working"
|
||||
```
|
||||
|
||||
### Batch check all polecats
|
||||
|
||||
```bash
|
||||
# Quick status check
|
||||
for p in furiosa nux slit; do
|
||||
echo "=== $p ==="
|
||||
gt peek gastown/$p 20
|
||||
done
|
||||
```
|
||||
|
||||
## For Template Authors
|
||||
|
||||
When writing agent templates (deacon.md.tmpl, polecat.md.tmpl, etc.), include this guidance:
|
||||
|
||||
```markdown
|
||||
## Session Communication
|
||||
|
||||
To send messages to other agents, use gt commands:
|
||||
- `gt nudge <rig/polecat> <message>` - Send reliably
|
||||
- `gt peek <rig/polecat>` - Read output
|
||||
|
||||
⚠️ NEVER use raw tmux send-keys - it's unreliable for Claude sessions.
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The nudge/peek commands wrap these underlying functions:
|
||||
|
||||
| Command | Wrapper | Underlying |
|
||||
|---------|---------|------------|
|
||||
| `gt nudge` | `tmux.NudgeSession()` | literal send-keys + delay + Enter |
|
||||
| `gt peek` | `session.Capture()` | tmux capture-pane |
|
||||
|
||||
The 500ms delay in NudgeSession was determined empirically. Shorter delays fail
|
||||
intermittently; longer delays work but slow down communication unnecessarily.
|
||||
@@ -1,268 +0,0 @@
|
||||
# Session Lifecycle: Context Cycling in Gas Town
|
||||
|
||||
> **Status**: Foundational Architecture
|
||||
> **See also**: [beads-data-plane.md](beads-data-plane.md), [propulsion-principle.md](propulsion-principle.md)
|
||||
|
||||
## Overview
|
||||
|
||||
Gas Town agents have persistent identities, but their sessions are ephemeral. This is the
|
||||
"cattle not pets" model from Kubernetes: the agent (Mayor, Witness, polecat Toast) is
|
||||
the persistent identity; the Claude Code session running that agent is disposable cattle
|
||||
that can be killed and respawned at any time.
|
||||
|
||||
Work persists in beads. Sessions come and go. This document explains the unified model
|
||||
for session lifecycle across all roles.
|
||||
|
||||
## The Single-Bond Principle
|
||||
|
||||
> A bead should be achievable in a single typical Claude Code session.
|
||||
|
||||
In molecular chemistry, a single bond connects exactly two atoms. In Gas Town, a single
|
||||
session should complete exactly one bead. This is the atomic unit of efficient work.
|
||||
|
||||
**Why this matters:**
|
||||
- Clean execution: start session → do work → cycle
|
||||
- No mid-work state to track outside beads
|
||||
- Handoffs are clean boundaries, not messy interruptions
|
||||
- The system stays efficient as it scales
|
||||
|
||||
**The ceiling rises over time.** What fits in "one session" grows with model capability.
|
||||
Opus 4.5 can handle larger beads than earlier models. But the principle remains: if a
|
||||
bead won't fit, decompose it.
|
||||
|
||||
### Violating the Principle
|
||||
|
||||
You can violate the Single-Bond Principle. Gas Town is flexible. But violations trigger
|
||||
the **Happy Path** or **Sad Path**:
|
||||
|
||||
**Happy Path** (controlled):
|
||||
1. Worker recognizes mid-work: "This won't fit in one session"
|
||||
2. Choose:
|
||||
- (a) Partial implementation → handoff with context notes → successor continues
|
||||
- (b) Self-decompose: convert bead to epic, create sub-tasks, continue with first task
|
||||
3. If scope grew substantially → notify Witness/Mayor/Human for rescheduling
|
||||
|
||||
**Sad Path** (uncontrolled):
|
||||
- Worker underestimates → context fills unexpectedly → compaction triggers
|
||||
- Compaction is the least desirable handoff: sudden, no context notes, state may be unclear
|
||||
- Successor must reconstruct context from beads and git state
|
||||
|
||||
The Happy Path preserves continuity. The Sad Path forces recovery.
|
||||
|
||||
## The Context Budget Model
|
||||
|
||||
**N is not "sessions" or "rounds" - it's a proxy for context budget.**
|
||||
|
||||
Every action consumes context:
|
||||
|
||||
| Action Type | Context Cost | Examples |
|
||||
|-------------|--------------|----------|
|
||||
| Boring | ~1-5% | Health ping, empty inbox check, clean rebase |
|
||||
| Interesting | 20-50%+ | Conflict resolution, debugging, implementation |
|
||||
|
||||
The heuristics (N=20 for Deacon, N=1 for Polecat) estimate "when will we hit ~80% context?"
|
||||
They're tuning parameters, not laws of physics.
|
||||
|
||||
### Why "Interesting" Triggers Immediate Handoff
|
||||
|
||||
An "interesting" event consumes context like a full bead:
|
||||
- Reading conflict diffs
|
||||
- Debugging test failures
|
||||
- Making implementation decisions
|
||||
- Writing substantial code
|
||||
|
||||
After an interesting event, you've used your context budget. Fresh session handles
|
||||
the next event better than a session at 80% capacity.
|
||||
|
||||
## Unified Session Cycling Model
|
||||
|
||||
All workers use the same model. Only the heuristic differs:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ SESSION LIFECYCLE │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ │
|
||||
│ │ Start │───▶│ Execute Step │───▶│ Check Budget│───▶│ Handoff │ │
|
||||
│ │ Session │ │ │ │ │ │ or Done │ │
|
||||
│ └──────────┘ └──────┬───────┘ └──────┬──────┘ └──────────┘ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ Budget OK? ──Yes──▶ Loop to next step │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Per-Role Heuristics
|
||||
|
||||
| Role | N | Unit | "Interesting" Trigger |
|
||||
|------|---|------|----------------------|
|
||||
| **Polecat** | 1 | assignment | Every assignment is interesting |
|
||||
| **Crew** | ∞ | human decides | Context full or human requests |
|
||||
| **Deacon** | 20 | patrol rounds | Lifecycle request, remediation, escalation |
|
||||
| **Witness** | 15 | polecat interactions | (interactions are the cost unit) |
|
||||
| **Refinery** | 20 | simple MRs | Complex rebase, conflict resolution |
|
||||
|
||||
**Polecat (N=1)**: Every assignment fills context with implementation details. There's
|
||||
no "boring" polecat work - it's all interesting. Forced to cycle when work is complete
|
||||
and merged.
|
||||
|
||||
**Crew (N=∞)**: Human-managed. Cycle when it feels right, or when context fills naturally.
|
||||
|
||||
**Patrol Workers (N=15-20)**: Routine checks are cheap. Batch many before cycling. But
|
||||
any "interesting" event resets to immediate handoff.
|
||||
|
||||
## Mid-Step Handoff
|
||||
|
||||
Any worker can handoff mid-step. The molecule tracks state:
|
||||
|
||||
```
|
||||
Step 3 of 7, context filling
|
||||
│
|
||||
▼
|
||||
gt handoff -s "Context notes" -m "Details..."
|
||||
│
|
||||
▼
|
||||
Session dies, successor spawns
|
||||
│
|
||||
▼
|
||||
gt mol status → sees step 3 open → continues
|
||||
```
|
||||
|
||||
This is normal and supported. The Single-Bond Principle is aspirational, not mandatory.
|
||||
Mid-step handoff is the Happy Path when a bead turns out larger than estimated.
|
||||
|
||||
## Handoff Mechanics
|
||||
|
||||
How handoff works depends on the role:
|
||||
|
||||
### Polecats: Outer Ring Handoff
|
||||
|
||||
Polecats can't self-respawn - they need cleanup (worktree removal). They go through
|
||||
their Witness:
|
||||
|
||||
```
|
||||
Polecat Witness
|
||||
│ │
|
||||
│ gt done (or gt handoff) │
|
||||
│ │
|
||||
▼ │
|
||||
Send LIFECYCLE mail ──────────────▶ │
|
||||
│ │
|
||||
│ (wait for termination) ▼
|
||||
│ Patrol inbox-check step
|
||||
│ │
|
||||
│ ▼
|
||||
│ Process LIFECYCLE request
|
||||
│ │
|
||||
◀─────────────────────────────── Kill session, cleanup worktree
|
||||
```
|
||||
|
||||
### Non-Polecats: Self-Respawn
|
||||
|
||||
Crew, Mayor, Witness, Refinery, Deacon are persistent. They respawn themselves:
|
||||
|
||||
```
|
||||
Agent
|
||||
│
|
||||
│ gt handoff -s "Context" -m "Notes"
|
||||
│
|
||||
▼
|
||||
Send handoff mail to self (optional)
|
||||
│
|
||||
▼
|
||||
tmux respawn-pane -k (kills self, starts fresh claude)
|
||||
│
|
||||
▼
|
||||
New session runs gt prime, finds hook, continues
|
||||
```
|
||||
|
||||
No outer ring needed - they just restart in place.
|
||||
|
||||
## State Persistence
|
||||
|
||||
What survives a handoff:
|
||||
|
||||
| Persists | Where | Example |
|
||||
|----------|-------|---------|
|
||||
| Pinned molecule | Beads (rig) | What you're working on |
|
||||
| Handoff mail | Beads (town) | Context notes for successor |
|
||||
| Git commits | Git | Code changes |
|
||||
| Issue state | Beads | Open/closed, assignee, etc. |
|
||||
|
||||
What doesn't survive:
|
||||
|
||||
| Lost | Why |
|
||||
|------|-----|
|
||||
| Claude context | Session dies |
|
||||
| In-memory state | Process dies |
|
||||
| Uncommitted changes | Not in git |
|
||||
| Unflushed beads | Not synced |
|
||||
|
||||
**Key insight**: The molecule is the source of truth for work. Handoff mail is
|
||||
supplementary context. You could lose all handoff mail and still know what to
|
||||
work on from your hook.
|
||||
|
||||
## The Oversized Bead Protocol
|
||||
|
||||
When you realize mid-work that a bead won't fit in one session:
|
||||
|
||||
### Option A: Partial Implementation + Handoff
|
||||
|
||||
1. Commit what you have (even if incomplete)
|
||||
2. Update bead description with progress notes
|
||||
3. `gt handoff -s "Partial impl" -m "Completed X, remaining: Y, Z"`
|
||||
4. Successor continues from your checkpoint
|
||||
|
||||
Best when: Work is linear, successor can pick up where you left off.
|
||||
|
||||
### Option B: Self-Decomposition
|
||||
|
||||
1. Convert current bead to epic (if not already)
|
||||
2. Create sub-task beads for remaining work
|
||||
3. Close or update original with "decomposed into X, Y, Z"
|
||||
4. Continue with first sub-task
|
||||
|
||||
Best when: Work has natural breakpoints, parallelization possible.
|
||||
|
||||
### When to Escalate
|
||||
|
||||
Notify Witness/Mayor/Human when:
|
||||
- Scope grew >2x from original estimate
|
||||
- Decomposition affects other scheduled work
|
||||
- Blockers require external input
|
||||
- You're unsure which option to choose
|
||||
|
||||
```bash
|
||||
# Escalate to Witness (polecat)
|
||||
gt mail send <rig>/witness -s "Scope change: <bead-id>" -m "..."
|
||||
|
||||
# Escalate to Mayor (cross-rig)
|
||||
gt mail send mayor/ -s "Scope change: <bead-id>" -m "..."
|
||||
|
||||
# Escalate to Human
|
||||
gt mail send --human -s "Need input: <bead-id>" -m "..."
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
1. **Single-Bond Principle**: One bead ≈ one session (aspirational)
|
||||
2. **Context Budget**: N-heuristics proxy for "when will context fill?"
|
||||
3. **Unified Model**: All roles cycle the same way, different heuristics
|
||||
4. **Mid-Step Handoff**: Normal and supported via persistent molecules
|
||||
5. **Happy Path**: Recognize early, decompose or partial-handoff
|
||||
6. **Sad Path**: Compaction - recover from beads + git state
|
||||
|
||||
The system is designed for ephemeral sessions with persistent state. Embrace cycling.
|
||||
Fresh context handles problems better than stale context at capacity.
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [beads-data-plane.md](beads-data-plane.md) - How state persists in beads
|
||||
- [propulsion-principle.md](propulsion-principle.md) - The "RUN IT" protocol
|
||||
- [pinned-beads-design.md](pinned-beads-design.md) - Hook mechanics
|
||||
- [wisp-architecture.md](wisp-architecture.md) - Ephemeral patrol molecules
|
||||
@@ -1,193 +0,0 @@
|
||||
# Hook, Sling, Handoff: Work Assignment Commands
|
||||
|
||||
> **Status**: Implemented
|
||||
> **Updated**: 2024-12-24
|
||||
> **See also**: [molecular-chemistry.md](molecular-chemistry.md) for the full Rig → Cook → Run lifecycle
|
||||
|
||||
## The Propulsion Principle (GUPP)
|
||||
|
||||
The Gastown Universal Propulsion Principle is simple:
|
||||
|
||||
> **If you find something on your hook, YOU RUN IT.**
|
||||
|
||||
No decisions. No "should I?" Just ignition. Agents execute what's on their
|
||||
hook rather than deciding what to do.
|
||||
|
||||
```
|
||||
Hook has work → Work happens.
|
||||
```
|
||||
|
||||
That's the whole engine. Everything else is plumbing.
|
||||
|
||||
## The Command Menagerie
|
||||
|
||||
Three commands for putting work on hooks, each with distinct semantics:
|
||||
|
||||
| Command | Action | Context | Use Case |
|
||||
|---------|--------|---------|----------|
|
||||
| `gt hook <bead>` | Attach only | Preserved | Assign work for later |
|
||||
| `gt sling <bead>` | Attach + run | Preserved | Kick off work immediately |
|
||||
| `gt handoff <bead>` | Attach + restart | Fresh | Restart with new context |
|
||||
|
||||
### The Relationships
|
||||
|
||||
```
|
||||
gt hook = pin (assign)
|
||||
gt sling = hook + run now
|
||||
gt handoff = hook + restart (fresh context via GUPP)
|
||||
```
|
||||
|
||||
A hypothetical `gt run` lurks in the liminal UX space - it would be "start
|
||||
working on the hooked item now" without the attach step. Currently implicit
|
||||
in startup via GUPP.
|
||||
|
||||
## gt hook: Durability Primitive
|
||||
|
||||
```bash
|
||||
gt hook <bead-id> [flags]
|
||||
```
|
||||
|
||||
**What it does**: Attaches work to your hook. Nothing more.
|
||||
|
||||
**When to use**:
|
||||
- You want to assign work but chat with the agent first
|
||||
- Setting up work before triggering execution
|
||||
- Preparing for a handoff without immediate restart
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
gt hook gt-abc # Attach issue
|
||||
gt hook gt-abc -s "Context here" # With subject for later handoff mail
|
||||
```
|
||||
|
||||
The hook provides **durability** - the agent can restart, compact, or hand off,
|
||||
but until the hook is changed or closed, that agent owns the work.
|
||||
|
||||
## gt sling: Hook and Run Now
|
||||
|
||||
```bash
|
||||
gt sling <bead-id> [target] [flags]
|
||||
```
|
||||
|
||||
**What it does**:
|
||||
1. Attaches bead to the hook (durability)
|
||||
2. Injects a prompt to start working NOW
|
||||
|
||||
**When to use**:
|
||||
- You've been chatting with an agent and want to kick off a workflow
|
||||
- You want to assign work to another agent that has useful context
|
||||
- You (Overseer) want to start work then attend to another window
|
||||
- Starting work without losing current conversation context
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
gt sling gt-abc # Hook and start on it now
|
||||
gt sling gt-abc -s "Fix the bug" # With context subject
|
||||
gt sling gt-abc crew # Sling to crew worker
|
||||
gt sling gt-abc gastown/crew/max # Sling to specific agent
|
||||
```
|
||||
|
||||
**Key distinction from handoff**: Sling preserves context. The agent doesn't
|
||||
restart - they receive an injected prompt and begin working with their current
|
||||
conversation history intact.
|
||||
|
||||
## gt handoff: Hook and Restart
|
||||
|
||||
```bash
|
||||
gt handoff [bead-or-role] [flags]
|
||||
```
|
||||
|
||||
**What it does**:
|
||||
1. If bead provided: attaches to hook first
|
||||
2. Restarts the session (respawns pane)
|
||||
3. New session wakes, finds hook, runs via GUPP
|
||||
|
||||
**When to use**:
|
||||
- Context has become too large or stale
|
||||
- You want a fresh session but with work continuity
|
||||
- Handing off your own session before context limits
|
||||
- Triggering restart on another agent's session
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
gt handoff # Just restart (uses existing hook)
|
||||
gt handoff gt-abc # Hook bead, then restart
|
||||
gt handoff gt-abc -s "Fix it" # Hook with context, then restart
|
||||
gt handoff -s "Context" -m "Notes" # Restart with handoff mail
|
||||
gt handoff crew # Hand off crew session
|
||||
gt handoff mayor # Hand off mayor session
|
||||
```
|
||||
|
||||
**Interaction with roles**: The optional argument is polymorphic:
|
||||
- If it looks like a bead ID (prefix `gt-`, `hq-`, `bd-`): hooks it
|
||||
- Otherwise: treats it as a role to hand off
|
||||
|
||||
## Agent Lifecycle with Hooks
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ Agent Hook Lifecycle │
|
||||
├────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ STARTUP (GUPP) │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ gt mol status │ → hook has work? → RUN IT │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Work on it │ ← agent executes molecule/bead │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Complete/Exit │ → close hook, check for next │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ REASSIGNMENT (sling) │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ gt sling <id> │ → hook updates, prompt injected │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Starts working │ ← preserves context │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ CONTEXT REFRESH (handoff) │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │gt handoff <id> │ → hook updates, session restarts │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Fresh context │ ← GUPP kicks in on startup │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Command Quick Reference
|
||||
|
||||
```bash
|
||||
# Just assign, don't start
|
||||
gt hook gt-xyz
|
||||
|
||||
# Assign and start now (keep context)
|
||||
gt sling gt-xyz
|
||||
gt sling gt-xyz crew # To another agent
|
||||
|
||||
# Assign and restart (fresh context)
|
||||
gt handoff gt-xyz
|
||||
gt handoff # Just restart, use existing hook
|
||||
|
||||
# Check what's on hook
|
||||
gt mol status
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- `docs/propulsion-principle.md` - GUPP design
|
||||
- `docs/molecular-chemistry.md` - Full Rig/Cook/Run lifecycle (formulas → protos → mols/wisps)
|
||||
- `docs/wisp-architecture.md` - Ephemeral wisps for patrol loops
|
||||
- `internal/wisp/` - Hook storage implementation
|
||||
413
docs/vision.md
413
docs/vision.md
@@ -1,413 +0,0 @@
|
||||
# Gas Town: Ideas We're Exploring
|
||||
|
||||
> **Status**: These are ideas we're experimenting with, not proven solutions.
|
||||
> We're sharing them in case others find them useful or want to explore together.
|
||||
|
||||
Gas Town is an experiment in multi-agent coordination. We use steam-age metaphors
|
||||
to think about how work flows through a system of AI agents.
|
||||
|
||||
---
|
||||
|
||||
## Idea 1: The Steam Engine Metaphor
|
||||
|
||||
```
|
||||
Claude = Fire (the energy source)
|
||||
Claude Code = Steam Engine (harnesses the fire)
|
||||
Gas Town = Steam Train (coordinates engines on tracks)
|
||||
Beads = Railroad Tracks (the persistent ledger of work)
|
||||
```
|
||||
|
||||
The engine does work and generates steam. Gas Town coordinates many engines on
|
||||
a shared network, routing work to the right engines, tracking outcomes, and
|
||||
ensuring nothing is lost.
|
||||
|
||||
| Component | Phase | Role |
|
||||
|-----------|-------|------|
|
||||
| **Proto** | Solid (crystal) | Frozen workflow templates |
|
||||
| **Mol** | Liquid | Flowing durable work instances |
|
||||
| **Wisp** | Vapor (gas) | Transient ephemeral traces |
|
||||
| **Digest** | Distillate | Compressed permanent records |
|
||||
|
||||
---
|
||||
|
||||
## Idea 2: Village vs Hierarchy
|
||||
|
||||
We're exploring whether a "village" model works better than centralized monitoring.
|
||||
|
||||
**The pattern we're trying to avoid:**
|
||||
```
|
||||
Centralized Monitor → watches all workers → single point of failure
|
||||
→ fragile protocols → cascading failures
|
||||
```
|
||||
|
||||
**The pattern we're experimenting with:**
|
||||
```
|
||||
Every worker → understands the whole → can help any neighbor
|
||||
→ peek is encouraged → distributed awareness
|
||||
→ ant colony without murder → self-healing system
|
||||
```
|
||||
|
||||
### Aspiration: Antifragility
|
||||
|
||||
We're aiming for anti-fragility - a system that gets stronger from stress rather
|
||||
than just surviving it. Whether we achieve this is an open question.
|
||||
|
||||
Properties we're trying to build:
|
||||
|
||||
- **Distributed awareness**: Every agent understands the system deeply
|
||||
- **Mutual monitoring**: Any agent can peek at any other agent's health
|
||||
- **Collective intervention**: If you see something stuck, you can help
|
||||
- **No single point of failure**: The village survives individual failures
|
||||
- **Organic healing**: Problems get fixed by whoever notices them first
|
||||
|
||||
This is an ant colony, except the ants don't kill defective members - they help
|
||||
them recover. Workers who crash are respawned. Workers who get stuck are nudged.
|
||||
Workers who need help receive it.
|
||||
|
||||
### Practical Implications
|
||||
|
||||
1. **Every patrol includes neighbor-checking**
|
||||
- Polecats peek at other polecats
|
||||
- Witness peeks at Refinery
|
||||
- Refinery peeks at Witness
|
||||
- Everyone can peek at the Deacon
|
||||
|
||||
2. **`gt peek` is universal vocabulary**
|
||||
- Any agent can check any other agent's health
|
||||
- Health states are shared vocabulary: idle, working, stuck, done
|
||||
|
||||
3. **Exit state enums are teaching tools**
|
||||
- COMPLETED, BLOCKED, REFACTOR, ESCALATE
|
||||
- Every agent learns these
|
||||
- When peeking neighbors, agents recognize states and can help
|
||||
|
||||
4. **Mail is the nervous system**
|
||||
- Asynchronous, persistent, auditable
|
||||
- Survives crashes and restarts
|
||||
- The village communicates through mail
|
||||
|
||||
---
|
||||
|
||||
## Idea 3: Rig, Cook, Run
|
||||
|
||||
Work in Gas Town flows through three phases:
|
||||
|
||||
```
|
||||
RIG ────→ COOK ────→ RUN
|
||||
```
|
||||
|
||||
| Phase | What Happens | Key Operator |
|
||||
|-------|--------------|--------------|
|
||||
| **Rig** | Compose formulas (source level) | extends, compose |
|
||||
| **Cook** | Instantiate work (pour/wisp) | cook, pour, wisp |
|
||||
| **Run** | Execute steps | Agent execution |
|
||||
|
||||
**Rig** is source-level composition (formula YAML).
|
||||
**Bond** is artifact-level composition (protos, mols, wisps).
|
||||
|
||||
See [molecular-chemistry.md](molecular-chemistry.md) for the full specification.
|
||||
|
||||
### The Three Phases of Matter
|
||||
|
||||
Work artifacts exist in three phases:
|
||||
|
||||
| Phase | Name | State | Behavior |
|
||||
|-------|------|-------|----------|
|
||||
| **Solid** | Proto | Frozen template | Crystallized, immutable, reusable |
|
||||
| **Liquid** | Mol | Flowing instance | Dynamic, adapting, persistent |
|
||||
| **Vapor** | Wisp | Ephemeral trace | Transient, dissipates, operational |
|
||||
|
||||
### Phase Transition Operators
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ PROTO │
|
||||
│ (solid) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌─────────┼─────────┐
|
||||
│ │ │
|
||||
pour wisp distill
|
||||
│ │ ↑
|
||||
▼ ▼ │
|
||||
┌─────────┐ ┌─────────┐ │
|
||||
│ MOL │ │ WISP │ │
|
||||
│(liquid) │ │ (vapor) │ │
|
||||
└────┬────┘ └────┬────┘ │
|
||||
│ │ │
|
||||
squash squash │
|
||||
│ │ │
|
||||
▼ ▼ │
|
||||
┌─────────┐ ┌─────────┐ │
|
||||
│ DIGEST │ │evaporates│ │
|
||||
│(crystal)│ │ or burn │ │
|
||||
└─────────┘ └──────────┘ │
|
||||
│ │
|
||||
└───────────────────┘
|
||||
(experience crystallizes)
|
||||
```
|
||||
|
||||
| Operator | From | To | Effect |
|
||||
|----------|------|------|--------|
|
||||
| `pour` | Proto | Mol | Instantiate as persistent liquid |
|
||||
| `wisp` | Proto | Wisp | Instantiate as ephemeral vapor |
|
||||
| `bond` | Any + Any | Compound | Polymorphic combination |
|
||||
| `squash` | Mol/Wisp | Digest | Condense to permanent record |
|
||||
| `burn` | Wisp | Nothing | Discard without record |
|
||||
| `distill` | Mol | Proto | Extract reusable template |
|
||||
|
||||
### The Polymorphic Bond Operator
|
||||
|
||||
**Bond** is the artifact-level combiner (distinct from **rig**, which composes
|
||||
source formulas). Bond adapts to its operands:
|
||||
|
||||
| bond | Proto | Mol | Wisp |
|
||||
|------|-------|-----|------|
|
||||
| **Proto** | Compound Proto | Pour + attach | Wisp + attach |
|
||||
| **Mol** | Pour + attach | Link | Link |
|
||||
| **Wisp** | Wisp + attach | Link | Link |
|
||||
|
||||
This enables patterns like:
|
||||
- Patrol wisp discovers issue → bonds new work mol
|
||||
- Feature work needs diagnostic → bonds vapor wisp
|
||||
- Witness tracks polecats → bonds lease per polecat
|
||||
|
||||
---
|
||||
|
||||
## Idea 4: Beads as Data Plane
|
||||
|
||||
We use Beads (a separate project) as the persistence layer. It's Git + Issues
|
||||
in one human-readable format.
|
||||
|
||||
**Properties we liked:**
|
||||
- **Git-backed**: Uses existing infrastructure
|
||||
- **Human-readable**: You can read `.beads/` files directly
|
||||
- **Portable**: No vendor lock-in
|
||||
|
||||
See [github.com/steveyegge/beads](https://github.com/steveyegge/beads) for more.
|
||||
|
||||
### Git as Persistence
|
||||
|
||||
Everything persists through git:
|
||||
- Issues are JSONL in `.beads/`
|
||||
- Molecules are structured issues
|
||||
- Mail is issues with labels
|
||||
|
||||
This gives us offline-first operation and no infrastructure requirements.
|
||||
|
||||
### Control Plane = Data Plane
|
||||
|
||||
Gas Town uses Beads as both control plane and data plane:
|
||||
|
||||
| Data Type | Beads Representation |
|
||||
|-----------|---------------------|
|
||||
| Work items | Issues (tasks, bugs, features) |
|
||||
| Workflows | Molecules (type=molecule) |
|
||||
| Messages | Mail beads (type=message) |
|
||||
| Merge requests | Queue entries (type=merge-request) |
|
||||
| Agent state | Status on assigned issues |
|
||||
|
||||
The control state IS data in Beads. Agents read Beads to know what to do next.
|
||||
There is no separate orchestrator - Beads IS the orchestrator.
|
||||
|
||||
---
|
||||
|
||||
## Idea 5: Patrol Loops
|
||||
|
||||
Gas Town runs on continuous monitoring loops called **patrols**.
|
||||
|
||||
### Patrol Agents
|
||||
|
||||
| Agent | Role | Patrol Focus |
|
||||
|-------|------|--------------|
|
||||
| **Deacon** | Town-level daemon | Health of all agents, plugin execution |
|
||||
| **Witness** | Per-rig polecat monitor | Polecat lifecycle, nudging, cleanup |
|
||||
| **Refinery** | Per-rig merge processor | Merge queue, validation, integration |
|
||||
|
||||
### Patrol Wisps
|
||||
|
||||
Patrol agents run ephemeral wisps for their cycles:
|
||||
- Wisp starts at cycle begin
|
||||
- Steps complete as work progresses
|
||||
- Wisp squashes to digest at cycle end
|
||||
- New wisp created for next cycle
|
||||
|
||||
This prevents accumulation: patrol work is vapor that condenses to minimal
|
||||
digests, not liquid that pools forever.
|
||||
|
||||
### The Witness Polecat-Tracking Wisp
|
||||
|
||||
The Witness maintains a rolling wisp with a **lease** per active polecat:
|
||||
|
||||
```
|
||||
wisp-witness-patrol
|
||||
├── lease: furiosa (boot → working → done)
|
||||
├── lease: nux (working)
|
||||
└── lease: slit (done, closed)
|
||||
```
|
||||
|
||||
Each lease is a bonded vapor molecule tracking one polecat's lifecycle.
|
||||
When a polecat exits, its lease closes. When all leases close, the wisp
|
||||
squashes to a summary digest.
|
||||
|
||||
---
|
||||
|
||||
## Idea 6: Propulsion Over Protocol
|
||||
|
||||
We're experimenting with "pull-based" work where agents propel themselves
|
||||
rather than waiting for explicit commands:
|
||||
|
||||
1. **Check hook/pin** - What's attached to me?
|
||||
2. **Find next step** - What's ready in my molecule?
|
||||
3. **Execute** - Do the work
|
||||
4. **Advance** - Close step, find next
|
||||
5. **Exit properly** - One of four exit types
|
||||
|
||||
The idea is pull-based work: the molecule provides instructions, the agent executes.
|
||||
|
||||
### Hooks and Pins
|
||||
|
||||
Agents have **hooks** where work hangs. Work gets **pinned** to hooks.
|
||||
|
||||
```
|
||||
Agent (with hook)
|
||||
└── pinned mol (or wisp)
|
||||
├── step 1 (done)
|
||||
├── step 2 (in_progress)
|
||||
└── step 3 (pending)
|
||||
```
|
||||
|
||||
This enables:
|
||||
- **Crash recovery**: Agent restarts, reads pinned mol, continues
|
||||
- **Context survival**: Mol state persists across sessions
|
||||
- **Handoff**: New session reads predecessor's pinned work
|
||||
- **Observability**: `bd hook` shows what an agent is working on
|
||||
|
||||
### The Four Exits
|
||||
|
||||
Every polecat converges on one of four exits:
|
||||
|
||||
| Exit | Meaning | Action |
|
||||
|------|---------|--------|
|
||||
| **COMPLETED** | Work finished | Submit to merge queue |
|
||||
| **BLOCKED** | External dependency | File blocker, defer, notify |
|
||||
| **REFACTOR** | Work too large | Break down, defer rest |
|
||||
| **ESCALATE** | Need human judgment | Document, mail human, defer |
|
||||
|
||||
All exits pass through the exit-decision step. All exits end in request-shutdown.
|
||||
The polecat never exits directly - it waits to be killed by the Witness.
|
||||
|
||||
---
|
||||
|
||||
## Idea 7: Nondeterministic Idempotence
|
||||
|
||||
An idea we're exploring for crash recovery:
|
||||
|
||||
- **Deterministic structure**: Molecule defines exactly what steps exist
|
||||
- **Nondeterministic execution**: Any worker can execute any ready step
|
||||
- **Idempotent progress**: Completed steps stay completed
|
||||
|
||||
```
|
||||
Worker A picks up "design" step
|
||||
Worker A completes "design"
|
||||
Worker A crashes mid-"implement"
|
||||
Worker B restarts, queries ready work
|
||||
Worker B sees "implement" is ready (design done, implement pending)
|
||||
Worker B continues from exactly where A left off
|
||||
```
|
||||
|
||||
No work is lost. No state is in memory. Any worker can continue any molecule.
|
||||
|
||||
---
|
||||
|
||||
## The Agent Hierarchy
|
||||
|
||||
### Overseer (Human)
|
||||
- Sets strategy and priorities
|
||||
- Reviews and approves output
|
||||
- Handles escalations
|
||||
- Operates the system
|
||||
|
||||
### Mayor (AI - Town-wide)
|
||||
- Dispatches work across rigs
|
||||
- Coordinates cross-project dependencies
|
||||
- Handles strategic decisions
|
||||
|
||||
### Deacon (AI - Town-level daemon)
|
||||
- Ensures patrol agents are running
|
||||
- Executes maintenance plugins
|
||||
- Handles lifecycle requests
|
||||
|
||||
### Witness (AI - Per-rig)
|
||||
- Manages polecat lifecycle
|
||||
- Detects stuck workers
|
||||
- Handles session cycling
|
||||
|
||||
### Refinery (AI - Per-rig)
|
||||
- Processes merge queue
|
||||
- Reviews and integrates code
|
||||
- Maintains branch hygiene
|
||||
|
||||
### Polecat (AI - Ephemeral workers)
|
||||
- Executes work molecules
|
||||
- Files discovered issues
|
||||
- Ephemeral - spawn, work, disappear
|
||||
|
||||
---
|
||||
|
||||
## The Steam Train in Action
|
||||
|
||||
Putting it all together:
|
||||
|
||||
```
|
||||
1. Human files issue in Beads
|
||||
2. Mayor dispatches: gt sling --issue <id>
|
||||
3. Polecat created with:
|
||||
- Fresh worktree
|
||||
- mol-polecat-work pinned to hook
|
||||
- Work assignment in mail
|
||||
4. Deacon/Witness notified: POLECAT_STARTED
|
||||
5. Witness bonds lease to patrol wisp
|
||||
6. Polecat:
|
||||
- Reads polecat.md, orients
|
||||
- Reads mail, gets assignment
|
||||
- Executes mol-polecat-work steps
|
||||
- Makes commits, runs tests
|
||||
- Submits to merge queue
|
||||
- Exits via request-shutdown
|
||||
7. Witness:
|
||||
- Receives SHUTDOWN mail
|
||||
- Closes polecat's lease
|
||||
- Kills session, cleans up
|
||||
8. Refinery:
|
||||
- Processes merge queue
|
||||
- Rebases, tests, merges
|
||||
- Pushes to main
|
||||
9. Digest created: work outcome crystallized
|
||||
10. Loop: new work, new polecats, new cycles
|
||||
```
|
||||
|
||||
The flywheel spins. The village watches itself. The train keeps running.
|
||||
|
||||
---
|
||||
|
||||
## What We're Building Toward
|
||||
|
||||
We're interested in exploring AI as collaborator rather than just assistant.
|
||||
Instead of AI suggesting code, what if AI could complete tasks while humans
|
||||
review the outcomes?
|
||||
|
||||
This is early-stage experimentation. Some of it works, much of it doesn't yet.
|
||||
We're sharing it in case the ideas are useful to others.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Gas Town explores:
|
||||
- Multi-agent coordination via "molecules" of work
|
||||
- Git-backed persistence via Beads
|
||||
- Distributed monitoring via the "village" model
|
||||
- Crash recovery via nondeterministic idempotence
|
||||
|
||||
We don't know if these ideas will pan out. We invite you to explore with us.
|
||||
@@ -1,139 +0,0 @@
|
||||
# Wisp Architecture: Simplified
|
||||
|
||||
> Status: Updated December 2024 - Simplified from separate directory to flag-based
|
||||
|
||||
## Overview
|
||||
|
||||
**Wisps** are ephemeral issues - transient workflow state that should not be synced
|
||||
to the shared repository. They're used for operational messages (like lifecycle mail)
|
||||
and patrol cycle traces that would otherwise accumulate unbounded.
|
||||
|
||||
## Core Principle
|
||||
|
||||
**Wisps are regular issues with `Wisp: true` flag.**
|
||||
|
||||
The old architecture used a separate `.beads-wisp/` directory. This was over-engineered.
|
||||
The simplified approach:
|
||||
|
||||
| Old | New |
|
||||
|-----|-----|
|
||||
| `.beads-wisp/issues.jsonl` | `.beads/issues.jsonl` with `Wisp: true` |
|
||||
| Separate directory, git init, gitignore | Single database, filtered on sync |
|
||||
| Complex dual-inbox routing | Simple flag check |
|
||||
|
||||
## How It Works
|
||||
|
||||
### Creating Wisps
|
||||
|
||||
```bash
|
||||
# Create an ephemeral issue
|
||||
bd create --title "Patrol cycle" --wisp
|
||||
|
||||
# Send ephemeral mail (automatically sets Wisp=true)
|
||||
gt mail send --wisp -s "Lifecycle: spawn" -m "..."
|
||||
```
|
||||
|
||||
### Sync Filtering
|
||||
|
||||
When `bd sync` exports to JSONL for git:
|
||||
- Issues with `Wisp: true` are **excluded**
|
||||
- Only permanent issues are synced to remote
|
||||
- No separate directory needed
|
||||
|
||||
### Querying
|
||||
|
||||
```bash
|
||||
# List all issues (including wisps)
|
||||
bd list
|
||||
|
||||
# List only wisps
|
||||
bd list --wisp
|
||||
|
||||
# List only permanent issues
|
||||
bd list --no-wisp
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Ephemeral Mail (Lifecycle Messages)
|
||||
|
||||
Spawn notifications, session handoffs, and other operational messages that:
|
||||
- Don't need to be synced to remote
|
||||
- Would accumulate unbounded
|
||||
- Have no long-term audit value
|
||||
|
||||
```bash
|
||||
gt mail send gastown/polecats/nux --wisp \
|
||||
-s "LIFECYCLE: spawn" \
|
||||
-m "Work on issue gt-abc"
|
||||
```
|
||||
|
||||
### Patrol Cycle Traces
|
||||
|
||||
Deacon, Witness, and Refinery run continuous loops. Each cycle would create
|
||||
accumulating history. Wisps let them track cycle state without permanent records.
|
||||
|
||||
### Hook Files
|
||||
|
||||
Agent hook files (`hook-<agent>.json`) are stored in `.beads/` but are local-only
|
||||
runtime state, not synced. These track what work is assigned to each agent for
|
||||
restart-and-resume.
|
||||
|
||||
## Wisp Garbage Collection
|
||||
|
||||
Wisps can accumulate over time. The `wisp-gc` doctor check cleans up stale wisps:
|
||||
|
||||
```bash
|
||||
# Check for stale wisps
|
||||
gt doctor -v
|
||||
|
||||
# Auto-cleanup stale wisps (>1h old by default)
|
||||
gt doctor --fix
|
||||
|
||||
# Manual cleanup via bd
|
||||
bd wisp gc # Preview what would be cleaned
|
||||
bd wisp gc --force # Actually delete stale wisps
|
||||
bd wisp gc --age 2h # Custom age threshold
|
||||
```
|
||||
|
||||
The Deacon includes wisp-gc in its patrol cycle via `gt doctor --fix`.
|
||||
|
||||
### What Gets Cleaned
|
||||
|
||||
- Wisps older than the threshold (default 1 hour)
|
||||
- Wisps not associated with active sessions
|
||||
- Orphaned lifecycle mail
|
||||
|
||||
### What's Preserved
|
||||
|
||||
- Wisps with active sessions still running
|
||||
- Wisps younger than the threshold
|
||||
- Non-wisp issues (permanent records)
|
||||
|
||||
## Decision Matrix
|
||||
|
||||
| Question | Answer | Use |
|
||||
|----------|--------|-----|
|
||||
| Should this sync to remote? | No | Wisp |
|
||||
| Is this operational/lifecycle? | Yes | Wisp |
|
||||
| Would this accumulate unbounded? | Yes | Wisp |
|
||||
| Does this need audit trail? | Yes | Regular issue |
|
||||
| Might others need to see this? | Yes | Regular issue |
|
||||
|
||||
## Migration from .beads-wisp/
|
||||
|
||||
The old `.beads-wisp/` directories can be deleted:
|
||||
|
||||
```bash
|
||||
# Remove legacy wisp directories
|
||||
rm -rf ~/gt/.beads-wisp/
|
||||
rm -rf ~/gt/gastown/.beads-wisp/
|
||||
find ~/gt -type d -name '.beads-wisp' -exec rm -rf {} +
|
||||
```
|
||||
|
||||
No migration needed - these contained transient data with no long-term value.
|
||||
|
||||
## Related
|
||||
|
||||
- [molecules.md](molecules.md) - Molecule system (wisps can be molecule instances)
|
||||
- [architecture.md](architecture.md) - Overall Gas Town architecture
|
||||
@@ -1,94 +0,0 @@
|
||||
# Witness Patrol Design
|
||||
|
||||
> The Witness is the Pit Boss. Oversight, not implementation.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
| Duty | Action |
|
||||
|------|--------|
|
||||
| Handle POLECAT_DONE | Create cleanup wisp, process later |
|
||||
| Handle HELP requests | Assess, help or escalate to Mayor |
|
||||
| Ensure refinery alive | Restart if needed |
|
||||
| Survey workers | Detect stuck polecats, nudge or escalate |
|
||||
| Process cleanups | Verify git clean, kill session, burn wisp |
|
||||
|
||||
## Patrol Shape (Linear)
|
||||
|
||||
```
|
||||
inbox-check → process-cleanups → check-refinery → survey-workers → context-check → loop
|
||||
```
|
||||
|
||||
No dynamic arms. No fanout gates. Simple loop like Deacon.
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
| Principle | Meaning |
|
||||
|-----------|---------|
|
||||
| **Discovery over tracking** | Observe reality each cycle, don't maintain state |
|
||||
| **Events over state** | POLECAT_DONE triggers wisps, not queue updates |
|
||||
| **Cleanup wisps as finalizers** | Pending cleanup = wisp exists |
|
||||
| **Task tool for parallelism** | Subagents inspect polecats, not molecule arms |
|
||||
| **Fresh judgment each cycle** | No persistent nudge counters |
|
||||
|
||||
## Cleanup: The Finalizer Pattern
|
||||
|
||||
```
|
||||
POLECAT_DONE arrives
|
||||
↓
|
||||
Create wisp: bd create --wisp --title "cleanup:<polecat>" --labels cleanup
|
||||
↓
|
||||
(wisp exists = cleanup pending)
|
||||
↓
|
||||
Witness process-cleanups step:
|
||||
- Verify: git status clean, no unpushed, issue closed
|
||||
- Execute: gt session kill, worktree removed
|
||||
- Burn wisp
|
||||
↓
|
||||
Failed? Leave wisp, retry next cycle
|
||||
```
|
||||
|
||||
## Assessing Stuck Polecats
|
||||
|
||||
With step-based restarts, polecats are either:
|
||||
- **Working a step**: Active tool calls, progress
|
||||
- **Starting a step**: Just respawned, reading hook
|
||||
- **Stuck on a step**: No progress, same step for multiple cycles
|
||||
|
||||
| Observation | Action |
|
||||
|-------------|--------|
|
||||
| Active tool calls | None |
|
||||
| Just started step (<5 min) | None |
|
||||
| Idle 5-15 min, same step | Gentle nudge |
|
||||
| Idle 15+ min, same step | Direct nudge |
|
||||
| Idle 30+ min despite nudges | Escalate to Mayor |
|
||||
| Errors visible | Assess, help or escalate |
|
||||
| Says "done" but no POLECAT_DONE | Nudge to signal completion |
|
||||
|
||||
**No persistent nudge counts**. Each cycle: observe reality, make fresh judgment.
|
||||
|
||||
"How long stuck on same step" is discoverable from beads timestamps.
|
||||
|
||||
## Parallelism via Task Tool
|
||||
|
||||
Inspect multiple polecats concurrently using subagents:
|
||||
|
||||
```markdown
|
||||
## survey-workers step
|
||||
|
||||
For each polecat, launch Task tool subagent:
|
||||
- Capture tmux output
|
||||
- Assess state (working/idle/error/done)
|
||||
- Check beads for step progress
|
||||
- Decide and execute action
|
||||
|
||||
Task tool handles parallelism. One subagent per polecat.
|
||||
```
|
||||
|
||||
## Formula
|
||||
|
||||
See `.beads/formulas/mol-witness-patrol.formula.toml`
|
||||
|
||||
## Related
|
||||
|
||||
- [polecat-lifecycle.md](polecat-lifecycle.md) - Step-based execution model
|
||||
- [molecular-chemistry.md](molecular-chemistry.md) - MEOW stack
|
||||
Reference in New Issue
Block a user