feat(spawn): Replace tmux injection with persistent mail-based work assignment (ga-yp3)

- gt spawn now sends work assignment to polecat inbox instead of tmux injection
- Add --identity flag to gt mail inbox and gt mail check
- Add --force flag to gt spawn to override existing unread mail
- Update polecat template with startup protocol for reading inbox
- Fix pre-existing lint issue in start.go

The new flow is more reliable:
1. Spawn sends work assignment mail to polecat inbox
2. Polecat starts and runs gt prime
3. gt prime automatically runs gt mail check --inject
4. Polecat reads work assignment from inbox

Benefits:
- Persistence across session restarts
- No racing against Claude initialization
- Audit trail in beads
- Edge case handling for existing unread mail

🤖 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-19 22:07:38 -08:00
parent c7e83b1619
commit 5622abbdfe
6 changed files with 129 additions and 28 deletions

View File

@@ -7,6 +7,7 @@
{"id":"gt-0pc","title":"Document Overseer role (human operator)","description":"Document the Overseer role in Gas Town architecture.\n\n## The Overseer\n\nThe **Overseer** is the human operator of Gas Town. Not an agent - a person.\n\n## Responsibilities\n\n| Area | Overseer Does | Mayor/Agents Do |\n|------|---------------|-----------------|\n| Strategy | Define project goals | Execute toward goals |\n| Priorities | Set priority order | Work in priority order |\n| Escalations | Final decision on stuck work | Escalate to Overseer |\n| Resources | Provision machines | Use allocated resources |\n| Quality | Review \u0026 approve swarm output | Produce output |\n| Operations | Run gt commands, monitor dashboards | Do the work |\n\n## Key Interactions\n\n### Overseer → Mayor\n- Start/stop Mayor sessions\n- Direct Mayor via conversation\n- Review Mayor recommendations\n- Approve cross-rig decisions\n\n### Mayor → Overseer (Escalations)\n- Stuck workers after retries\n- Resource decisions (add machines, polecats)\n- Ambiguous requirements\n- Architecture decisions\n\n## Operating Cadence\n\nTypical Overseer workflow:\n1. Morning: Check status, review overnight work\n2. During day: Monitor, respond to escalations, adjust priorities\n3. End of day: Review progress, plan next batch\n\n## Commands for Overseers\n\n```bash\ngt status # Quick health check\ngt doctor # Detailed diagnostics \ngt doctor --fix # Auto-repair issues\ngt inbox # Messages from agents\ngt stop --all # Emergency halt\n```\n\n## Documentation Updates\n\nAdd to docs/architecture.md:\n- Overseer section under Agent Roles\n- Clarify Mayor reports to Overseer\n- Add Overseer to workflow diagrams","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T23:18:03.177633-08:00","updated_at":"2025-12-15T23:22:51.477786-08:00","closed_at":"2025-12-15T23:22:51.477786-08:00"}
{"id":"gt-0pl","title":"Polecat CLAUDE.md: configure auto-approve for bd and gt commands","description":"Polecats get stuck waiting for bash command approval when running\nbd and gt commands. Need to configure Claude Code to auto-approve these.\n\nOptions:\n1. Add allowedTools to polecat CLAUDE.md\n2. Configure .claude/settings.json in polecat directory\n3. Use --dangerously-skip-permissions flag (not recommended)\n\nShould auto-approve:\n- bd (beads commands)\n- gt (gastown commands)\n- go build/test\n- git status/add/commit/push\n\nShould still require approval:\n- rm -rf\n- Arbitrary commands outside project\n\nRelated to polecat prompting (gt-e1y, gt-sd6).","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T14:10:27.611612-08:00","updated_at":"2025-12-17T14:22:00.715979-08:00","closed_at":"2025-12-17T14:22:00.715979-08:00"}
{"id":"gt-0qki","title":"Refinery-Witness communication protocol","description":"Define mail protocol between Refinery and Witness:\n\nFROM Witness → Refinery:\n- 'Polecat ready': polecat X completed work, ready for merge\n- 'Rework complete': polecat Y finished requested rework\n\nFROM Refinery → Witness:\n- 'Merge success': polecat X merged, can be cleaned up\n- 'Merge failed': polecat X needs rework on \u003creason\u003e\n- 'Rework request': please have a polecat rebase X on current main\n\nImplement as structured mail with parseable format.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-19T18:09:27.451344-08:00","updated_at":"2025-12-19T18:09:27.451344-08:00","dependencies":[{"issue_id":"gt-0qki","depends_on_id":"gt-ktal","type":"blocks","created_at":"2025-12-19T18:09:39.58445-08:00","created_by":"daemon"}]}
{"id":"gt-14hd","title":"Work on ga-xxp: Define mol-polecat-work standard molecule...","description":"Work on ga-xxp: Define mol-polecat-work standard molecule. See bd show ga-xxp for full details.","status":"in_progress","priority":2,"issue_type":"task","assignee":"gastown/polecat-01","created_at":"2025-12-19T21:49:14.070072-08:00","updated_at":"2025-12-19T21:49:14.140363-08:00"}
{"id":"gt-17r","title":"Doctor check: Zombie session cleanup","description":"Detect and clean up zombie tmux sessions via gt doctor.\n\n## Problem\n\nZombie sessions occur when:\n- Agent crashes without cleanup\n- gt kill fails mid-operation\n- System restart leaves orphan sessions\n- Session naming collision\n\n## Checks\n\n### ZombieSessionCheck\n- List all tmux sessions matching gt-* pattern\n- Cross-reference with known polecats\n- Flag sessions with no corresponding polecat state\n- Flag sessions for removed polecats\n- Check session age vs polecat creation time\n\n### Detection Criteria\n- Session exists but polecat directory doesn't\n- Session name doesn't match any registered polecat\n- Polecat state=idle but session running\n- Multiple sessions for same polecat\n\n## Output\n\n```\n[WARN] Zombie tmux sessions detected:\n - gt-wyvern-OldPolecat (polecat removed)\n - gt-beads-Unknown (no matching polecat)\n - gt-wyvern-Toast (duplicate session)\n\n Run 'gt doctor --fix' to clean up\n```\n\n## Auto-Fix (--fix flag)\n\n- Kill orphan tmux sessions\n- Update polecat state to match reality\n- Log all cleanup actions\n\n## Safety\n\n- Never kill sessions where polecat state=working\n- Prompt before killing if --fix used without --force\n- Create audit log of killed sessions","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T23:18:01.446702-08:00","updated_at":"2025-12-15T23:18:39.517644-08:00","dependencies":[{"issue_id":"gt-17r","depends_on_id":"gt-f9x.4","type":"blocks","created_at":"2025-12-15T23:19:05.66301-08:00","created_by":"daemon"},{"issue_id":"gt-17r","depends_on_id":"gt-7ik","type":"blocks","created_at":"2025-12-17T15:44:41.945064-08:00","created_by":"daemon"}]}
{"id":"gt-17zr","title":"gt refinery start: doesn't actually start a session","description":"## Problem\n\n`gt refinery start gastown` reports success but doesn't start a tmux session.\n\n## Evidence\n\n```\n$ gt refinery start gastown\nStarting refinery for gastown...\n✓ Refinery started for gastown\n\n$ tmux list-sessions | grep refinery\n(nothing)\n\n$ gt refinery status gastown\nState: ○ stopped\n```\n\n## Expected\n\nShould start a tmux session (e.g., gt-gastown-refinery) with Claude processing the merge queue.\n\n## Related\n\n- gt-kcee: Witness commands also need implementation\n- The refinery 'start' may just be updating state.json without spawning a session","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-18T21:58:57.188389-08:00","updated_at":"2025-12-19T01:33:49.858934-08:00","closed_at":"2025-12-19T01:33:49.858934-08:00"}
{"id":"gt-1cuq","title":"Merge: gt-svi.1","description":"type: merge-request\nbranch: polecat/Max\ntarget: main\nsource_issue: gt-svi.1\nrig: gastown","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-18T20:15:31.738938-08:00","updated_at":"2025-12-18T20:15:49.759778-08:00","closed_at":"2025-12-18T20:15:49.759778-08:00"}
@@ -181,6 +182,7 @@
{"id":"gt-ebl","title":"CLI: names commands for polecat naming pool","description":"Polecat naming pool for auto-generated names.\n\n## Commands\n\n### gt names generate\n```\ngt names generate [--count N]\n```\nGenerate N names from pool.\n\n### gt names add\n```\ngt names add \u003cname\u003e...\n```\nAdd custom names to pool.\n\n### gt names list\n```\ngt names list\n```\nShow available and used names.\n\n### gt names reset\n```\ngt names reset [--keep-used]\n```\nReset pool to defaults.\n\n## Config File\n\u003crig\u003e/town/naming.json:\n```json\n{\n \"enabled\": true,\n \"auto_refill\": true,\n \"refill_threshold\": 5,\n \"pool\": {\n \"available\": [\"Toast\", \"Nux\", \"Capable\", ...],\n \"used\": [\"Alice\", \"Bob\"]\n }\n}\n```\n\n## Default Pool\nMad Max themed: Toast, Nux, Capable, Furiosa, Immortan, etc.\n\n## Integration\ngt polecat add calls naming pool if no name given:\n```go\nif name == \"\" {\n name, err = naming.Generate(rigPath)\n}\n```\n\n## New Package\ninternal/naming/\n├── pool.go # Pool management\n└── defaults.go # Default name lists\n\n## Acceptance Criteria\n- [ ] Auto-generate names on polecat add\n- [ ] Track used vs available\n- [ ] Auto-refill when low\n- [ ] Custom names addable","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-16T14:48:33.592129-08:00","updated_at":"2025-12-16T16:07:13.882465-08:00"}
{"id":"gt-egu","title":"gt refinery attach: Attach to refinery session","description":"Add 'gt refinery attach [rig]' command to attach to refinery tmux session.\n\nMirrors 'gt mayor attach' pattern. If rig not specified, use current rig context.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T21:47:19.164342-08:00","updated_at":"2025-12-17T22:28:45.661097-08:00","closed_at":"2025-12-17T22:28:45.661097-08:00","dependencies":[{"issue_id":"gt-egu","depends_on_id":"gt-hw6","type":"blocks","created_at":"2025-12-17T22:22:47.578871-08:00","created_by":"daemon"}]}
{"id":"gt-eqys","title":"gt spawn: pasted work assignment needs manual Enter to start","description":"## Problem\n\nAfter `gt spawn` pastes the work assignment into Claude, the session waits for Enter.\n\n## Current Behavior\n\n1. `gt spawn gastown/Rictus --issue gt-xxx`\n2. Session starts, work is pasted\n3. Claude shows 'Pasted text #1 +53 lines' but doesn't start\n4. Must manually send Enter or attach and press Enter\n\n## Expected\n\nThe spawn should send Enter after pasting to kick off the work.\n\n## Related\n\nSame debounce issue as tmux notifications (gt-vnp9).","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-18T21:54:14.111101-08:00","updated_at":"2025-12-19T12:01:32.74364-08:00","closed_at":"2025-12-19T12:01:32.74364-08:00"}
{"id":"gt-er0u","title":"Work on ga-yp3: Polecat inbox system for reliable work as...","description":"Work on ga-yp3: Polecat inbox system for reliable work assignment. This is P1 priority. See bd show ga-yp3 for full design spec.","status":"in_progress","priority":2,"issue_type":"task","assignee":"gastown/polecat-02","created_at":"2025-12-19T21:57:34.473056-08:00","updated_at":"2025-12-19T21:57:34.540384-08:00"}
{"id":"gt-eu9","title":"Witness session cycling and handoff","description":"Add session cycling and handoff protocol to Witness CLAUDE.md template.\n\n## Session Cycling Protocol\n\n```markdown\n## Session Cycling\n\nYour context will fill over long swarms. Proactively cycle when:\n- Running for many hours\n- Losing track of which workers you've checked\n- Responses getting slower\n- About to start complex operation\n\n### Handoff Protocol\n\n1. **Capture current state**:\n```bash\ntown list . # Worker states\ntown all beads # Pending verifications \ntown inbox # Unprocessed messages\n```\n\n2. **Compose handoff note**:\n```\n[HANDOFF_TYPE]: witness_cycle\n[TIMESTAMP]: \u003cnow\u003e\n[RIG]: \u003crig\u003e\n\n## Active Workers\n\u003clist workers and status\u003e\n\n## Pending Verifications\n\u003cworkers signaled done but not verified\u003e\n\n## Recent Actions\n\u003clast 3-5 actions\u003e\n\n## Warnings/Notes\n\u003canything next session should know\u003e\n\n## Next Steps\n\u003cwhat should happen next\u003e\n```\n\n3. **Send handoff**:\n```bash\ntown mail send \u003crig\u003e/witness -s \"Session Handoff\" -m \"\u003cnote\u003e\"\n```\n\n4. **Exit cleanly**: End session, daemon spawns fresh one.\n\n### On Fresh Session Start\n\n1. Check for handoff: `town inbox | grep \"Session Handoff\"`\n2. If found, read it and resume from handoff state\n3. If not found, do full status check\n```\n\n## Implementation\n\nAdd to WITNESS_CLAUDE.md template.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T19:48:55.484911-08:00","updated_at":"2025-12-15T20:47:30.768506-08:00","dependencies":[{"issue_id":"gt-eu9","depends_on_id":"gt-82y","type":"blocks","created_at":"2025-12-15T19:49:05.846443-08:00","created_by":"daemon"}]}
{"id":"gt-f8v","title":"Witness pre-kill verification protocol","description":"Add pre-kill verification protocol to Witness CLAUDE.md template.\n\n## Protocol for Witness Prompting\n\n```markdown\n## Pre-Kill Verification Protocol\n\nBefore killing any worker session, verify workspace is clean.\n\n### Verification Steps\n\nWhen a worker signals done:\n\n1. **Capture worker state**:\n```bash\ntown capture \u003cpolecat\u003e \"git status \u0026\u0026 git stash list \u0026\u0026 bd sync --status\"\n```\n\n2. **Assess the output** (use your judgment):\n- Is working tree clean?\n- Is stash list empty?\n- Is beads synced?\n\n3. **Decision**:\n- **CLEAN**: Proceed to kill session\n- **DIRTY**: Send nudge with specific issues\n\n### Nudge Templates\n\n**Uncommitted Changes**:\n```\ntown inject \u003cpolecat\u003e \"WITNESS CHECK: Uncommitted changes found. Please commit or discard: \u003cfiles\u003e. Signal done when clean.\"\n```\n\n**Beads Not Synced**:\n```\ntown inject \u003cpolecat\u003e \"WITNESS CHECK: Beads not synced. Run 'bd sync' then commit. Signal done when complete.\"\n```\n\n### Kill Sequence\n\nOnly after verification passes:\n```bash\ntown kill \u003cpolecat\u003e\ntown sleep \u003cpolecat\u003e\n```\n\n### Escalation\n\nIf worker fails verification 3+ times:\n```bash\ntown mail send mayor/ -s \"Escalation: \u003cpolecat\u003e stuck\" -m \"Cannot complete cleanup after 3 attempts. Issues: \u003clist\u003e.\"\n```\n```\n\n## Implementation\n\nAdd to WITNESS_CLAUDE.md template.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T19:48:54.065679-08:00","updated_at":"2025-12-15T20:47:30.415244-08:00","dependencies":[{"issue_id":"gt-f8v","depends_on_id":"gt-82y","type":"blocks","created_at":"2025-12-15T19:49:05.763378-08:00","created_by":"daemon"}]}
{"id":"gt-f9x","title":"Town \u0026 Rig Management: install, doctor, federation","description":"Reify the Gas Town installation as a first-class concept.\n\n## Goals\n- Installable: gt install [path] creates complete installation\n- Diagnosable: gt doctor checks and fixes issues\n- Federable: Clone town to VMs with central control\n\n## Architecture Reference\n\nSee docs/architecture.md for full design, especially:\n- Directory structure (Town Level / Rig Level sections)\n- Configuration (town.json, rigs.json schemas)\n- Key design decisions (visible config dir, decentralized agents)","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-15T16:36:37.344283-08:00","updated_at":"2025-12-15T21:15:13.120038-08:00","dependencies":[{"issue_id":"gt-f9x","depends_on_id":"gt-u1j.1","type":"blocks","created_at":"2025-12-15T16:37:32.3363-08:00","created_by":"daemon"}]}

View File

@@ -26,8 +26,10 @@ var (
mailInboxJSON bool
mailReadJSON bool
mailInboxUnread bool
mailInboxIdentity string
mailCheckInject bool
mailCheckJSON bool
mailCheckIdentity string
mailThreadJSON bool
mailReplySubject string
mailReplyMessage string
@@ -78,11 +80,13 @@ var mailInboxCmd = &cobra.Command{
Long: `Check messages in an inbox.
If no address is specified, shows the current context's inbox.
Use --identity for polecats to explicitly specify their identity.
Examples:
gt mail inbox # Current context
gt mail inbox # Current context (auto-detected)
gt mail inbox mayor/ # Mayor's inbox
gt mail inbox gastown/Toast # Polecat's inbox`,
gt mail inbox gastown/Toast # Polecat's inbox
gt mail inbox --identity gastown/Toast # Explicit polecat identity`,
Args: cobra.MaximumNArgs(1),
RunE: runMailInbox,
}
@@ -120,9 +124,12 @@ Exit codes (--inject mode):
0 - Always (hooks should never block)
Output: system-reminder if mail exists, silent if no mail
Use --identity for polecats to explicitly specify their identity.
Examples:
gt mail check # Simple check
gt mail check --inject # For hooks`,
gt mail check # Simple check (auto-detect identity)
gt mail check --inject # For hooks
gt mail check --identity gastown/Toast # Explicit polecat identity`,
RunE: runMailCheck,
}
@@ -169,6 +176,7 @@ func init() {
// Inbox flags
mailInboxCmd.Flags().BoolVar(&mailInboxJSON, "json", false, "Output as JSON")
mailInboxCmd.Flags().BoolVarP(&mailInboxUnread, "unread", "u", false, "Show only unread messages")
mailInboxCmd.Flags().StringVar(&mailInboxIdentity, "identity", "", "Explicit identity for inbox (e.g., gastown/Toast)")
// Read flags
mailReadCmd.Flags().BoolVar(&mailReadJSON, "json", false, "Output as JSON")
@@ -176,6 +184,7 @@ func init() {
// Check flags
mailCheckCmd.Flags().BoolVar(&mailCheckInject, "inject", false, "Output format for Claude Code hooks")
mailCheckCmd.Flags().BoolVar(&mailCheckJSON, "json", false, "Output as JSON")
mailCheckCmd.Flags().StringVar(&mailCheckIdentity, "identity", "", "Explicit identity for inbox (e.g., gastown/Toast)")
// Thread flags
mailThreadCmd.Flags().BoolVar(&mailThreadJSON, "json", false, "Output as JSON")
@@ -270,9 +279,11 @@ func runMailInbox(cmd *cobra.Command, args []string) error {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Determine which inbox to check
// Determine which inbox to check (priority: --identity flag, positional arg, auto-detect)
address := ""
if len(args) > 0 {
if mailInboxIdentity != "" {
address = mailInboxIdentity
} else if len(args) > 0 {
address = args[0]
} else {
address = detectSender()
@@ -519,8 +530,13 @@ func runMailCheck(cmd *cobra.Command, args []string) error {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Determine which inbox
address := detectSender()
// Determine which inbox (priority: --identity flag, auto-detect)
address := ""
if mailCheckIdentity != "" {
address = mailCheckIdentity
} else {
address = detectSender()
}
// Get mailbox
router := mail.NewRouter(workDir)

View File

@@ -284,12 +284,14 @@ func outputPolecatContext(ctx RoleContext) {
fmt.Printf("%s\n\n", style.Bold.Render("# Polecat Context"))
fmt.Printf("You are polecat **%s** in rig: %s\n\n",
style.Bold.Render(ctx.Polecat), style.Bold.Render(ctx.Rig))
fmt.Println("## Responsibilities")
fmt.Println("- Work on assigned issues")
fmt.Println("- Commit work to your branch")
fmt.Println("- Signal completion for merge queue")
fmt.Println("## Startup Protocol")
fmt.Println("1. Run `gt prime` - loads context and checks mail automatically")
fmt.Println("2. Check inbox - if mail shown, read with `gt mail read <id>`")
fmt.Println("3. Look for '📋 Work Assignment' messages for your task")
fmt.Println("4. If no mail, check `bd list --status=in_progress` for existing work")
fmt.Println()
fmt.Println("## Key Commands")
fmt.Println("- `gt mail inbox` - Check your inbox for work assignments")
fmt.Println("- `bd show <issue>` - View your assigned issue")
fmt.Println("- `bd close <issue>` - Mark issue complete")
fmt.Println("- `gt done` - Signal work ready for merge")

View File

@@ -13,6 +13,7 @@ import (
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/polecat"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/session"
@@ -30,6 +31,7 @@ var (
spawnPolecat string
spawnRig string
spawnMolecule string
spawnForce bool
)
var spawnCmd = &cobra.Command{
@@ -68,6 +70,7 @@ func init() {
spawnCmd.Flags().StringVar(&spawnPolecat, "polecat", "", "Polecat name (alternative to positional arg)")
spawnCmd.Flags().StringVar(&spawnRig, "rig", "", "Rig name (defaults to current directory's rig)")
spawnCmd.Flags().StringVar(&spawnMolecule, "molecule", "", "Molecule ID to instantiate on the issue")
spawnCmd.Flags().BoolVar(&spawnForce, "force", false, "Force spawn even if polecat has unread mail")
rootCmd.AddCommand(spawnCmd)
}
@@ -179,6 +182,21 @@ func runSpawn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("polecat '%s' is already working on %s", polecatName, pc.Issue)
}
// Check for unread mail in polecat's inbox (indicates existing unstarted work)
polecatAddress := fmt.Sprintf("%s/%s", rigName, polecatName)
router := mail.NewRouter(r.Path)
mailbox, err := router.GetMailbox(polecatAddress)
if err == nil {
_, unread, _ := mailbox.Count()
if unread > 0 && !spawnForce {
return fmt.Errorf("polecat '%s' has %d unread message(s) in inbox (possible existing work assignment)\nUse --force to override, or let the polecat process its inbox first",
polecatName, unread)
} else if unread > 0 {
fmt.Printf("%s Polecat has %d unread message(s), proceeding with --force\n",
style.Dim.Render("Warning:"), unread)
}
}
// Beads operations use mayor/rig directory (rig-level beads)
beadsPath := filepath.Join(r.Path, "mayor", "rig")
@@ -285,6 +303,16 @@ func runSpawn(cmd *cobra.Command, args []string) error {
return nil
}
// Send work assignment mail to polecat inbox (before starting session)
// polecatAddress and router already defined above when checking for unread mail
workMsg := buildWorkAssignmentMail(issue, spawnMessage, polecatAddress)
fmt.Printf("Sending work assignment to %s inbox...\n", polecatAddress)
if err := router.Send(workMsg); err != nil {
return fmt.Errorf("sending work assignment: %w", err)
}
fmt.Printf("%s Work assignment sent\n", style.Bold.Render("✓"))
// Start session
t := tmux.NewTmux()
sessMgr := session.NewManager(t, r)
@@ -292,29 +320,23 @@ func runSpawn(cmd *cobra.Command, args []string) error {
// Check if already running
running, _ := sessMgr.IsRunning(polecatName)
if running {
// Just inject the context
fmt.Printf("Session already running, injecting context...\n")
// Session already running - send notification to check inbox
fmt.Printf("Session already running, notifying to check inbox...\n")
time.Sleep(500 * time.Millisecond) // Brief pause for notification
} else {
// Start new session
// Start new session - polecat will check inbox via gt prime startup hook
fmt.Printf("Starting session for %s/%s...\n", rigName, polecatName)
if err := sessMgr.Start(polecatName, session.StartOptions{}); err != nil {
return fmt.Errorf("starting session: %w", err)
}
// Wait for Claude to fully initialize (needs 4-5s for prompt)
fmt.Printf("Waiting for Claude to initialize...\n")
time.Sleep(5 * time.Second)
}
// Inject initial context
context := buildSpawnContext(issue, spawnMessage)
fmt.Printf("Injecting work assignment...\n")
if err := sessMgr.Inject(polecatName, context); err != nil {
return fmt.Errorf("injecting context: %w", err)
// Wait briefly for session to stabilize
time.Sleep(1 * time.Second)
}
fmt.Printf("%s Session started. Attach with: %s\n",
style.Bold.Render("✓"),
style.Dim.Render(fmt.Sprintf("gt session at %s/%s", rigName, polecatName)))
fmt.Printf(" %s\n", style.Dim.Render("Polecat will read work assignment from inbox on startup"))
return nil
}
@@ -450,6 +472,7 @@ func syncBeads(workDir string, fromMain bool) error {
}
// buildSpawnContext creates the initial context message for the polecat.
// Deprecated: Use buildWorkAssignmentMail instead for mail-based work assignment.
func buildSpawnContext(issue *BeadsIssue, message string) string {
var sb strings.Builder
@@ -478,3 +501,48 @@ func buildSpawnContext(issue *BeadsIssue, message string) string {
return sb.String()
}
// buildWorkAssignmentMail creates a work assignment mail message for a polecat.
// This replaces tmux-based context injection with persistent mailbox delivery.
func buildWorkAssignmentMail(issue *BeadsIssue, message, polecatAddress string) *mail.Message {
var subject string
var body strings.Builder
if issue != nil {
subject = fmt.Sprintf("📋 Work Assignment: %s", issue.Title)
body.WriteString(fmt.Sprintf("Issue: %s\n", issue.ID))
body.WriteString(fmt.Sprintf("Title: %s\n", issue.Title))
body.WriteString(fmt.Sprintf("Priority: P%d\n", issue.Priority))
body.WriteString(fmt.Sprintf("Type: %s\n", issue.Type))
if issue.Description != "" {
body.WriteString(fmt.Sprintf("\nDescription:\n%s\n", issue.Description))
}
} else if message != "" {
// Truncate for subject if too long
titleText := message
if len(titleText) > 50 {
titleText = titleText[:47] + "..."
}
subject = fmt.Sprintf("📋 Work Assignment: %s", titleText)
body.WriteString(fmt.Sprintf("Task: %s\n", message))
}
body.WriteString("\n## Workflow\n")
body.WriteString("1. Run `gt prime` to load polecat context\n")
body.WriteString("2. Run `bd sync --from-main` to get fresh beads\n")
body.WriteString("3. Work on your task, commit changes\n")
body.WriteString("4. Run `bd close <issue-id>` when done\n")
body.WriteString("5. Run `bd sync` to push beads changes\n")
body.WriteString("6. Push code: `git push origin HEAD`\n")
body.WriteString("7. Signal DONE with summary\n")
return &mail.Message{
From: "mayor/",
To: polecatAddress,
Subject: subject,
Body: body.String(),
Priority: mail.PriorityHigh,
Type: mail.TypeTask,
}
}

View File

@@ -203,7 +203,7 @@ func runGracefulShutdown(t *tmux.Tmux) error {
}
func runImmediateShutdown(t *tmux.Tmux) error {
fmt.Println("Shutting down Gas Town...\n")
fmt.Println("Shutting down Gas Town...")
stopped := 0

View File

@@ -61,9 +61,22 @@ Agent-friendly UX is critical. Your guesses reveal what's intuitive.
- `gt done` - Signal work ready for merge queue
- `bd sync` - Sync beads changes
## Startup Protocol
When your session starts, follow this protocol:
1. **Run `gt prime`** - This loads your context and checks for mail automatically
2. **Check your inbox** - If `gt prime` shows mail, read it with `gt mail read <id>`
3. **Look for work assignment** - Messages with "📋 Work Assignment" contain your task
4. **If no mail** - Check `bd list --status=in_progress` for existing assignments
5. **Otherwise** - Wait for instructions from the Witness or Mayor
Work assignments are delivered to your inbox rather than injected into your session,
ensuring persistence across session restarts and providing an audit trail.
## Work Protocol
1. **Start**: Check mail for assignment, or `bd show <assigned-issue>`
1. **Start**: Read your work assignment from mail, or `bd show <assigned-issue>`
2. **Work**: Implement the solution in your clone
3. **Commit**: Regular commits with clear messages
4. **Test**: Verify your changes work