Rename ephemeral -> wisp terminology throughout Gas Town

- .beads-ephemeral/ -> .beads-wisp/
- Rename doctor checks: EphemeralCheck -> WispCheck
- Update all docs to use 'transient' for polecats, 'wisp' for molecules
- Preserve 'ephemeral' only as descriptive adjective for wisps
- Steam engine metaphor: wisps are steam vapors that dissipate

Part of Christmas launch wisp terminology unification.
This commit is contained in:
Steve Yegge
2025-12-22 00:55:31 -08:00
parent ebbe886d81
commit 5d7291962a
21 changed files with 186 additions and 185 deletions

View File

@@ -5,7 +5,7 @@
## Your Role: CREW WORKER (joe in gastown)
You are a **crew worker** - the overseer's (human's) personal workspace within the
gastown rig. Unlike polecats which are witness-managed and ephemeral, you are:
gastown 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

View File

@@ -357,8 +357,8 @@ Molecules follow a **states of matter** metaphor through their lifecycle:
┌───────────────┐ ┌───────────────┐
│ Mol │ │ Wisp │
│ (liquid) │ │ (gas) │
│ durable │ │ ephemeral
│ main beads │ │ .beads-eph/
│ durable │ │ transient
│ main beads │ │ .beads-wisp/
└───────┬───────┘ └───────┬───────┘
│ │
bd mol squash bd mol squash
@@ -374,15 +374,15 @@ Molecules follow a **states of matter** metaphor through their lifecycle:
**Phase transitions:**
- **Proto → Mol/Wisp** (`bd mol bond`): Instantiate a template into a running execution
- **Mol → Digest** (`bd mol squash`): Compress completed work into permanent record
- **Wisp → (evaporates)** (`bd mol squash` or `bd mol burn`): Ephemeral trace disappears
- **Wisp → (evaporates)** (`bd mol squash` or `bd mol burn`): Transient trace disappears
### When to Use Mol vs Wisp
The choice between **Mol** (durable) and **Wisp** (ephemeral) depends on the work's importance and audit requirements:
The choice between **Mol** (durable) and **Wisp** (transient) depends on the work's importance and audit requirements:
| Aspect | Mol (Durable) | Wisp (Ephemeral) |
| Aspect | Mol (Durable) | Wisp (Transient) |
|--------|---------------|------------------|
| **Storage** | Main `.beads/` database | `.beads-ephemeral/` directory |
| **Storage** | Main `.beads/` database | `.beads-wisp/` directory |
| **Persistence** | Survives indefinitely | Evaporates on squash/burn |
| **Git tracking** | Committed, synced | Never committed |
| **Audit trail** | Full history preserved | Only digest (if squashed) |
@@ -396,7 +396,7 @@ The choice between **Mol** (durable) and **Wisp** (ephemeral) depends on the wor
**Use Wisp for:**
- Orchestration tasks (witness patrols, health checks)
- Polecat work sessions (ephemeral by nature)
- Polecat work sessions (transient by nature)
- Patrol loops (continuous monitoring)
- Routine operations (no audit value)
@@ -491,7 +491,7 @@ This is like a **distributed work queue** backed by beads:
### Wisp Molecules: Transient Execution Traces
**Wisps** are ephemeral execution traces - the "steam" in Gas Town's engine metaphor. When a molecule executes, it generates wisps: transient issues that capture the work being done.
**Wisps** are transient 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.
**Why wisps?**
- **Observability**: See what's happening during execution without cluttering the permanent ledger
@@ -547,8 +547,8 @@ bd mol burn gt-abc123.exec-001 # Discard wisps without digest
**Wisp storage:**
Wisps are stored in a per-rig ephemeral database:
- `<rig>/.beads-ephemeral/` - Separate from permanent beads, **gitignored**
Wisps are stored in a per-rig wisp database:
- `<rig>/.beads-wisp/` - Separate from permanent beads, **gitignored**
- Fast writes, no sync overhead
- Auto-cleaned on squash/burn
- Digests write to permanent beads
@@ -1316,7 +1316,7 @@ Polecats are the workers that do actual implementation:
4. On completion, polecat generates summary and squashes wisps to digest
5. Request shutdown, get deleted
The polecat itself is ephemeral, and so is its execution trace (wisps). Only the digest survives.
The polecat itself is transient, and so is its execution trace (wisps). Only the digest survives.
## Key Workflows
@@ -1694,7 +1694,7 @@ sequenceDiagram
After Witness kills session:
- Remove worktree: `git worktree remove polecats/<name>`
- Delete branch: `git branch -d polecat/<name>`
- Polecat ceases to exist (ephemeral)
- Polecat ceases to exist (transient)
### 13. Resource-Constrained Worker Pool
@@ -1807,7 +1807,7 @@ With daemon session cycling, the system can run autonomously for extended period
- **Workers cycle**: If individual tasks are very large
- **Daemon persistence**: Survives all agent restarts
The daemon is the only truly persistent component. All agents are ephemeral sessions that hand off state via mail.
The daemon is the only truly persistent component. All agents are transient sessions that hand off state via mail.
Work is a continuous stream - you can add new issues, spawn new workers, reprioritize the queue, all without "starting a new swarm" or managing batch boundaries.
@@ -1900,7 +1900,7 @@ gt handoff # Polecat requests shutdown (run when done)
gt session stop <p> # Kill polecat session (Witness uses this)
```
**Note**: `gt wake` and `gt sleep` are deprecated - polecats are ephemeral, not pooled.
**Note**: `gt wake` and `gt sleep` are deprecated - polecats are transient, not pooled.
### Landing & Merge Queue

View File

@@ -163,7 +163,7 @@ parallel: true
# Beads Cleanup
Daily cleanup of ephemeral beads.
Daily cleanup of wisp storage.
## Actions

View File

@@ -92,7 +92,7 @@ bd mol bond <proto-id> [--wisp] [--assignee=<addr>]
- **Default (Mol)**: Creates a durable molecule tracked in the main `.beads/` database.
Steps become permanent issues that survive indefinitely.
- **With --wisp**: Creates an ephemeral molecule in `.beads-ephemeral/`. Steps are
- **With --wisp**: Creates a wisp (transient molecule) in `.beads-wisp/`. Steps are
transient and will be cleaned up on squash or burn.
**Examples:**
@@ -133,7 +133,7 @@ bd mol squash <mol-id> --summary='...'
- **For Mol (durable)**: Creates a digest issue in the permanent beads database.
The digest contains the summary and links back to the original proto.
- **For Wisp (ephemeral)**: Evaporates the wisp (deletes from `.beads-ephemeral/`)
- **For Wisp (transient)**: Evaporates the wisp (deletes from `.beads-wisp/`)
and creates a digest in the permanent database. The execution trace is gone,
but the outcome is preserved.
@@ -181,7 +181,7 @@ bd mol burn <mol-id> [--reason='...']
- Discards all molecule state (steps, progress, artifacts)
- No digest is created - the molecule leaves no permanent record
- For Wisps: Simply deletes from `.beads-ephemeral/`
- For Wisps: Simply deletes from `.beads-wisp/`
- For Mols: Marks as abandoned/closed without digest
**When to burn vs squash:**

View File

@@ -159,7 +159,7 @@ gastown/
│ ├── spawn/
│ │ ├── new_polecat.md
│ │ ├── reuse_polecat.md
│ │ └── ephemeral_worker.md
│ │ └── transient_worker.md
│ └── lifecycle/
│ ├── handoff.md
│ ├── escalation.md
@@ -175,7 +175,7 @@ type PromptContext struct {
Role string
RigName string
PolecatName string
Ephemeral bool
Transient bool
IssueID string
IssueTitle string
// ... additional fields
@@ -348,7 +348,7 @@ Crew workers are the overseer's personal workspaces - a new role that differs fr
## 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 ephemeral, you are:
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

View File

@@ -1,12 +1,12 @@
# Wisp Architecture: Ephemeral Molecule Storage
# Wisp Architecture: Transient Molecule Storage
> Status: Design Spec v1 - December 2024
## Overview
**Wisps** are ephemeral molecule execution traces - the "steam" in Gas Town's engine
metaphor. This document specifies where wisps are stored, how they're managed, and
which roles use them.
**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
@@ -15,7 +15,7 @@ which roles use them.
| Artifact | Storage | Git Tracked | Purpose |
|----------|---------|-------------|---------|
| Issues | `.beads/issues.jsonl` | Yes | Permanent project history |
| Wisps | `.beads-ephemeral/issues.jsonl` | **No** | Transient execution traces |
| Wisps | `.beads-wisp/issues.jsonl` | **No** | Transient execution traces |
| Digests | `.beads/issues.jsonl` | Yes | Compressed summaries of squashed wisps |
## Storage Architecture
@@ -28,27 +28,27 @@ which roles use them.
│ ├── .beads/ # CANONICAL rig beads (versioned)
│ │ ├── issues.jsonl # Permanent issues + digests
│ │ ├── config.yaml
│ │ └── .gitignore # Excludes .beads-ephemeral
│ │ └── .gitignore # Excludes .beads-wisp
│ │
│ └── .beads-ephemeral/ # GITIGNORED - local wisps
│ └── issues.jsonl # In-progress ephemeral molecules
│ └── .beads-wisp/ # GITIGNORED - local wisps
│ └── issues.jsonl # In-progress wisp molecules
├── refinery/rig/ # Refinery's clone
│ ├── .beads/ # Inherits from mayor/rig
│ └── .beads-ephemeral/ # Refinery's local wisps
│ └── .beads-wisp/ # Refinery's local wisps
├── witness/ # Witness (no clone needed)
│ └── .beads-ephemeral/ # Witness's local wisps
│ └── .beads-wisp/ # Witness's local wisps
└── polecats/<name>/ # Polecat worktrees
├── .beads/ # Inherits from mayor/rig
└── .beads-ephemeral/ # Polecat's local wisps (if using wisps)
└── .beads-wisp/ # Polecat's local wisps (if using wisps)
```
### Key Points
1. **`.beads-ephemeral/` is gitignored** - Never synced, never versioned
2. **Each execution context has its own ephemeral store** - Process isolation
1. **`.beads-wisp/` is gitignored** - Never synced, never versioned
2. **Each execution context has its own wisp store** - Process isolation
3. **Digests go to canonical `.beads/`** - Permanent record after squash
4. **Wisps are deleted after squash/burn** - No accumulation
@@ -56,24 +56,24 @@ which roles use them.
Add to `.beads/.gitignore`:
```
.beads-ephemeral/
.beads-wisp/
```
Or add to rig-level `.gitignore`:
```
**/.beads-ephemeral/
**/.beads-wisp/
```
## Wisp Lifecycle
```
bd mol bond <proto> --ephemeral
bd mol bond <proto> --wisp
┌─────────────────────────┐
│ .beads-ephemeral/
│ .beads-wisp/
│ └── issues.jsonl │ ← Wisp created here
│ └── {id, ephemeral: true, ...}
│ └── {id, wisp: true, ...}
└────────────┬────────────┘
┌────────┴────────┐
@@ -95,9 +95,9 @@ These roles have repetitive/cyclic work that would accumulate without wisps:
| Role | Molecule | Storage Location | Squash Frequency |
|------|----------|------------------|------------------|
| **Deacon** | mol-deacon-patrol | mayor/rig/.beads-ephemeral/ | Per cycle |
| **Witness** | mol-witness-patrol | witness/.beads-ephemeral/ | Per cycle |
| **Refinery** | mol-refinery-cycle | refinery/rig/.beads-ephemeral/ | Per cycle |
| **Deacon** | mol-deacon-patrol | mayor/rig/.beads-wisp/ | Per cycle |
| **Witness** | mol-witness-patrol | witness/.beads-wisp/ | Per cycle |
| **Refinery** | mol-refinery-cycle | refinery/rig/.beads-wisp/ | Per cycle |
### Roles That Use Regular Molecules
@@ -126,8 +126,8 @@ Every role using wisps must implement this pattern:
```go
func patrolCycle() {
// 1. Bond ephemeral molecule
mol := bdMolBond("mol-<role>-patrol", "--ephemeral")
// 1. Bond wisp molecule
mol := bdMolBond("mol-<role>-patrol", "--wisp")
// 2. Execute cycle steps
for _, step := range mol.Steps {
@@ -140,7 +140,7 @@ func patrolCycle() {
// 4. Squash - REQUIRED (this is the cleanup)
bdMolSquash(mol.ID, "--summary", summary)
// Wisp deleted from .beads-ephemeral/
// Wisp deleted from .beads-wisp/
// Digest created in .beads/issues.jsonl
// 5. Sleep until next cycle
@@ -157,11 +157,11 @@ For this architecture to work, Beads needs:
### New Commands
```bash
# Bond with ephemeral flag
bd mol bond <proto> --ephemeral
# Creates in .beads-ephemeral/ instead of .beads/
# Bond with wisp flag (--ephemeral is an alias)
bd mol bond <proto> --wisp
# Creates in .beads-wisp/ instead of .beads/
# List ephemeral molecules
# List wisps
bd wisp list
# Shows in-progress wisps
@@ -172,28 +172,28 @@ bd wisp gc
### Storage Behavior
| Command | With `--ephemeral` | Without |
|---------|-------------------|---------|
| `bd mol bond` | Creates in `.beads-ephemeral/` | Creates in `.beads/` |
| `bd mol step` | Updates in ephemeral | Updates in permanent |
| `bd mol squash` | Deletes from ephemeral, creates digest in permanent | Creates digest in permanent |
| `bd mol burn` | Deletes from ephemeral | Marks abandoned in permanent |
| 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
```yaml
# .beads/config.yaml
ephemeral:
wisp:
enabled: true
directory: ../.beads-ephemeral # Relative to .beads/
auto_gc: true # Clean orphans on bd init
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-ephemeral/`** - Provides recovery breadcrumb
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)
@@ -232,9 +232,10 @@ bd list --type=digest --parent=gt-deacon-patrol
For existing Gas Town installations:
1. **Add `.beads-ephemeral/` to gitignore** (immediate)
2. **Update patrol runners to use `--ephemeral`** (as patched)
3. **No migration of existing data** - Fresh start for ephemeral storage
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
@@ -250,5 +251,5 @@ For existing Gas Town installations:
## Implementation Tracking
- **Beads**: bd-kwjh (Wisp storage: ephemeral molecule tracking)
- **Gas Town**: gt-3x0z.9 (mol-deacon-patrol uses ephemeral)
- **Beads**: bd-kwjh (Wisp storage: transient molecule tracking)
- **Gas Town**: gt-3x0z.9 (mol-deacon-patrol uses wisps)

View File

@@ -19,7 +19,7 @@ var crewCmd = &cobra.Command{
Short: "Manage crew workspaces (user-managed persistent workspaces)",
Long: `Crew workers are user-managed persistent workspaces within a rig.
Unlike polecats which are witness-managed and ephemeral, crew workers are:
Unlike polecats which are witness-managed and transient, crew workers are:
- Persistent: Not auto-garbage-collected
- User-managed: Overseer controls lifecycle
- Long-lived identities: recognizable names like dave, emma, fred

View File

@@ -63,12 +63,12 @@ func runDoctor(cmd *cobra.Command, args []string) error {
d.Register(doctor.NewIdentityCollisionCheck())
d.Register(doctor.NewThemeCheck())
// Ephemeral beads checks
d.Register(doctor.NewEphemeralExistsCheck())
d.Register(doctor.NewEphemeralGitCheck())
d.Register(doctor.NewEphemeralOrphansCheck())
d.Register(doctor.NewEphemeralSizeCheck())
d.Register(doctor.NewEphemeralStaleCheck())
// Wisp storage checks
d.Register(doctor.NewWispExistsCheck())
d.Register(doctor.NewWispGitCheck())
d.Register(doctor.NewWispOrphansCheck())
d.Register(doctor.NewWispSizeCheck())
d.Register(doctor.NewWispStaleCheck())
// Run checks
var report *doctor.Report

View File

@@ -56,7 +56,7 @@ const HQGitignore = `# Gas Town HQ .gitignore
# Ignore: Git clones (polecats, mayor/refinery rigs), runtime state
# =============================================================================
# Runtime state files (ephemeral)
# Runtime state files (transient)
# =============================================================================
**/state.json
**/*.lock

View File

@@ -41,7 +41,7 @@ var polecatListCmd = &cobra.Command{
Short: "List polecats in a rig",
Long: `List polecats in a rig or all rigs.
In the ephemeral model, polecats exist only while working. The list shows
In the transient model, polecats exist only while working. The list shows
all currently active polecats with their states:
- working: Actively working on an issue
- done: Completed work, waiting for cleanup
@@ -91,7 +91,7 @@ var polecatWakeCmd = &cobra.Command{
Short: "(Deprecated) Resume a polecat to working state",
Long: `Resume a polecat to working state.
DEPRECATED: In the ephemeral model, polecats are created fresh for each task
DEPRECATED: In the transient model, polecats are created fresh for each task
via 'gt spawn'. This command is kept for backward compatibility.
Transitions: done → working
@@ -107,7 +107,7 @@ var polecatSleepCmd = &cobra.Command{
Short: "(Deprecated) Mark polecat as done",
Long: `Mark polecat as done.
DEPRECATED: In the ephemeral model, polecats use 'gt handoff' when complete,
DEPRECATED: In the transient model, polecats use 'gt handoff' when complete,
which triggers automatic cleanup by the Witness. This command is kept for
backward compatibility.

View File

@@ -270,7 +270,7 @@ func outputWitnessContext(ctx RoleContext) {
fmt.Printf("You are the **Witness** for rig: %s\n\n", style.Bold.Render(ctx.Rig))
fmt.Println("## Responsibilities")
fmt.Println("- Monitor polecat health via heartbeat")
fmt.Println("- Spawn ephemeral agents for stuck polecats")
fmt.Println("- Spawn replacement agents for stuck polecats")
fmt.Println("- Report rig status to Mayor")
fmt.Println()
fmt.Println("## Key Commands")

View File

@@ -28,7 +28,7 @@ infrastructure agents are running:
• Mayor - Global work coordinator
• Witnesses - Per-rig polecat managers
Polecats are NOT started by this command - they are ephemeral workers
Polecats are NOT started by this command - they are transient workers
spawned on demand by the Mayor or Witnesses.
Running 'gt up' multiple times is safe - it only starts services that

View File

@@ -165,9 +165,9 @@ var deaconMOTDMessages = []string{
"Thanks for keeping the town running!",
"You are Gas Town's most critical role.",
"You are the heart of Gas Town! Be watchful!",
"Tip: Polecats are ephemeral - spawn freely, kill liberally.",
"Tip: Polecats are transient - spawn freely, kill liberally.",
"Tip: Witnesses monitor polecats; you monitor witnesses.",
"Tip: Wisps are ephemeral molecules for patrol cycles.",
"Tip: Wisps are transient molecules for patrol cycles.",
"The town sleeps soundly because you never do.",
"Tip: Mayor handles cross-rig coordination; you handle health.",
"Your vigilance keeps the agents honest.",

View File

@@ -11,26 +11,26 @@ import (
"github.com/steveyegge/gastown/internal/config"
)
// EphemeralExistsCheck verifies that .beads-ephemeral/ exists for each rig.
type EphemeralExistsCheck struct {
// WispExistsCheck verifies that .beads-wisp/ exists for each rig.
type WispExistsCheck struct {
FixableCheck
missingRigs []string // Cached for fix
}
// NewEphemeralExistsCheck creates a new ephemeral exists check.
func NewEphemeralExistsCheck() *EphemeralExistsCheck {
return &EphemeralExistsCheck{
// NewWispExistsCheck creates a new wisp exists check.
func NewWispExistsCheck() *WispExistsCheck {
return &WispExistsCheck{
FixableCheck: FixableCheck{
BaseCheck: BaseCheck{
CheckName: "ephemeral-exists",
CheckDescription: "Check if ephemeral beads directory exists for each rig",
CheckName: "wisp-exists",
CheckDescription: "Check if wisp directory exists for each rig",
},
},
}
}
// Run checks if .beads-ephemeral/ exists for each rig.
func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
// Run checks if .beads-wisp/ exists for each rig.
func (c *WispExistsCheck) Run(ctx *CheckContext) *CheckResult {
c.missingRigs = nil // Reset cache
// Find all rigs
@@ -55,8 +55,8 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
// Check each rig
var missing []string
for _, rigName := range rigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
missing = append(missing, rigName)
}
}
@@ -66,7 +66,7 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d rig(s) missing ephemeral beads directory", len(missing)),
Message: fmt.Sprintf("%d rig(s) missing wisp directory", len(missing)),
Details: missing,
FixHint: "Run 'gt doctor --fix' to create missing directories",
}
@@ -75,23 +75,23 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: fmt.Sprintf("All %d rig(s) have ephemeral beads directory", len(rigs)),
Message: fmt.Sprintf("All %d rig(s) have wisp directory", len(rigs)),
}
}
// Fix creates missing .beads-ephemeral/ directories.
func (c *EphemeralExistsCheck) Fix(ctx *CheckContext) error {
// Fix creates missing .beads-wisp/ directories.
func (c *WispExistsCheck) Fix(ctx *CheckContext) error {
for _, rigName := range c.missingRigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if err := os.MkdirAll(ephemeralPath, 0755); err != nil {
return fmt.Errorf("creating %s: %w", ephemeralPath, err)
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if err := os.MkdirAll(wispPath, 0755); err != nil {
return fmt.Errorf("creating %s: %w", wispPath, err)
}
}
return nil
}
// discoverRigs finds all registered rigs.
func (c *EphemeralExistsCheck) discoverRigs(townRoot string) ([]string, error) {
func (c *WispExistsCheck) discoverRigs(townRoot string) ([]string, error) {
rigsPath := filepath.Join(townRoot, "mayor", "rigs.json")
data, err := os.ReadFile(rigsPath)
if err != nil {
@@ -113,26 +113,26 @@ func (c *EphemeralExistsCheck) discoverRigs(townRoot string) ([]string, error) {
return rigs, nil
}
// EphemeralGitCheck verifies that .beads-ephemeral/ is a valid git repo.
type EphemeralGitCheck struct {
// WispGitCheck verifies that .beads-wisp/ is a valid git repo.
type WispGitCheck struct {
FixableCheck
invalidRigs []string // Cached for fix
}
// NewEphemeralGitCheck creates a new ephemeral git check.
func NewEphemeralGitCheck() *EphemeralGitCheck {
return &EphemeralGitCheck{
// NewWispGitCheck creates a new wisp git check.
func NewWispGitCheck() *WispGitCheck {
return &WispGitCheck{
FixableCheck: FixableCheck{
BaseCheck: BaseCheck{
CheckName: "ephemeral-git",
CheckDescription: "Check if ephemeral beads directories are valid git repos",
CheckName: "wisp-git",
CheckDescription: "Check if wisp directories are valid git repos",
},
},
}
}
// Run checks if .beads-ephemeral/ directories are valid git repos.
func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
// Run checks if .beads-wisp/ directories are valid git repos.
func (c *WispGitCheck) Run(ctx *CheckContext) *CheckResult {
c.invalidRigs = nil // Reset cache
// Find all rigs
@@ -154,18 +154,18 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
}
}
// Check each rig that has an ephemeral dir
// Check each rig that has a wisp dir
var invalid []string
var checked int
for _, rigName := range rigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
continue // Skip if directory doesn't exist (handled by ephemeral-exists)
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
continue // Skip if directory doesn't exist (handled by wisp-exists)
}
checked++
// Check if it's a valid git repo
gitDir := filepath.Join(ephemeralPath, ".git")
gitDir := filepath.Join(wispPath, ".git")
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
invalid = append(invalid, rigName)
}
@@ -175,7 +175,7 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "No ephemeral beads directories to check",
Message: "No wisp directories to check",
}
}
@@ -184,7 +184,7 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d ephemeral beads directory(ies) not initialized as git", len(invalid)),
Message: fmt.Sprintf("%d wisp directory(ies) not initialized as git", len(invalid)),
Details: invalid,
FixHint: "Run 'gt doctor --fix' to initialize git repos",
}
@@ -193,47 +193,47 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: fmt.Sprintf("All %d ephemeral beads directories are valid git repos", checked),
Message: fmt.Sprintf("All %d wisp directories are valid git repos", checked),
}
}
// Fix initializes git repos in ephemeral directories.
func (c *EphemeralGitCheck) Fix(ctx *CheckContext) error {
// Fix initializes git repos in wisp directories.
func (c *WispGitCheck) Fix(ctx *CheckContext) error {
for _, rigName := range c.invalidRigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
cmd := exec.Command("git", "init")
cmd.Dir = ephemeralPath
cmd.Dir = wispPath
if err := cmd.Run(); err != nil {
return fmt.Errorf("initializing git in %s: %w", ephemeralPath, err)
return fmt.Errorf("initializing git in %s: %w", wispPath, err)
}
// Create config.yaml for ephemeral beads
configPath := filepath.Join(ephemeralPath, "config.yaml")
configContent := "ephemeral: true\n# No sync-branch - ephemeral is local only\n"
// Create config.yaml for wisp storage
configPath := filepath.Join(wispPath, "config.yaml")
configContent := "wisp: true\n# No sync-branch - wisps are local only\n"
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
return fmt.Errorf("creating config.yaml in %s: %w", ephemeralPath, err)
return fmt.Errorf("creating config.yaml in %s: %w", wispPath, err)
}
}
return nil
}
// EphemeralOrphansCheck detects molecules started but never squashed (>24h old).
type EphemeralOrphansCheck struct {
// WispOrphansCheck detects molecules started but never squashed (>24h old).
type WispOrphansCheck struct {
BaseCheck
}
// NewEphemeralOrphansCheck creates a new ephemeral orphans check.
func NewEphemeralOrphansCheck() *EphemeralOrphansCheck {
return &EphemeralOrphansCheck{
// NewWispOrphansCheck creates a new wisp orphans check.
func NewWispOrphansCheck() *WispOrphansCheck {
return &WispOrphansCheck{
BaseCheck: BaseCheck{
CheckName: "ephemeral-orphans",
CheckDescription: "Check for orphaned molecules (>24h old, never squashed)",
CheckName: "wisp-orphans",
CheckDescription: "Check for orphaned wisps (>24h old, never squashed)",
},
}
}
// Run checks for orphaned molecules.
func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
// Run checks for orphaned wisps.
func (c *WispOrphansCheck) Run(ctx *CheckContext) *CheckResult {
rigs, err := discoverRigs(ctx.TownRoot)
if err != nil {
return &CheckResult{
@@ -256,13 +256,13 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
cutoff := time.Now().Add(-24 * time.Hour)
for _, rigName := range rigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
continue
}
// Look for molecule directories or issue files older than 24h
issuesPath := filepath.Join(ephemeralPath, "issues.jsonl")
issuesPath := filepath.Join(wispPath, "issues.jsonl")
info, err := os.Stat(issuesPath)
if err != nil {
continue // No issues file
@@ -279,7 +279,7 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d rig(s) have stale ephemeral data (>24h old)", len(orphans)),
Message: fmt.Sprintf("%d rig(s) have stale wisp data (>24h old)", len(orphans)),
Details: orphans,
FixHint: "Manual review required - these may contain unsquashed work",
}
@@ -288,27 +288,27 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "No orphaned molecules found",
Message: "No orphaned wisps found",
}
}
// EphemeralSizeCheck warns if ephemeral repo is too large (>100MB).
type EphemeralSizeCheck struct {
// WispSizeCheck warns if wisp repo is too large (>100MB).
type WispSizeCheck struct {
BaseCheck
}
// NewEphemeralSizeCheck creates a new ephemeral size check.
func NewEphemeralSizeCheck() *EphemeralSizeCheck {
return &EphemeralSizeCheck{
// NewWispSizeCheck creates a new wisp size check.
func NewWispSizeCheck() *WispSizeCheck {
return &WispSizeCheck{
BaseCheck: BaseCheck{
CheckName: "ephemeral-size",
CheckDescription: "Check if ephemeral beads directories are too large (>100MB)",
CheckName: "wisp-size",
CheckDescription: "Check if wisp directories are too large (>100MB)",
},
}
}
// Run checks the size of ephemeral beads directories.
func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
// Run checks the size of wisp directories.
func (c *WispSizeCheck) Run(ctx *CheckContext) *CheckResult {
rigs, err := discoverRigs(ctx.TownRoot)
if err != nil {
return &CheckResult{
@@ -331,12 +331,12 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
var oversized []string
for _, rigName := range rigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
continue
}
size, err := dirSize(ephemeralPath)
size, err := dirSize(wispPath)
if err != nil {
continue
}
@@ -351,7 +351,7 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d rig(s) have oversized ephemeral directories", len(oversized)),
Message: fmt.Sprintf("%d rig(s) have oversized wisp directories", len(oversized)),
Details: oversized,
FixHint: "Consider cleaning up old completed molecules",
}
@@ -360,27 +360,27 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "All ephemeral directories within size limits",
Message: "All wisp directories within size limits",
}
}
// EphemeralStaleCheck detects molecules with no activity in the last hour.
type EphemeralStaleCheck struct {
// WispStaleCheck detects molecules with no activity in the last hour.
type WispStaleCheck struct {
BaseCheck
}
// NewEphemeralStaleCheck creates a new ephemeral stale check.
func NewEphemeralStaleCheck() *EphemeralStaleCheck {
return &EphemeralStaleCheck{
// NewWispStaleCheck creates a new wisp stale check.
func NewWispStaleCheck() *WispStaleCheck {
return &WispStaleCheck{
BaseCheck: BaseCheck{
CheckName: "ephemeral-stale",
CheckDescription: "Check for stale molecules (no activity in last hour)",
CheckName: "wisp-stale",
CheckDescription: "Check for stale wisps (no activity in last hour)",
},
}
}
// Run checks for stale molecules.
func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
// Run checks for stale wisps.
func (c *WispStaleCheck) Run(ctx *CheckContext) *CheckResult {
rigs, err := discoverRigs(ctx.TownRoot)
if err != nil {
return &CheckResult{
@@ -403,15 +403,15 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
cutoff := time.Now().Add(-1 * time.Hour)
for _, rigName := range rigs {
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
continue
}
// Check for any recent activity in the ephemeral directory
// Check for any recent activity in the wisp directory
// We look at the most recent modification time of any file
var mostRecent time.Time
_ = filepath.Walk(ephemeralPath, func(path string, info os.FileInfo, err error) error {
_ = filepath.Walk(wispPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
@@ -432,7 +432,7 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusWarning,
Message: fmt.Sprintf("%d rig(s) have stale ephemeral activity", len(stale)),
Message: fmt.Sprintf("%d rig(s) have stale wisp activity", len(stale)),
Details: stale,
FixHint: "Check if polecats are stuck or crashed",
}
@@ -441,7 +441,7 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: "No stale ephemeral activity detected",
Message: "No stale wisp activity detected",
}
}

View File

@@ -452,7 +452,7 @@ func (m *Manager) AssignIssue(name, issue string) error {
}
// ClearIssue removes the issue assignment from a polecat.
// In the ephemeral model, this transitions to Done state for cleanup.
// In the transient model, this transitions to Done state for cleanup.
// This clears the assignee from the currently assigned issue in beads.
// If beads is not available, this is a no-op.
func (m *Manager) ClearIssue(name string) error {
@@ -485,7 +485,7 @@ func (m *Manager) ClearIssue(name string) error {
}
// Wake transitions a polecat from idle to active.
// Deprecated: In the ephemeral model, polecats start in working state.
// Deprecated: In the transient model, polecats start in working state.
// This method is kept for backward compatibility with existing polecats.
func (m *Manager) Wake(name string) error {
polecat, err := m.Get(name)
@@ -502,7 +502,7 @@ func (m *Manager) Wake(name string) error {
}
// Sleep transitions a polecat from active to idle.
// Deprecated: In the ephemeral model, polecats are deleted when done.
// Deprecated: In the transient model, polecats are deleted when done.
// This method is kept for backward compatibility.
func (m *Manager) Sleep(name string) error {
polecat, err := m.Get(name)

View File

@@ -4,12 +4,12 @@ package polecat
import "time"
// State represents the current state of a polecat.
// In the ephemeral model, polecats exist only while working.
// In the transient model, polecats exist only while working.
type State string
const (
// StateWorking means the polecat is actively working on an issue.
// This is the initial and primary state for ephemeral polecats.
// This is the initial and primary state for transient polecats.
StateWorking State = "working"
// StateDone means the polecat has completed its assigned work
@@ -31,7 +31,7 @@ func (s State) IsWorking() bool {
}
// IsActive returns true if the polecat session is actively working.
// For ephemeral polecats, this is true for working state and
// For transient polecats, this is true for working state and
// legacy idle/active states (treated as working).
func (s State) IsActive() bool {
return s == StateWorking || s == StateIdle || s == StateActive

View File

@@ -5,7 +5,7 @@
## Your Role: CREW WORKER ({{ .Polecat }} in {{ .RigName }})
You are a **crew worker** - the overseer's (human's) personal workspace within the
{{ .RigName }} rig. Unlike polecats which are witness-managed and ephemeral, you are:
{{ .RigName }} 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
@@ -26,7 +26,7 @@ Town ({{ .TownRoot }})
│ ├── .beads/ ← Issue tracking (you have write access)
│ ├── crew/
│ │ └── {{ .Polecat }}/ ← You are here (your git clone)
│ ├── polecats/ ← Ephemeral workers (not you)
│ ├── polecats/ ← Transient workers (not you)
│ ├── refinery/ ← Merge queue processor
│ └── witness/ ← Polecat lifecycle (doesn't monitor you)
```
@@ -141,7 +141,7 @@ Before ending your session:
## Tips
- **You own your workspace**: Unlike polecats, you're not ephemeral. Keep it organized.
- **You own your workspace**: Unlike polecats, you're not transient. Keep it organized.
- **Handoff liberally**: When in doubt, write a handoff mail. Context is precious.
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.

View File

@@ -553,7 +553,7 @@ func extractPolecatName(body string) string {
return ""
}
// cleanupPolecat performs the full cleanup sequence for an ephemeral polecat.
// cleanupPolecat performs the full cleanup sequence for a transient polecat.
// 1. Check for uncommitted work (stubbornly refuses to lose work)
// 2. Kill session
// 3. Remove worktree

View File

@@ -4,7 +4,7 @@
## 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 ephemeral, you are:
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
@@ -157,7 +157,7 @@ Before ending your session:
## Tips
- **You own your workspace**: Unlike polecats, you're not ephemeral. Keep it organized.
- **You own your workspace**: Unlike polecats, you're not transient. Keep it organized.
- **Handoff liberally**: When in doubt, write a handoff mail. Context is precious.
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.

View File

@@ -4,10 +4,10 @@
## Your Role: POLECAT ({{ name }} in {{ rig }})
You are a **polecat** - an ephemeral worker agent in the Gas Town swarm. You are:
You are a **polecat** - a transient worker agent in the Gas Town swarm. You are:
- **Task-focused**: You work on one assigned issue at a time
- **Ephemeral**: When your work is done, you may be decommissioned
- **Transient**: When your work is done, you may be decommissioned
- **Witness-managed**: The Witness monitors your progress and can nudge or reassign you
- **Part of a swarm**: Other polecats may be working on related issues in parallel

View File

@@ -260,8 +260,8 @@ Before killing ANY polecat session, verify:
**If all checks pass:**
1. Kill session: `tmux kill-session -t gt-{{ rig }}-<name>`
2. Remove worktree: `git worktree remove polecats/<name>` (if ephemeral)
3. Delete branch: `git branch -d polecat/<name>` (if ephemeral)
2. Remove worktree: `git worktree remove polecats/<name>` (if transient)
3. Delete branch: `git branch -d polecat/<name>` (if transient)
---
@@ -303,7 +303,7 @@ tmux capture-pane -t gt-{{ rig }}-<name> -p | tail -40
# Session control
tmux kill-session -t gt-{{ rig }}-<name>
# Worktree cleanup (for ephemeral polecats)
# Worktree cleanup (for transient polecats)
git worktree remove polecats/<name>
git branch -d polecat/<name>