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:
@@ -5,7 +5,7 @@
|
|||||||
## Your Role: CREW WORKER (joe in gastown)
|
## Your Role: CREW WORKER (joe in gastown)
|
||||||
|
|
||||||
You are a **crew worker** - the overseer's (human's) personal workspace within the
|
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
|
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||||
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||||
|
|||||||
@@ -357,8 +357,8 @@ Molecules follow a **states of matter** metaphor through their lifecycle:
|
|||||||
┌───────────────┐ ┌───────────────┐
|
┌───────────────┐ ┌───────────────┐
|
||||||
│ Mol │ │ Wisp │
|
│ Mol │ │ Wisp │
|
||||||
│ (liquid) │ │ (gas) │
|
│ (liquid) │ │ (gas) │
|
||||||
│ durable │ │ ephemeral │
|
│ durable │ │ transient │
|
||||||
│ main beads │ │ .beads-eph/ │
|
│ main beads │ │ .beads-wisp/ │
|
||||||
└───────┬───────┘ └───────┬───────┘
|
└───────┬───────┘ └───────┬───────┘
|
||||||
│ │
|
│ │
|
||||||
bd mol squash bd mol squash
|
bd mol squash bd mol squash
|
||||||
@@ -374,15 +374,15 @@ Molecules follow a **states of matter** metaphor through their lifecycle:
|
|||||||
**Phase transitions:**
|
**Phase transitions:**
|
||||||
- **Proto → Mol/Wisp** (`bd mol bond`): Instantiate a template into a running execution
|
- **Proto → Mol/Wisp** (`bd mol bond`): Instantiate a template into a running execution
|
||||||
- **Mol → Digest** (`bd mol squash`): Compress completed work into permanent record
|
- **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
|
### 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 |
|
| **Persistence** | Survives indefinitely | Evaporates on squash/burn |
|
||||||
| **Git tracking** | Committed, synced | Never committed |
|
| **Git tracking** | Committed, synced | Never committed |
|
||||||
| **Audit trail** | Full history preserved | Only digest (if squashed) |
|
| **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:**
|
**Use Wisp for:**
|
||||||
- Orchestration tasks (witness patrols, health checks)
|
- Orchestration tasks (witness patrols, health checks)
|
||||||
- Polecat work sessions (ephemeral by nature)
|
- Polecat work sessions (transient by nature)
|
||||||
- Patrol loops (continuous monitoring)
|
- Patrol loops (continuous monitoring)
|
||||||
- Routine operations (no audit value)
|
- Routine operations (no audit value)
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ This is like a **distributed work queue** backed by beads:
|
|||||||
|
|
||||||
### Wisp Molecules: Transient Execution Traces
|
### 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?**
|
**Why wisps?**
|
||||||
- **Observability**: See what's happening during execution without cluttering the permanent ledger
|
- **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:**
|
**Wisp storage:**
|
||||||
|
|
||||||
Wisps are stored in a per-rig ephemeral database:
|
Wisps are stored in a per-rig wisp database:
|
||||||
- `<rig>/.beads-ephemeral/` - Separate from permanent beads, **gitignored**
|
- `<rig>/.beads-wisp/` - Separate from permanent beads, **gitignored**
|
||||||
- Fast writes, no sync overhead
|
- Fast writes, no sync overhead
|
||||||
- Auto-cleaned on squash/burn
|
- Auto-cleaned on squash/burn
|
||||||
- Digests write to permanent beads
|
- 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
|
4. On completion, polecat generates summary and squashes wisps to digest
|
||||||
5. Request shutdown, get deleted
|
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
|
## Key Workflows
|
||||||
|
|
||||||
@@ -1694,7 +1694,7 @@ sequenceDiagram
|
|||||||
After Witness kills session:
|
After Witness kills session:
|
||||||
- Remove worktree: `git worktree remove polecats/<name>`
|
- Remove worktree: `git worktree remove polecats/<name>`
|
||||||
- Delete branch: `git branch -d polecat/<name>`
|
- Delete branch: `git branch -d polecat/<name>`
|
||||||
- Polecat ceases to exist (ephemeral)
|
- Polecat ceases to exist (transient)
|
||||||
|
|
||||||
### 13. Resource-Constrained Worker Pool
|
### 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
|
- **Workers cycle**: If individual tasks are very large
|
||||||
- **Daemon persistence**: Survives all agent restarts
|
- **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.
|
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)
|
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
|
### Landing & Merge Queue
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ parallel: true
|
|||||||
|
|
||||||
# Beads Cleanup
|
# Beads Cleanup
|
||||||
|
|
||||||
Daily cleanup of ephemeral beads.
|
Daily cleanup of wisp storage.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ bd mol bond <proto-id> [--wisp] [--assignee=<addr>]
|
|||||||
|
|
||||||
- **Default (Mol)**: Creates a durable molecule tracked in the main `.beads/` database.
|
- **Default (Mol)**: Creates a durable molecule tracked in the main `.beads/` database.
|
||||||
Steps become permanent issues that survive indefinitely.
|
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.
|
transient and will be cleaned up on squash or burn.
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
@@ -133,7 +133,7 @@ bd mol squash <mol-id> --summary='...'
|
|||||||
|
|
||||||
- **For Mol (durable)**: Creates a digest issue in the permanent beads database.
|
- **For Mol (durable)**: Creates a digest issue in the permanent beads database.
|
||||||
The digest contains the summary and links back to the original proto.
|
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,
|
and creates a digest in the permanent database. The execution trace is gone,
|
||||||
but the outcome is preserved.
|
but the outcome is preserved.
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ bd mol burn <mol-id> [--reason='...']
|
|||||||
|
|
||||||
- Discards all molecule state (steps, progress, artifacts)
|
- Discards all molecule state (steps, progress, artifacts)
|
||||||
- No digest is created - the molecule leaves no permanent record
|
- 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
|
- For Mols: Marks as abandoned/closed without digest
|
||||||
|
|
||||||
**When to burn vs squash:**
|
**When to burn vs squash:**
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ gastown/
|
|||||||
│ ├── spawn/
|
│ ├── spawn/
|
||||||
│ │ ├── new_polecat.md
|
│ │ ├── new_polecat.md
|
||||||
│ │ ├── reuse_polecat.md
|
│ │ ├── reuse_polecat.md
|
||||||
│ │ └── ephemeral_worker.md
|
│ │ └── transient_worker.md
|
||||||
│ └── lifecycle/
|
│ └── lifecycle/
|
||||||
│ ├── handoff.md
|
│ ├── handoff.md
|
||||||
│ ├── escalation.md
|
│ ├── escalation.md
|
||||||
@@ -175,7 +175,7 @@ type PromptContext struct {
|
|||||||
Role string
|
Role string
|
||||||
RigName string
|
RigName string
|
||||||
PolecatName string
|
PolecatName string
|
||||||
Ephemeral bool
|
Transient bool
|
||||||
IssueID string
|
IssueID string
|
||||||
IssueTitle string
|
IssueTitle string
|
||||||
// ... additional fields
|
// ... 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 }})
|
## Your Role: CREW WORKER ({{ name }} in {{ rig }})
|
||||||
|
|
||||||
You are a **crew worker** - the overseer's (human's) personal workspace within the {{ rig }} 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
|
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||||
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Wisp Architecture: Ephemeral Molecule Storage
|
# Wisp Architecture: Transient Molecule Storage
|
||||||
|
|
||||||
> Status: Design Spec v1 - December 2024
|
> Status: Design Spec v1 - December 2024
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
**Wisps** are ephemeral molecule execution traces - the "steam" in Gas Town's engine
|
**Wisps** are transient molecule execution traces - the "steam" in Gas Town's engine
|
||||||
metaphor. This document specifies where wisps are stored, how they're managed, and
|
metaphor. Claude is fire; Claude Code is a Steam engine; Gas Town is a Steam Train,
|
||||||
which roles use them.
|
with Beads as the tracks. Wisps are steam vapors that dissipate after the work is done.
|
||||||
|
|
||||||
## Core Principle
|
## Core Principle
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ which roles use them.
|
|||||||
| Artifact | Storage | Git Tracked | Purpose |
|
| Artifact | Storage | Git Tracked | Purpose |
|
||||||
|----------|---------|-------------|---------|
|
|----------|---------|-------------|---------|
|
||||||
| Issues | `.beads/issues.jsonl` | Yes | Permanent project history |
|
| 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 |
|
| Digests | `.beads/issues.jsonl` | Yes | Compressed summaries of squashed wisps |
|
||||||
|
|
||||||
## Storage Architecture
|
## Storage Architecture
|
||||||
@@ -28,27 +28,27 @@ which roles use them.
|
|||||||
│ ├── .beads/ # CANONICAL rig beads (versioned)
|
│ ├── .beads/ # CANONICAL rig beads (versioned)
|
||||||
│ │ ├── issues.jsonl # Permanent issues + digests
|
│ │ ├── issues.jsonl # Permanent issues + digests
|
||||||
│ │ ├── config.yaml
|
│ │ ├── config.yaml
|
||||||
│ │ └── .gitignore # Excludes .beads-ephemeral
|
│ │ └── .gitignore # Excludes .beads-wisp
|
||||||
│ │
|
│ │
|
||||||
│ └── .beads-ephemeral/ # GITIGNORED - local wisps
|
│ └── .beads-wisp/ # GITIGNORED - local wisps
|
||||||
│ └── issues.jsonl # In-progress ephemeral molecules
|
│ └── issues.jsonl # In-progress wisp molecules
|
||||||
│
|
│
|
||||||
├── refinery/rig/ # Refinery's clone
|
├── refinery/rig/ # Refinery's clone
|
||||||
│ ├── .beads/ # Inherits from mayor/rig
|
│ ├── .beads/ # Inherits from mayor/rig
|
||||||
│ └── .beads-ephemeral/ # Refinery's local wisps
|
│ └── .beads-wisp/ # Refinery's local wisps
|
||||||
│
|
│
|
||||||
├── witness/ # Witness (no clone needed)
|
├── witness/ # Witness (no clone needed)
|
||||||
│ └── .beads-ephemeral/ # Witness's local wisps
|
│ └── .beads-wisp/ # Witness's local wisps
|
||||||
│
|
│
|
||||||
└── polecats/<name>/ # Polecat worktrees
|
└── polecats/<name>/ # Polecat worktrees
|
||||||
├── .beads/ # Inherits from mayor/rig
|
├── .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
|
### Key Points
|
||||||
|
|
||||||
1. **`.beads-ephemeral/` is gitignored** - Never synced, never versioned
|
1. **`.beads-wisp/` is gitignored** - Never synced, never versioned
|
||||||
2. **Each execution context has its own ephemeral store** - Process isolation
|
2. **Each execution context has its own wisp store** - Process isolation
|
||||||
3. **Digests go to canonical `.beads/`** - Permanent record after squash
|
3. **Digests go to canonical `.beads/`** - Permanent record after squash
|
||||||
4. **Wisps are deleted after squash/burn** - No accumulation
|
4. **Wisps are deleted after squash/burn** - No accumulation
|
||||||
|
|
||||||
@@ -56,24 +56,24 @@ which roles use them.
|
|||||||
|
|
||||||
Add to `.beads/.gitignore`:
|
Add to `.beads/.gitignore`:
|
||||||
```
|
```
|
||||||
.beads-ephemeral/
|
.beads-wisp/
|
||||||
```
|
```
|
||||||
|
|
||||||
Or add to rig-level `.gitignore`:
|
Or add to rig-level `.gitignore`:
|
||||||
```
|
```
|
||||||
**/.beads-ephemeral/
|
**/.beads-wisp/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Wisp Lifecycle
|
## Wisp Lifecycle
|
||||||
|
|
||||||
```
|
```
|
||||||
bd mol bond <proto> --ephemeral
|
bd mol bond <proto> --wisp
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────┐
|
┌─────────────────────────┐
|
||||||
│ .beads-ephemeral/ │
|
│ .beads-wisp/ │
|
||||||
│ └── issues.jsonl │ ← Wisp created here
|
│ └── 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 |
|
| Role | Molecule | Storage Location | Squash Frequency |
|
||||||
|------|----------|------------------|------------------|
|
|------|----------|------------------|------------------|
|
||||||
| **Deacon** | mol-deacon-patrol | mayor/rig/.beads-ephemeral/ | Per cycle |
|
| **Deacon** | mol-deacon-patrol | mayor/rig/.beads-wisp/ | Per cycle |
|
||||||
| **Witness** | mol-witness-patrol | witness/.beads-ephemeral/ | Per cycle |
|
| **Witness** | mol-witness-patrol | witness/.beads-wisp/ | Per cycle |
|
||||||
| **Refinery** | mol-refinery-cycle | refinery/rig/.beads-ephemeral/ | Per cycle |
|
| **Refinery** | mol-refinery-cycle | refinery/rig/.beads-wisp/ | Per cycle |
|
||||||
|
|
||||||
### Roles That Use Regular Molecules
|
### Roles That Use Regular Molecules
|
||||||
|
|
||||||
@@ -126,8 +126,8 @@ Every role using wisps must implement this pattern:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func patrolCycle() {
|
func patrolCycle() {
|
||||||
// 1. Bond ephemeral molecule
|
// 1. Bond wisp molecule
|
||||||
mol := bdMolBond("mol-<role>-patrol", "--ephemeral")
|
mol := bdMolBond("mol-<role>-patrol", "--wisp")
|
||||||
|
|
||||||
// 2. Execute cycle steps
|
// 2. Execute cycle steps
|
||||||
for _, step := range mol.Steps {
|
for _, step := range mol.Steps {
|
||||||
@@ -140,7 +140,7 @@ func patrolCycle() {
|
|||||||
|
|
||||||
// 4. Squash - REQUIRED (this is the cleanup)
|
// 4. Squash - REQUIRED (this is the cleanup)
|
||||||
bdMolSquash(mol.ID, "--summary", summary)
|
bdMolSquash(mol.ID, "--summary", summary)
|
||||||
// Wisp deleted from .beads-ephemeral/
|
// Wisp deleted from .beads-wisp/
|
||||||
// Digest created in .beads/issues.jsonl
|
// Digest created in .beads/issues.jsonl
|
||||||
|
|
||||||
// 5. Sleep until next cycle
|
// 5. Sleep until next cycle
|
||||||
@@ -157,11 +157,11 @@ For this architecture to work, Beads needs:
|
|||||||
### New Commands
|
### New Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Bond with ephemeral flag
|
# Bond with wisp flag (--ephemeral is an alias)
|
||||||
bd mol bond <proto> --ephemeral
|
bd mol bond <proto> --wisp
|
||||||
# Creates in .beads-ephemeral/ instead of .beads/
|
# Creates in .beads-wisp/ instead of .beads/
|
||||||
|
|
||||||
# List ephemeral molecules
|
# List wisps
|
||||||
bd wisp list
|
bd wisp list
|
||||||
# Shows in-progress wisps
|
# Shows in-progress wisps
|
||||||
|
|
||||||
@@ -172,28 +172,28 @@ bd wisp gc
|
|||||||
|
|
||||||
### Storage Behavior
|
### Storage Behavior
|
||||||
|
|
||||||
| Command | With `--ephemeral` | Without |
|
| Command | With `--wisp` | Without |
|
||||||
|---------|-------------------|---------|
|
|---------|---------------|---------|
|
||||||
| `bd mol bond` | Creates in `.beads-ephemeral/` | Creates in `.beads/` |
|
| `bd mol bond` | Creates in `.beads-wisp/` | Creates in `.beads/` |
|
||||||
| `bd mol step` | Updates in ephemeral | Updates in permanent |
|
| `bd mol step` | Updates in wisp store | Updates in permanent |
|
||||||
| `bd mol squash` | Deletes from ephemeral, creates digest in permanent | Creates digest in permanent |
|
| `bd mol squash` | Deletes from wisp, creates digest in permanent | Creates digest in permanent |
|
||||||
| `bd mol burn` | Deletes from ephemeral | Marks abandoned in permanent |
|
| `bd mol burn` | Deletes from wisp | Marks abandoned in permanent |
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# .beads/config.yaml
|
# .beads/config.yaml
|
||||||
ephemeral:
|
wisp:
|
||||||
enabled: true
|
enabled: true
|
||||||
directory: ../.beads-ephemeral # Relative to .beads/
|
directory: ../.beads-wisp # Relative to .beads/
|
||||||
auto_gc: true # Clean orphans on bd init
|
auto_gc: true # Clean orphans on bd init
|
||||||
```
|
```
|
||||||
|
|
||||||
## Crash Recovery
|
## Crash Recovery
|
||||||
|
|
||||||
If a patrol crashes mid-cycle:
|
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:**
|
2. **On restart, agent can:**
|
||||||
- Resume from last step (if step tracking is granular)
|
- Resume from last step (if step tracking is granular)
|
||||||
- Or burn and start fresh (simpler for patrol loops)
|
- 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:
|
For existing Gas Town installations:
|
||||||
|
|
||||||
1. **Add `.beads-ephemeral/` to gitignore** (immediate)
|
1. **Add `.beads-wisp/` to gitignore** (immediate)
|
||||||
2. **Update patrol runners to use `--ephemeral`** (as patched)
|
2. **Update patrol runners to use `--wisp`** (as patched)
|
||||||
3. **No migration of existing data** - Fresh start for ephemeral storage
|
3. **No migration of existing data** - Fresh start for wisp storage
|
||||||
|
4. **Optional**: Remove old `.beads-ephemeral/` directories
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -250,5 +251,5 @@ For existing Gas Town installations:
|
|||||||
|
|
||||||
## Implementation Tracking
|
## Implementation Tracking
|
||||||
|
|
||||||
- **Beads**: bd-kwjh (Wisp storage: ephemeral molecule tracking)
|
- **Beads**: bd-kwjh (Wisp storage: transient molecule tracking)
|
||||||
- **Gas Town**: gt-3x0z.9 (mol-deacon-patrol uses ephemeral)
|
- **Gas Town**: gt-3x0z.9 (mol-deacon-patrol uses wisps)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var crewCmd = &cobra.Command{
|
|||||||
Short: "Manage crew workspaces (user-managed persistent workspaces)",
|
Short: "Manage crew workspaces (user-managed persistent workspaces)",
|
||||||
Long: `Crew workers are user-managed persistent workspaces within a rig.
|
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
|
- Persistent: Not auto-garbage-collected
|
||||||
- User-managed: Overseer controls lifecycle
|
- User-managed: Overseer controls lifecycle
|
||||||
- Long-lived identities: recognizable names like dave, emma, fred
|
- Long-lived identities: recognizable names like dave, emma, fred
|
||||||
|
|||||||
@@ -63,12 +63,12 @@ func runDoctor(cmd *cobra.Command, args []string) error {
|
|||||||
d.Register(doctor.NewIdentityCollisionCheck())
|
d.Register(doctor.NewIdentityCollisionCheck())
|
||||||
d.Register(doctor.NewThemeCheck())
|
d.Register(doctor.NewThemeCheck())
|
||||||
|
|
||||||
// Ephemeral beads checks
|
// Wisp storage checks
|
||||||
d.Register(doctor.NewEphemeralExistsCheck())
|
d.Register(doctor.NewWispExistsCheck())
|
||||||
d.Register(doctor.NewEphemeralGitCheck())
|
d.Register(doctor.NewWispGitCheck())
|
||||||
d.Register(doctor.NewEphemeralOrphansCheck())
|
d.Register(doctor.NewWispOrphansCheck())
|
||||||
d.Register(doctor.NewEphemeralSizeCheck())
|
d.Register(doctor.NewWispSizeCheck())
|
||||||
d.Register(doctor.NewEphemeralStaleCheck())
|
d.Register(doctor.NewWispStaleCheck())
|
||||||
|
|
||||||
// Run checks
|
// Run checks
|
||||||
var report *doctor.Report
|
var report *doctor.Report
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const HQGitignore = `# Gas Town HQ .gitignore
|
|||||||
# Ignore: Git clones (polecats, mayor/refinery rigs), runtime state
|
# Ignore: Git clones (polecats, mayor/refinery rigs), runtime state
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Runtime state files (ephemeral)
|
# Runtime state files (transient)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
**/state.json
|
**/state.json
|
||||||
**/*.lock
|
**/*.lock
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ var polecatListCmd = &cobra.Command{
|
|||||||
Short: "List polecats in a rig",
|
Short: "List polecats in a rig",
|
||||||
Long: `List polecats in a rig or all rigs.
|
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:
|
all currently active polecats with their states:
|
||||||
- working: Actively working on an issue
|
- working: Actively working on an issue
|
||||||
- done: Completed work, waiting for cleanup
|
- done: Completed work, waiting for cleanup
|
||||||
@@ -91,7 +91,7 @@ var polecatWakeCmd = &cobra.Command{
|
|||||||
Short: "(Deprecated) Resume a polecat to working state",
|
Short: "(Deprecated) Resume a polecat to working state",
|
||||||
Long: `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.
|
via 'gt spawn'. This command is kept for backward compatibility.
|
||||||
|
|
||||||
Transitions: done → working
|
Transitions: done → working
|
||||||
@@ -107,7 +107,7 @@ var polecatSleepCmd = &cobra.Command{
|
|||||||
Short: "(Deprecated) Mark polecat as done",
|
Short: "(Deprecated) Mark polecat as done",
|
||||||
Long: `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
|
which triggers automatic cleanup by the Witness. This command is kept for
|
||||||
backward compatibility.
|
backward compatibility.
|
||||||
|
|
||||||
|
|||||||
@@ -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.Printf("You are the **Witness** for rig: %s\n\n", style.Bold.Render(ctx.Rig))
|
||||||
fmt.Println("## Responsibilities")
|
fmt.Println("## Responsibilities")
|
||||||
fmt.Println("- Monitor polecat health via heartbeat")
|
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("- Report rig status to Mayor")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("## Key Commands")
|
fmt.Println("## Key Commands")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ infrastructure agents are running:
|
|||||||
• Mayor - Global work coordinator
|
• Mayor - Global work coordinator
|
||||||
• Witnesses - Per-rig polecat managers
|
• 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.
|
spawned on demand by the Mayor or Witnesses.
|
||||||
|
|
||||||
Running 'gt up' multiple times is safe - it only starts services that
|
Running 'gt up' multiple times is safe - it only starts services that
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ var deaconMOTDMessages = []string{
|
|||||||
"Thanks for keeping the town running!",
|
"Thanks for keeping the town running!",
|
||||||
"You are Gas Town's most critical role.",
|
"You are Gas Town's most critical role.",
|
||||||
"You are the heart of Gas Town! Be watchful!",
|
"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: 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.",
|
"The town sleeps soundly because you never do.",
|
||||||
"Tip: Mayor handles cross-rig coordination; you handle health.",
|
"Tip: Mayor handles cross-rig coordination; you handle health.",
|
||||||
"Your vigilance keeps the agents honest.",
|
"Your vigilance keeps the agents honest.",
|
||||||
|
|||||||
@@ -11,26 +11,26 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/config"
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EphemeralExistsCheck verifies that .beads-ephemeral/ exists for each rig.
|
// WispExistsCheck verifies that .beads-wisp/ exists for each rig.
|
||||||
type EphemeralExistsCheck struct {
|
type WispExistsCheck struct {
|
||||||
FixableCheck
|
FixableCheck
|
||||||
missingRigs []string // Cached for fix
|
missingRigs []string // Cached for fix
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEphemeralExistsCheck creates a new ephemeral exists check.
|
// NewWispExistsCheck creates a new wisp exists check.
|
||||||
func NewEphemeralExistsCheck() *EphemeralExistsCheck {
|
func NewWispExistsCheck() *WispExistsCheck {
|
||||||
return &EphemeralExistsCheck{
|
return &WispExistsCheck{
|
||||||
FixableCheck: FixableCheck{
|
FixableCheck: FixableCheck{
|
||||||
BaseCheck: BaseCheck{
|
BaseCheck: BaseCheck{
|
||||||
CheckName: "ephemeral-exists",
|
CheckName: "wisp-exists",
|
||||||
CheckDescription: "Check if ephemeral beads directory exists for each rig",
|
CheckDescription: "Check if wisp directory exists for each rig",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks if .beads-ephemeral/ exists for each rig.
|
// Run checks if .beads-wisp/ exists for each rig.
|
||||||
func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *WispExistsCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
c.missingRigs = nil // Reset cache
|
c.missingRigs = nil // Reset cache
|
||||||
|
|
||||||
// Find all rigs
|
// Find all rigs
|
||||||
@@ -55,8 +55,8 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
// Check each rig
|
// Check each rig
|
||||||
var missing []string
|
var missing []string
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
|
||||||
missing = append(missing, rigName)
|
missing = append(missing, rigName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
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,
|
Details: missing,
|
||||||
FixHint: "Run 'gt doctor --fix' to create missing directories",
|
FixHint: "Run 'gt doctor --fix' to create missing directories",
|
||||||
}
|
}
|
||||||
@@ -75,23 +75,23 @@ func (c *EphemeralExistsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
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.
|
// Fix creates missing .beads-wisp/ directories.
|
||||||
func (c *EphemeralExistsCheck) Fix(ctx *CheckContext) error {
|
func (c *WispExistsCheck) Fix(ctx *CheckContext) error {
|
||||||
for _, rigName := range c.missingRigs {
|
for _, rigName := range c.missingRigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if err := os.MkdirAll(ephemeralPath, 0755); err != nil {
|
if err := os.MkdirAll(wispPath, 0755); err != nil {
|
||||||
return fmt.Errorf("creating %s: %w", ephemeralPath, err)
|
return fmt.Errorf("creating %s: %w", wispPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoverRigs finds all registered rigs.
|
// 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")
|
rigsPath := filepath.Join(townRoot, "mayor", "rigs.json")
|
||||||
data, err := os.ReadFile(rigsPath)
|
data, err := os.ReadFile(rigsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,26 +113,26 @@ func (c *EphemeralExistsCheck) discoverRigs(townRoot string) ([]string, error) {
|
|||||||
return rigs, nil
|
return rigs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EphemeralGitCheck verifies that .beads-ephemeral/ is a valid git repo.
|
// WispGitCheck verifies that .beads-wisp/ is a valid git repo.
|
||||||
type EphemeralGitCheck struct {
|
type WispGitCheck struct {
|
||||||
FixableCheck
|
FixableCheck
|
||||||
invalidRigs []string // Cached for fix
|
invalidRigs []string // Cached for fix
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEphemeralGitCheck creates a new ephemeral git check.
|
// NewWispGitCheck creates a new wisp git check.
|
||||||
func NewEphemeralGitCheck() *EphemeralGitCheck {
|
func NewWispGitCheck() *WispGitCheck {
|
||||||
return &EphemeralGitCheck{
|
return &WispGitCheck{
|
||||||
FixableCheck: FixableCheck{
|
FixableCheck: FixableCheck{
|
||||||
BaseCheck: BaseCheck{
|
BaseCheck: BaseCheck{
|
||||||
CheckName: "ephemeral-git",
|
CheckName: "wisp-git",
|
||||||
CheckDescription: "Check if ephemeral beads directories are valid git repos",
|
CheckDescription: "Check if wisp directories are valid git repos",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks if .beads-ephemeral/ directories are valid git repos.
|
// Run checks if .beads-wisp/ directories are valid git repos.
|
||||||
func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *WispGitCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
c.invalidRigs = nil // Reset cache
|
c.invalidRigs = nil // Reset cache
|
||||||
|
|
||||||
// Find all rigs
|
// 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 invalid []string
|
||||||
var checked int
|
var checked int
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
|
||||||
continue // Skip if directory doesn't exist (handled by ephemeral-exists)
|
continue // Skip if directory doesn't exist (handled by wisp-exists)
|
||||||
}
|
}
|
||||||
checked++
|
checked++
|
||||||
|
|
||||||
// Check if it's a valid git repo
|
// 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) {
|
if _, err := os.Stat(gitDir); os.IsNotExist(err) {
|
||||||
invalid = append(invalid, rigName)
|
invalid = append(invalid, rigName)
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
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{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
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,
|
Details: invalid,
|
||||||
FixHint: "Run 'gt doctor --fix' to initialize git repos",
|
FixHint: "Run 'gt doctor --fix' to initialize git repos",
|
||||||
}
|
}
|
||||||
@@ -193,47 +193,47 @@ func (c *EphemeralGitCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
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.
|
// Fix initializes git repos in wisp directories.
|
||||||
func (c *EphemeralGitCheck) Fix(ctx *CheckContext) error {
|
func (c *WispGitCheck) Fix(ctx *CheckContext) error {
|
||||||
for _, rigName := range c.invalidRigs {
|
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 := exec.Command("git", "init")
|
||||||
cmd.Dir = ephemeralPath
|
cmd.Dir = wispPath
|
||||||
if err := cmd.Run(); err != nil {
|
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
|
// Create config.yaml for wisp storage
|
||||||
configPath := filepath.Join(ephemeralPath, "config.yaml")
|
configPath := filepath.Join(wispPath, "config.yaml")
|
||||||
configContent := "ephemeral: true\n# No sync-branch - ephemeral is local only\n"
|
configContent := "wisp: true\n# No sync-branch - wisps are local only\n"
|
||||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EphemeralOrphansCheck detects molecules started but never squashed (>24h old).
|
// WispOrphansCheck detects molecules started but never squashed (>24h old).
|
||||||
type EphemeralOrphansCheck struct {
|
type WispOrphansCheck struct {
|
||||||
BaseCheck
|
BaseCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEphemeralOrphansCheck creates a new ephemeral orphans check.
|
// NewWispOrphansCheck creates a new wisp orphans check.
|
||||||
func NewEphemeralOrphansCheck() *EphemeralOrphansCheck {
|
func NewWispOrphansCheck() *WispOrphansCheck {
|
||||||
return &EphemeralOrphansCheck{
|
return &WispOrphansCheck{
|
||||||
BaseCheck: BaseCheck{
|
BaseCheck: BaseCheck{
|
||||||
CheckName: "ephemeral-orphans",
|
CheckName: "wisp-orphans",
|
||||||
CheckDescription: "Check for orphaned molecules (>24h old, never squashed)",
|
CheckDescription: "Check for orphaned wisps (>24h old, never squashed)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks for orphaned molecules.
|
// Run checks for orphaned wisps.
|
||||||
func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *WispOrphansCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
rigs, err := discoverRigs(ctx.TownRoot)
|
rigs, err := discoverRigs(ctx.TownRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
@@ -256,13 +256,13 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
cutoff := time.Now().Add(-24 * time.Hour)
|
cutoff := time.Now().Add(-24 * time.Hour)
|
||||||
|
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for molecule directories or issue files older than 24h
|
// 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)
|
info, err := os.Stat(issuesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // No issues file
|
continue // No issues file
|
||||||
@@ -279,7 +279,7 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
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,
|
Details: orphans,
|
||||||
FixHint: "Manual review required - these may contain unsquashed work",
|
FixHint: "Manual review required - these may contain unsquashed work",
|
||||||
}
|
}
|
||||||
@@ -288,27 +288,27 @@ func (c *EphemeralOrphansCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
Status: StatusOK,
|
||||||
Message: "No orphaned molecules found",
|
Message: "No orphaned wisps found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EphemeralSizeCheck warns if ephemeral repo is too large (>100MB).
|
// WispSizeCheck warns if wisp repo is too large (>100MB).
|
||||||
type EphemeralSizeCheck struct {
|
type WispSizeCheck struct {
|
||||||
BaseCheck
|
BaseCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEphemeralSizeCheck creates a new ephemeral size check.
|
// NewWispSizeCheck creates a new wisp size check.
|
||||||
func NewEphemeralSizeCheck() *EphemeralSizeCheck {
|
func NewWispSizeCheck() *WispSizeCheck {
|
||||||
return &EphemeralSizeCheck{
|
return &WispSizeCheck{
|
||||||
BaseCheck: BaseCheck{
|
BaseCheck: BaseCheck{
|
||||||
CheckName: "ephemeral-size",
|
CheckName: "wisp-size",
|
||||||
CheckDescription: "Check if ephemeral beads directories are too large (>100MB)",
|
CheckDescription: "Check if wisp directories are too large (>100MB)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks the size of ephemeral beads directories.
|
// Run checks the size of wisp directories.
|
||||||
func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *WispSizeCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
rigs, err := discoverRigs(ctx.TownRoot)
|
rigs, err := discoverRigs(ctx.TownRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
@@ -331,12 +331,12 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
var oversized []string
|
var oversized []string
|
||||||
|
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
size, err := dirSize(ephemeralPath)
|
size, err := dirSize(wispPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
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,
|
Details: oversized,
|
||||||
FixHint: "Consider cleaning up old completed molecules",
|
FixHint: "Consider cleaning up old completed molecules",
|
||||||
}
|
}
|
||||||
@@ -360,27 +360,27 @@ func (c *EphemeralSizeCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
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.
|
// WispStaleCheck detects molecules with no activity in the last hour.
|
||||||
type EphemeralStaleCheck struct {
|
type WispStaleCheck struct {
|
||||||
BaseCheck
|
BaseCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEphemeralStaleCheck creates a new ephemeral stale check.
|
// NewWispStaleCheck creates a new wisp stale check.
|
||||||
func NewEphemeralStaleCheck() *EphemeralStaleCheck {
|
func NewWispStaleCheck() *WispStaleCheck {
|
||||||
return &EphemeralStaleCheck{
|
return &WispStaleCheck{
|
||||||
BaseCheck: BaseCheck{
|
BaseCheck: BaseCheck{
|
||||||
CheckName: "ephemeral-stale",
|
CheckName: "wisp-stale",
|
||||||
CheckDescription: "Check for stale molecules (no activity in last hour)",
|
CheckDescription: "Check for stale wisps (no activity in last hour)",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks for stale molecules.
|
// Run checks for stale wisps.
|
||||||
func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *WispStaleCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
rigs, err := discoverRigs(ctx.TownRoot)
|
rigs, err := discoverRigs(ctx.TownRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
@@ -403,15 +403,15 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
cutoff := time.Now().Add(-1 * time.Hour)
|
cutoff := time.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
ephemeralPath := filepath.Join(ctx.TownRoot, rigName, ".beads-ephemeral")
|
wispPath := filepath.Join(ctx.TownRoot, rigName, ".beads-wisp")
|
||||||
if _, err := os.Stat(ephemeralPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wispPath); os.IsNotExist(err) {
|
||||||
continue
|
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
|
// We look at the most recent modification time of any file
|
||||||
var mostRecent time.Time
|
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 {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
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,
|
Details: stale,
|
||||||
FixHint: "Check if polecats are stuck or crashed",
|
FixHint: "Check if polecats are stuck or crashed",
|
||||||
}
|
}
|
||||||
@@ -441,7 +441,7 @@ func (c *EphemeralStaleCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
Status: StatusOK,
|
||||||
Message: "No stale ephemeral activity detected",
|
Message: "No stale wisp activity detected",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +452,7 @@ func (m *Manager) AssignIssue(name, issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClearIssue removes the issue assignment from a polecat.
|
// 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.
|
// This clears the assignee from the currently assigned issue in beads.
|
||||||
// If beads is not available, this is a no-op.
|
// If beads is not available, this is a no-op.
|
||||||
func (m *Manager) ClearIssue(name string) error {
|
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.
|
// 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.
|
// This method is kept for backward compatibility with existing polecats.
|
||||||
func (m *Manager) Wake(name string) error {
|
func (m *Manager) Wake(name string) error {
|
||||||
polecat, err := m.Get(name)
|
polecat, err := m.Get(name)
|
||||||
@@ -502,7 +502,7 @@ func (m *Manager) Wake(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sleep transitions a polecat from active to idle.
|
// 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.
|
// This method is kept for backward compatibility.
|
||||||
func (m *Manager) Sleep(name string) error {
|
func (m *Manager) Sleep(name string) error {
|
||||||
polecat, err := m.Get(name)
|
polecat, err := m.Get(name)
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ package polecat
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// State represents the current state of a polecat.
|
// 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
|
type State string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// StateWorking means the polecat is actively working on an issue.
|
// 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"
|
StateWorking State = "working"
|
||||||
|
|
||||||
// StateDone means the polecat has completed its assigned work
|
// 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.
|
// 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).
|
// legacy idle/active states (treated as working).
|
||||||
func (s State) IsActive() bool {
|
func (s State) IsActive() bool {
|
||||||
return s == StateWorking || s == StateIdle || s == StateActive
|
return s == StateWorking || s == StateIdle || s == StateActive
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## Your Role: CREW WORKER ({{ .Polecat }} in {{ .RigName }})
|
## Your Role: CREW WORKER ({{ .Polecat }} in {{ .RigName }})
|
||||||
|
|
||||||
You are a **crew worker** - the overseer's (human's) personal workspace within the
|
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
|
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||||
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||||
@@ -26,7 +26,7 @@ Town ({{ .TownRoot }})
|
|||||||
│ ├── .beads/ ← Issue tracking (you have write access)
|
│ ├── .beads/ ← Issue tracking (you have write access)
|
||||||
│ ├── crew/
|
│ ├── crew/
|
||||||
│ │ └── {{ .Polecat }}/ ← You are here (your git clone)
|
│ │ └── {{ .Polecat }}/ ← You are here (your git clone)
|
||||||
│ ├── polecats/ ← Ephemeral workers (not you)
|
│ ├── polecats/ ← Transient workers (not you)
|
||||||
│ ├── refinery/ ← Merge queue processor
|
│ ├── refinery/ ← Merge queue processor
|
||||||
│ └── witness/ ← Polecat lifecycle (doesn't monitor you)
|
│ └── witness/ ← Polecat lifecycle (doesn't monitor you)
|
||||||
```
|
```
|
||||||
@@ -141,7 +141,7 @@ Before ending your session:
|
|||||||
|
|
||||||
## Tips
|
## 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.
|
- **Handoff liberally**: When in doubt, write a handoff mail. Context is precious.
|
||||||
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
|
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
|
||||||
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.
|
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ func extractPolecatName(body string) string {
|
|||||||
return ""
|
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)
|
// 1. Check for uncommitted work (stubbornly refuses to lose work)
|
||||||
// 2. Kill session
|
// 2. Kill session
|
||||||
// 3. Remove worktree
|
// 3. Remove worktree
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Your Role: CREW WORKER ({{ name }} in {{ rig }})
|
## 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
|
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||||
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||||
@@ -157,7 +157,7 @@ Before ending your session:
|
|||||||
|
|
||||||
## Tips
|
## 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.
|
- **Handoff liberally**: When in doubt, write a handoff mail. Context is precious.
|
||||||
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
|
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
|
||||||
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.
|
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
## Your Role: POLECAT ({{ name }} in {{ rig }})
|
## 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
|
- **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
|
- **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
|
- **Part of a swarm**: Other polecats may be working on related issues in parallel
|
||||||
|
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ Before killing ANY polecat session, verify:
|
|||||||
|
|
||||||
**If all checks pass:**
|
**If all checks pass:**
|
||||||
1. Kill session: `tmux kill-session -t gt-{{ rig }}-<name>`
|
1. Kill session: `tmux kill-session -t gt-{{ rig }}-<name>`
|
||||||
2. Remove worktree: `git worktree remove polecats/<name>` (if ephemeral)
|
2. Remove worktree: `git worktree remove polecats/<name>` (if transient)
|
||||||
3. Delete branch: `git branch -d polecat/<name>` (if ephemeral)
|
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
|
# Session control
|
||||||
tmux kill-session -t gt-{{ rig }}-<name>
|
tmux kill-session -t gt-{{ rig }}-<name>
|
||||||
|
|
||||||
# Worktree cleanup (for ephemeral polecats)
|
# Worktree cleanup (for transient polecats)
|
||||||
git worktree remove polecats/<name>
|
git worktree remove polecats/<name>
|
||||||
git branch -d polecat/<name>
|
git branch -d polecat/<name>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user