Files
gastown/docs/wisp-architecture.md
Steve Yegge 003a4f2236 Fix wisp storage path: rig root not mayor/rig (gt-nq6j)
Wisps are at <rig>/.beads-wisp/, outside any git clone.
This matches the actual code implementation.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 03:11:41 -08:00

7.8 KiB

Wisp Architecture: Transient Molecule Storage

Status: Design Spec v1 - December 2024

Overview

Wisps are transient molecule execution traces - the "steam" in Gas Town's engine metaphor. 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 the work is done.

Core Principle

Wisps are local operational state, not project history.

Artifact Storage Git Tracked Purpose
Issues .beads/issues.jsonl Yes Permanent project history
Wisps .beads-wisp/issues.jsonl No Transient execution traces
Digests .beads/issues.jsonl Yes Compressed summaries of squashed wisps

Storage Architecture

Directory Structure

~/gt/gastown/                     # Rig root (not a git repo)
├── .beads-wisp/                  # Shared wisp storage (rig-level, gitignored)
│   └── issues.jsonl              # In-progress wisps (Deacon, Witness, Refinery)
│
├── mayor/rig/                    # Mayor's canonical clone
│   └── .beads/                   # CANONICAL rig beads (versioned)
│       ├── issues.jsonl          # Permanent issues + digests
│       └── config.yaml
│
├── refinery/rig/                 # Refinery's clone
│   └── .beads/                   # Inherits from mayor/rig
│
├── witness/                      # Witness (no clone needed)
│
└── polecats/<name>/              # Polecat worktrees
    └── .beads/                   # Inherits from mayor/rig

Key Points

  1. .beads-wisp/ is at rig root - Outside any git clone, naturally isolated
  2. All rig agents share <rig>/.beads-wisp/ - Deacon, Witness, Refinery
  3. Digests go to canonical .beads/ - Permanent record after squash
  4. Wisps are deleted after squash/burn - No accumulation
  5. Polecats don't use wisps - Each assignment is a deliverable with audit value

Gitignore Entry

Add to .beads/.gitignore:

.beads-wisp/

Or add to rig-level .gitignore:

**/.beads-wisp/

Wisp Lifecycle

bd mol bond <proto> --wisp
         │
         ▼
┌─────────────────────────┐
│  .beads-wisp/           │
│  └── issues.jsonl       │  ← Wisp created here
│      └── {id, wisp: true, ...}
└────────────┬────────────┘
             │
    ┌────────┴────────┐
    ▼                 ▼
bd mol burn      bd mol squash
    │                 │
    ▼                 ▼
(deleted)        ┌─────────────────────────┐
                 │  .beads/issues.jsonl    │
                 │  └── {id, type: digest} │  ← Digest here
                 └─────────────────────────┘

Role Assignments

Roles That Use Wisps

These roles have repetitive/cyclic work that would accumulate without wisps:

Role Molecule Storage Location Squash Frequency
Deacon mol-deacon-patrol <rig>/.beads-wisp/ Per cycle
Witness mol-witness-patrol <rig>/.beads-wisp/ Per cycle
Refinery mol-refinery-cycle <rig>/.beads-wisp/ Per cycle

Roles That Use Regular Molecules

These roles do discrete work with audit value:

Role Molecule Storage Reason
Polecat mol-polecat-work .beads/issues.jsonl Each assignment is a deliverable
Mayor (ad-hoc) .beads/issues.jsonl Coordination has history value
Crew (ad-hoc) .beads/issues.jsonl User work needs audit trail

Decision Matrix

Question Answer Use
Is this work repetitive/cyclic? Yes Wisp
Does the outcome matter more than the trace? Yes Wisp
Would this accumulate unbounded over time? Yes Wisp
Is this a discrete deliverable? Yes Regular Mol
Might I need to reference this later? Yes Regular Mol
Does this represent user-requested work? Yes Regular Mol

Patrol Pattern

Every role using wisps must implement this pattern:

func patrolCycle() {
    // 1. Bond wisp molecule
    mol := bdMolBond("mol-<role>-patrol", "--wisp")

    // 2. Execute cycle steps
    for _, step := range mol.Steps {
        executeStep(step)
        bdMolStep(step.ID, "--complete")
    }

    // 3. Generate summary (agent cognition)
    summary := generateCycleSummary()

    // 4. Squash - REQUIRED (this is the cleanup)
    bdMolSquash(mol.ID, "--summary", summary)
    // Wisp deleted from .beads-wisp/
    // Digest created in .beads/issues.jsonl

    // 5. Sleep until next cycle
    time.Sleep(patrolInterval)
}

Critical: Without step 4 (squash), wisps become technical debt.

Beads Implementation Requirements

For this architecture to work, Beads needs:

New Commands

# Bond with wisp flag (--ephemeral is an alias)
bd mol bond <proto> --wisp
# Creates in .beads-wisp/ instead of .beads/

# List wisps
bd wisp list
# Shows in-progress wisps

# Garbage collect orphaned wisps
bd wisp gc
# Cleans up wisps from crashed processes

Storage Behavior

Command With --wisp Without
bd mol bond Creates in .beads-wisp/ Creates in .beads/
bd mol step Updates in wisp store Updates in permanent
bd mol squash Deletes from wisp, creates digest in permanent Creates digest in permanent
bd mol burn Deletes from wisp Marks abandoned in permanent

Config

# .beads/config.yaml
wisp:
  enabled: true
  directory: ../.beads-wisp  # Relative to .beads/
  auto_gc: true              # Clean orphans on bd init

Crash Recovery

If a patrol crashes mid-cycle:

  1. Wisp persists in .beads-wisp/ - Provides recovery breadcrumb
  2. On restart, agent can:
    • Resume from last step (if step tracking is granular)
    • Or burn and start fresh (simpler for patrol loops)
  3. bd wisp gc cleans orphans - Wisps older than threshold with no active process

Orphan Detection

A wisp is orphaned if:

  • process_id field exists and process is dead
  • OR updated_at is older than threshold (e.g., 1 hour)
  • AND molecule is not complete

Digest Format

When a wisp is squashed, the digest captures the outcome:

{
  "id": "gt-xyz.digest-001",
  "type": "digest",
  "title": "Deacon patrol cycle @ 2024-12-21T10:30:00Z",
  "description": "Checked 3 witnesses, 2 refineries. All healthy. Processed 5 mail items.",
  "parent": "gt-xyz",
  "squashed_from": "gt-xyz.wisp-001",
  "created_at": "2024-12-21T10:32:00Z"
}

Digests are queryable:

bd list --type=digest --parent=gt-deacon-patrol
# Shows all patrol cycle summaries

Migration Path

For existing Gas Town installations:

  1. Add .beads-wisp/ to gitignore (immediate)
  2. Update patrol runners to use --wisp (as patched)
  3. No migration of existing data - Fresh start for wisp storage
  4. Optional: Remove old .beads-ephemeral/ directories

Open Questions

  1. Digest retention: Should old digests be pruned? How old?
  2. Wisp schema: Do wisps need additional fields (process_id, host, etc.)?
  3. Cross-process visibility: Should bd wisp list show all wisps or just current process?

Implementation Tracking

  • Beads: bd-kwjh (Wisp storage: transient molecule tracking)
  • Gas Town: gt-3x0z.9 (mol-deacon-patrol uses wisps)