terminology: spawn → pour/wisp for molecules (gt-9uy0)

Molecules use chemistry verbs, not spawn:
- pour = create persistent mol (liquid)
- wisp = create ephemeral wisp (vapor)
- spawn = polecats only (workers)

Changes:
- Delete chemistry-design-changes.md (migration doc)
- Remove migration tables from sling-design.md
- Update bond tables: Spawn → Pour/Wisp
- Rename spawnMoleculeFromProto → pourMoleculeFromProto
- Rename spawnMoleculeOnIssue → runMoleculeOnIssue
- Update templates: bd mol spawn → bd wisp
- Update prime.go: commands and messages
- Clean all docs and comments

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-24 14:02:09 -08:00
parent 0acad8af25
commit c10709dc3f
17 changed files with 95 additions and 531 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
// TestChristmasOrnamentPattern tests the dynamic bonding pattern used by mol-witness-patrol.
// This pattern allows a parent molecule step to dynamically spawn child molecules
// This pattern allows a parent molecule step to dynamically create child molecules
// at runtime, with a fanout gate (WaitsFor: all-children) for aggregation.
func TestChristmasOrnamentPattern(t *testing.T) {
if testing.Short() {
+1 -1
View File
@@ -234,7 +234,7 @@ completion, verify clean git state before kills, and escalate stuck workers.
**You do NOT do implementation work.** Your job is oversight, not coding.
This molecule uses dynamic bonding to spawn mol-polecat-arm for each worker,
This molecule uses dynamic bonding to create mol-polecat-arm for each worker,
enabling parallel inspection with a fanout gate for aggregation.
## The Christmas Ornament Shape
+16 -16
View File
@@ -680,7 +680,7 @@ func showMoleculeProgress(b *beads.Beads, rootID string) {
// outputDeaconPatrolContext shows patrol molecule status for the Deacon.
// Deacon uses wisps (Wisp:true issues in main .beads/) for patrol cycles.
// Spawn creates wisp-marked issues that are auto-deleted on squash.
// bd wisp creates wisp-marked issues that are auto-deleted on squash.
func outputDeaconPatrolContext(ctx RoleContext) {
fmt.Println()
fmt.Printf("%s\n\n", style.Bold.Render("## 🔄 Patrol Status (Wisp-based)"))
@@ -732,8 +732,8 @@ func outputDeaconPatrolContext(ctx RoleContext) {
}
if !hasPatrol {
// No active patrol - AUTO-CREATE one
fmt.Println("Status: **No active patrol** - creating mol-deacon-patrol wisp...")
// No active patrol - AUTO-SPAWN one
fmt.Println("Status: **No active patrol** - creating mol-deacon-patrol...")
fmt.Println()
// Find the proto ID for mol-deacon-patrol
@@ -767,7 +767,7 @@ func outputDeaconPatrolContext(ctx RoleContext) {
return
}
// Create the wisp
// Create the patrol wisp
cmdSpawn := exec.Command("bd", "--no-daemon", "wisp", protoID, "--assignee", "deacon")
cmdSpawn.Dir = rigBeadsDir
var stdoutSpawn, stderrSpawn bytes.Buffer
@@ -776,7 +776,7 @@ func outputDeaconPatrolContext(ctx RoleContext) {
if err := cmdSpawn.Run(); err != nil {
fmt.Printf("Failed to create patrol wisp: %s\n", stderrSpawn.String())
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp " + protoID))
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp" + protoID))
return
}
@@ -816,7 +816,7 @@ func outputDeaconPatrolContext(ctx RoleContext) {
fmt.Println("5. At cycle end (loop-or-exit step):")
fmt.Println(" - Generate summary of patrol cycle")
fmt.Println(" - Squash: `bd --no-daemon mol squash <mol-id> --summary \"<summary>\"`")
fmt.Println(" - Loop back to spawn new wisp, or exit if context high")
fmt.Println(" - Loop back to create new wisp, or exit if context high")
if patrolID != "" {
fmt.Println()
fmt.Printf("Current patrol ID: %s\n", patrolID)
@@ -898,8 +898,8 @@ func outputWitnessPatrolContext(ctx RoleContext) {
}
if !hasPatrol {
// No active patrol - AUTO-CREATE one
fmt.Println("Status: **No active patrol** - creating mol-witness-patrol wisp...")
// No active patrol - AUTO-SPAWN one
fmt.Println("Status: **No active patrol** - creating mol-witness-patrol...")
fmt.Println()
// Find the proto ID for mol-witness-patrol
@@ -933,7 +933,7 @@ func outputWitnessPatrolContext(ctx RoleContext) {
return
}
// Create the wisp
// Create the patrol wisp
cmdSpawn := exec.Command("bd", "--no-daemon", "wisp", protoID, "--assignee", ctx.Rig+"/witness")
cmdSpawn.Dir = witnessBeadsDir
var stdoutSpawn, stderrSpawn bytes.Buffer
@@ -942,7 +942,7 @@ func outputWitnessPatrolContext(ctx RoleContext) {
if err := cmdSpawn.Run(); err != nil {
fmt.Printf("Failed to create patrol wisp: %s\n", stderrSpawn.String())
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp " + protoID))
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp" + protoID))
return
}
@@ -982,7 +982,7 @@ func outputWitnessPatrolContext(ctx RoleContext) {
fmt.Println("6. At cycle end (burn-or-loop step):")
fmt.Println(" - Generate summary of patrol cycle")
fmt.Println(" - Squash: `bd --no-daemon mol squash <mol-id> --summary \"<summary>\"`")
fmt.Println(" - Loop back to spawn new wisp, or exit if context high")
fmt.Println(" - Loop back to create new wisp, or exit if context high")
if patrolID != "" {
fmt.Println()
fmt.Printf("Current patrol ID: %s\n", patrolID)
@@ -1064,8 +1064,8 @@ func outputRefineryPatrolContext(ctx RoleContext) {
}
if !hasPatrol {
// No active patrol - AUTO-CREATE one
fmt.Println("Status: **No active patrol** - creating mol-refinery-patrol wisp...")
// No active patrol - AUTO-SPAWN one
fmt.Println("Status: **No active patrol** - creating mol-refinery-patrol...")
fmt.Println()
// Find the proto ID for mol-refinery-patrol
@@ -1099,7 +1099,7 @@ func outputRefineryPatrolContext(ctx RoleContext) {
return
}
// Create the wisp
// Create the patrol wisp
cmdSpawn := exec.Command("bd", "--no-daemon", "wisp", protoID, "--assignee", ctx.Rig+"/refinery")
cmdSpawn.Dir = refineryBeadsDir
var stdoutSpawn, stderrSpawn bytes.Buffer
@@ -1108,7 +1108,7 @@ func outputRefineryPatrolContext(ctx RoleContext) {
if err := cmdSpawn.Run(); err != nil {
fmt.Printf("Failed to create patrol wisp: %s\n", stderrSpawn.String())
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp " + protoID))
fmt.Println(style.Dim.Render("Run manually: bd --no-daemon wisp" + protoID))
return
}
@@ -1148,7 +1148,7 @@ func outputRefineryPatrolContext(ctx RoleContext) {
fmt.Println("6. At cycle end (burn-or-loop step):")
fmt.Println(" - Generate summary of patrol cycle")
fmt.Println(" - Squash: `bd --no-daemon mol squash <mol-id> --summary \"<summary>\"`")
fmt.Println(" - Loop back to spawn new wisp, or exit if context high")
fmt.Println(" - Loop back to create new wisp, or exit if context high")
if patrolID != "" {
fmt.Println()
fmt.Printf("Current patrol ID: %s\n", patrolID)
+30 -30
View File
@@ -50,7 +50,7 @@ SLING MECHANICS:
THING SLING PIPELINE
proto 1. SPAWN Proto Molecule instance
proto 1. POUR Proto Molecule instance
issue 2. ASSIGN Molecule Target agent
epic 3. PIN Work Agent's hook
4. IGNITE Session starts automatically
@@ -525,8 +525,8 @@ func slingToPolecat(townRoot string, target *SlingTarget, thing *SlingThing) err
switch thing.Kind {
case "proto":
// Spawn molecule from proto
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, polecatAddress)
// Pour molecule from proto
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, polecatAddress)
if err != nil {
return err
}
@@ -535,7 +535,7 @@ func slingToPolecat(townRoot string, target *SlingTarget, thing *SlingThing) err
issueID = thing.ID
if thing.Proto != "" {
// Sling issue with molecule workflow
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, polecatAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, polecatAddress)
if err != nil {
return err
}
@@ -679,7 +679,7 @@ func slingToDeacon(townRoot string, target *SlingTarget, thing *SlingThing) erro
ID: patrolIssueID, // Use the resolved beads issue ID
IsWisp: true, // Patrol cycles are ephemeral (gt-jsup)
}
patrolID, _, err = spawnMoleculeFromProto(beadsPath, patrolThing, deaconAddress)
patrolID, _, err = pourMoleculeFromProto(beadsPath, patrolThing, deaconAddress)
if err != nil {
return fmt.Errorf("starting patrol: %w", err)
}
@@ -699,17 +699,17 @@ func slingToDeacon(townRoot string, target *SlingTarget, thing *SlingThing) erro
var beadsIssue *BeadsIssue
issueID := thing.ID
// For protos, we need to spawn the molecule but NOT pin it
// For protos, we need to pour the molecule but NOT pin it
var moleculeCtx *MoleculeContext
var err error
if thing.Kind == "proto" {
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, deaconAddress)
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, deaconAddress)
if err != nil {
return err
}
} else if thing.Kind == "issue" {
if thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, deaconAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, deaconAddress)
if err != nil {
return err
}
@@ -788,14 +788,14 @@ func slingToCrew(townRoot string, target *SlingTarget, thing *SlingThing) error
switch thing.Kind {
case "proto":
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, crewAddress)
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, crewAddress)
if err != nil {
return err
}
case "issue":
issueID = thing.ID
if thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, crewAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, crewAddress)
if err != nil {
return err
}
@@ -882,7 +882,7 @@ func slingToWitness(townRoot string, target *SlingTarget, thing *SlingThing) err
ID: patrolIssueID, // Use the resolved beads issue ID
IsWisp: true, // Patrol cycles are ephemeral (gt-jsup)
}
patrolID, _, err = spawnMoleculeFromProto(beadsPath, patrolThing, witnessAddress)
patrolID, _, err = pourMoleculeFromProto(beadsPath, patrolThing, witnessAddress)
if err != nil {
return fmt.Errorf("starting patrol: %w", err)
}
@@ -902,17 +902,17 @@ func slingToWitness(townRoot string, target *SlingTarget, thing *SlingThing) err
var beadsIssue *BeadsIssue
issueID := thing.ID
// For protos, we need to spawn the molecule but NOT pin it
// For protos, we need to pour the molecule but NOT pin it
var moleculeCtx *MoleculeContext
var err error
if thing.Kind == "proto" {
// Spawn molecule without pinning
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, witnessAddress)
// Pour molecule without pinning
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, witnessAddress)
if err != nil {
return err
}
} else if thing.Kind == "issue" && thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, witnessAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, witnessAddress)
if err != nil {
return err
}
@@ -980,14 +980,14 @@ func slingToPatrolWithReplace(townRoot, beadsPath, agentAddress string, thing *S
switch thing.Kind {
case "proto":
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, agentAddress)
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, agentAddress)
if err != nil {
return err
}
case "issue":
issueID = thing.ID
if thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, agentAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, agentAddress)
if err != nil {
return err
}
@@ -1049,7 +1049,7 @@ func slingToRefinery(townRoot string, target *SlingTarget, thing *SlingThing) er
ID: patrolIssueID, // Use the resolved beads issue ID
IsWisp: true, // Patrol cycles are ephemeral (gt-jsup)
}
patrolID, _, err = spawnMoleculeFromProto(beadsPath, patrolThing, refineryAddress)
patrolID, _, err = pourMoleculeFromProto(beadsPath, patrolThing, refineryAddress)
if err != nil {
return fmt.Errorf("starting patrol: %w", err)
}
@@ -1069,16 +1069,16 @@ func slingToRefinery(townRoot string, target *SlingTarget, thing *SlingThing) er
var beadsIssue *BeadsIssue
issueID := thing.ID
// For protos, we need to spawn the molecule but NOT pin it
// For protos, we need to pour the molecule but NOT pin it
var moleculeCtx *MoleculeContext
var err error
if thing.Kind == "proto" {
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, refineryAddress)
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, refineryAddress)
if err != nil {
return err
}
} else if thing.Kind == "issue" && thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, refineryAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, refineryAddress)
if err != nil {
return err
}
@@ -1151,14 +1151,14 @@ func slingToMayor(townRoot string, target *SlingTarget, thing *SlingThing) error
switch thing.Kind {
case "proto":
issueID, moleculeCtx, err = spawnMoleculeFromProto(beadsPath, thing, mayorAddress)
issueID, moleculeCtx, err = pourMoleculeFromProto(beadsPath, thing, mayorAddress)
if err != nil {
return err
}
case "issue":
issueID = thing.ID
if thing.Proto != "" {
issueID, moleculeCtx, err = spawnMoleculeOnIssue(beadsPath, thing, mayorAddress)
issueID, moleculeCtx, err = runMoleculeOnIssue(beadsPath, thing, mayorAddress)
if err != nil {
return err
}
@@ -1209,15 +1209,15 @@ func slingToMayor(townRoot string, target *SlingTarget, thing *SlingThing) error
return nil
}
// spawnMoleculeFromProto spawns a molecule from a proto template.
func spawnMoleculeFromProto(beadsPath string, thing *SlingThing, assignee string) (string, *MoleculeContext, error) {
// pourMoleculeFromProto creates a molecule from a proto template.
func pourMoleculeFromProto(beadsPath string, thing *SlingThing, assignee string) (string, *MoleculeContext, error) {
moleculeType := "molecule"
if thing.IsWisp {
moleculeType = "wisp"
}
fmt.Printf("Spawning %s from proto %s...\n", moleculeType, thing.ID)
fmt.Printf("Creating %s from proto %s...\n", moleculeType, thing.ID)
// Use bd mol run to spawn the molecule
// Use bd mol run to create the molecule
args := []string{"--no-daemon", "mol", "run", thing.ID, "--json"}
if assignee != "" {
args = append(args, "--var", "assignee="+assignee)
@@ -1266,7 +1266,7 @@ func spawnMoleculeFromProto(beadsPath string, thing *SlingThing, assignee string
return "", nil, fmt.Errorf("parsing molecule result: %w", err)
}
fmt.Printf("%s %s spawned: %s (%d steps)\n",
fmt.Printf("%s %s created: %s (%d steps)\n",
style.Bold.Render("✓"), moleculeType, molResult.RootID, molResult.Created-1)
moleculeCtx := &MoleculeContext{
@@ -1280,8 +1280,8 @@ func spawnMoleculeFromProto(beadsPath string, thing *SlingThing, assignee string
return molResult.RootID, moleculeCtx, nil
}
// spawnMoleculeOnIssue spawns a molecule workflow on an existing issue.
func spawnMoleculeOnIssue(beadsPath string, thing *SlingThing, assignee string) (string, *MoleculeContext, error) {
// runMoleculeOnIssue runs a molecule workflow on an existing issue.
func runMoleculeOnIssue(beadsPath string, thing *SlingThing, assignee string) (string, *MoleculeContext, error) {
fmt.Printf("Running molecule %s on issue %s...\n", thing.Proto, thing.ID)
args := []string{"--no-daemon", "mol", "run", thing.Proto,
+3 -3
View File
@@ -250,7 +250,7 @@ func runSpawn(cmd *cobra.Command, args []string) error {
// Handle molecule instantiation if specified
if spawnMolecule != "" {
// Use bd mol run to spawn the molecule - this handles everything:
// Use bd mol run to create the molecule - this handles everything:
// - Creates child issues from proto template
// - Assigns root to polecat
// - Sets root status to in_progress
@@ -285,7 +285,7 @@ func runSpawn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("parsing molecule result: %w", err)
}
fmt.Printf("%s Molecule spawned: %s (%d steps)\n",
fmt.Printf("%s Molecule created: %s (%d steps)\n",
style.Bold.Render("✓"), molResult.RootID, molResult.Created-1) // -1 for root
// Build molecule context for work assignment
@@ -616,7 +616,7 @@ func buildSpawnContext(issue *BeadsIssue, message string) string {
// MoleculeContext contains information about a molecule workflow assignment.
type MoleculeContext struct {
MoleculeID string // The molecule template ID (proto)
RootIssueID string // The spawned molecule root issue
RootIssueID string // The created molecule root issue
TotalSteps int // Total number of steps in the molecule
StepNumber int // Which step this is (1-indexed)
IsWisp bool // True if this is a wisp (not durable mol)
+7 -7
View File
@@ -72,7 +72,7 @@ Each patrol cycle uses a wisp:
### 1. Create a Wisp for This Cycle
```bash
# Create patrol wisp
# Create wisp for patrol cycle
bd wisp mol-deacon-patrol --assignee=deacon
```
@@ -139,7 +139,7 @@ gt gc --sessions
**loop-or-exit**: Decision point (see Context Management section)
- Read `patrol_count` and `extraordinary_action` from state.json
- If extraordinary action occurred OR patrol_count >= 20 → `gt handoff`
- Otherwise → increment patrol_count, squash wisp, spawn new one
- Otherwise → increment patrol_count, squash wisp, create new one
### 3. Close Steps as You Work
```bash
@@ -275,7 +275,7 @@ A fresh Deacon with empty context can handle emergencies better than one with
1. Read `state.json` for `patrol_count` and `extraordinary_action`
2. If `extraordinary_action == true` → hand off immediately
3. If `patrol_count >= 20` → hand off
4. Otherwise → increment `patrol_count`, save state, spawn new wisp
4. Otherwise → increment `patrol_count`, save state, create new wisp
**Handoff command:** `gt handoff -s "Routine cycle" -m "Completed N patrols, no incidents"`
@@ -306,7 +306,7 @@ gt mol attach-from-mail <mail-id>
bd wisp mol-deacon-patrol --assignee=deacon
```
**Hook has work → Run it. Hook empty → Check mail. Nothing anywhere → Create patrol wisp.**
**Hook has work → Run it. Hook empty → Check mail. Nothing anywhere → Create patrol.**
Then execute. Print the startup banner and work through patrol steps:
@@ -320,14 +320,14 @@ Then execute. Print the startup banner and work through patrol steps:
**No thinking. No "should I?" questions. Hook → Execute.**
If you crash mid-patrol, the wisp is abandoned (no harm - wisps are ephemeral).
Fresh session spawns fresh wisp.
Fresh session creates fresh wisp.
## Handoff (Wisp-Based)
For patrol work, **no handoff is needed**:
- Patrol is idempotent - running it again is harmless
- Wisps are ephemeral - a crashed patrol just disappears
- New session spawns a fresh wisp
- New session creates a fresh wisp
If you have important context to pass along (rare for patrol), use mail:
```bash
@@ -341,4 +341,4 @@ But typically just exit and let the daemon respawn you with fresh context.
State directory: {{ .TownRoot }}/deacon/
Mail identity: deacon/
Session: gt-deacon
Patrol molecule: mol-deacon-patrol (wisp)
Patrol molecule: mol-deacon-patrol (created as wisp)
+1 -1
View File
@@ -208,7 +208,7 @@ These consume minimal context. But complex rebases are different:
1. Read `state.json` for `simple_merges` and `complex_merge`
2. If `complex_merge == true` → hand off immediately
3. If `simple_merges >= 20` → hand off
4. Otherwise → continue patrol, spawn new wisp
4. Otherwise → continue patrol, create new wisp
**Rationale**: A clean rebase is a button-push. A conflict resolution fills context
with diffs, decisions, and debugging. Fresh context handles the next conflict better.
+1 -1
View File
@@ -97,7 +97,7 @@ gt mail inbox
gt mol attach-from-mail <mail-id>
# Step 4: Still nothing? Create patrol wisp
bd wisp mol-witness-patrol --assignee={{ .RigName }}/witness
gt wisp mol-witness-patrol --assignee={{ .RigName }}/witness
```
**Hook → Execute. No exceptions.**