From 9f9ed1b928c1ebd8bbc508cf289c9031e6de1a13 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 25 Dec 2025 01:21:12 -0800 Subject: [PATCH] Improve autonomous work mode and fix hook storage (gt-1xsah) - gt prime: New AUTONOMOUS WORK MODE prompt with clear DO/DON'T instructions - gt prime: Skip normal startup directive when in autonomous mode - gt prime: Search multiple beads locations for Mayor's beads - gt sling: Use GetRole() for role detection instead of cwd - gt sling: Store hooks in role's home directory, not git root This ensures hooks work correctly regardless of where commands are run from. Mayor's hooks always go to ~/gt/.beads/ even when running from a rig dir. --- internal/cmd/prime.go | 140 +++++++++++++++++++++++++++++------------- internal/cmd/sling.go | 52 ++++++++++++---- 2 files changed, 137 insertions(+), 55 deletions(-) diff --git a/internal/cmd/prime.go b/internal/cmd/prime.go index 10fe82d4..a44e39a4 100644 --- a/internal/cmd/prime.go +++ b/internal/cmd/prime.go @@ -125,7 +125,8 @@ func runPrime(cmd *cobra.Command, args []string) error { outputAttachmentStatus(ctx) // Check for slung work on hook (from gt sling) - checkSlungWork(ctx) + // If found, we're in autonomous mode - skip normal startup directive + hasSlungWork := checkSlungWork(ctx) // Output molecule context if working on a molecule step outputMoleculeContext(ctx) @@ -137,7 +138,10 @@ func runPrime(cmd *cobra.Command, args []string) error { runMailCheckInject(cwd) // Output startup directive for roles that should announce themselves - outputStartupDirective(ctx) + // Skip if in autonomous mode (slung work provides its own directive) + if !hasSlungWork { + outputStartupDirective(ctx) + } return nil } @@ -1273,35 +1277,85 @@ func outputRefineryPatrolContext(ctx RoleContext) { // checkSlungWork checks for slung work on the agent's hook. // If found, displays it prominently and tells the agent to execute it. // The wisp is burned after the agent acknowledges it. -func checkSlungWork(ctx RoleContext) { +// Returns true if slung work was found (caller should skip normal startup directive). +func checkSlungWork(ctx RoleContext) bool { // Determine agent identity for hook lookup agentID := getAgentIdentity(ctx) if agentID == "" { - return + return false } // Get the git clone root (hooks are stored at clone root, not cwd) cloneRoot, err := getGitRoot() if err != nil { // Not in a git repo - can't have hooks - return + return false } sw, err := wisp.ReadHook(cloneRoot, agentID) if err != nil { if errors.Is(err, wisp.ErrNoHook) { // No hook - normal case, nothing to do - return + return false } // Log other errors (permission, corruption) but continue fmt.Printf("%s Warning: error reading hook: %v\n", style.Dim.Render("⚠"), err) - return + return false } - // Found slung work! Display prominently + // Verify bead exists before showing autonomous mode + // Try multiple beads locations: cwd, clone root, and rig's beads dir + var stdout bytes.Buffer + beadExists := false + beadSearchDirs := []string{ctx.WorkDir, cloneRoot} + // For Mayor, also try the gastown rig's beads location + if ctx.Role == RoleMayor { + beadSearchDirs = append(beadSearchDirs, filepath.Join(ctx.TownRoot, "gastown", "mayor", "rig")) + } + for _, dir := range beadSearchDirs { + cmd := exec.Command("bd", "show", sw.BeadID) + cmd.Dir = dir + stdout.Reset() + cmd.Stdout = &stdout + cmd.Stderr = nil + if cmd.Run() == nil { + beadExists = true + break + } + } + + if !beadExists { + fmt.Println() + fmt.Printf("%s\n\n", style.Bold.Render("## 🎯 SLUNG WORK ON HOOK")) + fmt.Printf(" Bead ID: %s\n", style.Bold.Render(sw.BeadID)) + fmt.Printf(" %s Bead %s not found! It may have been deleted.\n", + style.Bold.Render("⚠ WARNING:"), sw.BeadID) + fmt.Println(" The hook will NOT be burned. Investigate this issue.") + fmt.Println() + // Don't burn - leave hook for debugging + return false + } + + // Build the role announcement string + roleAnnounce := buildRoleAnnouncement(ctx) + + // Found slung work! Display AUTONOMOUS MODE prominently fmt.Println() - fmt.Printf("%s\n\n", style.Bold.Render("## 🎯 SLUNG WORK ON HOOK")) - fmt.Printf("Work was slung onto your hook and awaits execution.\n\n") + fmt.Printf("%s\n\n", style.Bold.Render("## 🚨 AUTONOMOUS WORK MODE 🚨")) + fmt.Println("Work is slung on your hook. After announcing your role, begin IMMEDIATELY.") + fmt.Println() + fmt.Println("1. Announce: \"" + roleAnnounce + "\" (ONE line, no elaboration)") + fmt.Printf("2. Then IMMEDIATELY run: `bd show %s`\n", sw.BeadID) + fmt.Println("3. Begin execution - no waiting for user input") + fmt.Println() + fmt.Println("**DO NOT:**") + fmt.Println("- Wait for user response after announcing") + fmt.Println("- Ask clarifying questions") + fmt.Println("- Describe what you're going to do") + fmt.Println() + + // Show the slung work details + fmt.Printf("%s\n\n", style.Bold.Render("## Slung Work")) fmt.Printf(" Bead ID: %s\n", style.Bold.Render(sw.BeadID)) if sw.Subject != "" { fmt.Printf(" Subject: %s\n", sw.Subject) @@ -1309,48 +1363,48 @@ func checkSlungWork(ctx RoleContext) { if sw.Context != "" { fmt.Printf(" Context: %s\n", sw.Context) } - fmt.Printf(" Slung by: %s\n", sw.CreatedBy) - fmt.Printf(" Slung at: %s\n", sw.CreatedAt.Format("2006-01-02 15:04:05")) + fmt.Printf(" Slung by: %s at %s\n", sw.CreatedBy, sw.CreatedAt.Format("2006-01-02 15:04:05")) fmt.Println() - // Show the bead details - verify it exists - fmt.Println("**Bead details:**") - cmd := exec.Command("bd", "show", sw.BeadID) - cmd.Dir = cloneRoot - var stdout bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = nil - beadExists := cmd.Run() == nil - if beadExists { - // Show first 20 lines of bead details - lines := strings.Split(stdout.String(), "\n") - maxLines := 20 - if len(lines) > maxLines { - lines = lines[:maxLines] - lines = append(lines, "...") - } - for _, line := range lines { - fmt.Printf(" %s\n", line) - } - } else { - fmt.Printf(" %s Bead %s not found! It may have been deleted.\n", - style.Bold.Render("⚠ WARNING:"), sw.BeadID) - fmt.Println(" The hook will NOT be burned. Investigate this issue.") - fmt.Println() - // Don't burn - leave hook for debugging - return + // Show bead preview (first 15 lines) + lines := strings.Split(stdout.String(), "\n") + maxLines := 15 + if len(lines) > maxLines { + lines = lines[:maxLines] + lines = append(lines, "...") + } + fmt.Println("**Bead preview:**") + for _, line := range lines { + fmt.Printf(" %s\n", line) } - fmt.Println() - - // The propulsion principle - fmt.Println(style.Bold.Render("→ PROPULSION PRINCIPLE: Work is on your hook. RUN IT.")) - fmt.Println(" Begin working on this bead immediately. No human input needed.") fmt.Println() // Burn the hook now that it's been read and verified if err := wisp.BurnHook(cloneRoot, agentID); err != nil { fmt.Printf("%s Warning: could not burn hook: %v\n", style.Dim.Render("⚠"), err) } + + return true +} + +// buildRoleAnnouncement creates the role announcement string for autonomous mode. +func buildRoleAnnouncement(ctx RoleContext) string { + switch ctx.Role { + case RoleMayor: + return "Mayor, checking in." + case RoleDeacon: + return "Deacon, checking in." + case RoleWitness: + return fmt.Sprintf("%s Witness, checking in.", ctx.Rig) + case RoleRefinery: + return fmt.Sprintf("%s Refinery, checking in.", ctx.Rig) + case RolePolecat: + return fmt.Sprintf("%s Polecat %s, checking in.", ctx.Rig, ctx.Polecat) + case RoleCrew: + return fmt.Sprintf("%s Crew %s, checking in.", ctx.Rig, ctx.Polecat) + default: + return "Agent, checking in." + } } // getGitRoot returns the root of the current git repository. diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index 5ecdb5b1..74a58f89 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -74,6 +74,7 @@ func runSling(cmd *cobra.Command, args []string) error { // Determine target agent (self or specified) var targetAgent string var targetPane string + var hookRoot string // Where to store the hook (role's home) var err error if len(args) > 1 { @@ -82,19 +83,45 @@ func runSling(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("resolving target: %w", err) } - } else { - // Slinging to self - targetAgent, err = detectAgentIdentity() + // For remote targets, use their home directory + // TODO: resolve target's home properly + hookRoot, err = detectCloneRoot() if err != nil { - return fmt.Errorf("detecting agent identity: %w", err) + return fmt.Errorf("detecting clone root: %w", err) + } + } else { + // Slinging to self - use env-aware role detection + roleInfo, err := GetRole() + if err != nil { + return fmt.Errorf("detecting role: %w", err) + } + // Build agent identity from role + switch roleInfo.Role { + case RoleMayor: + targetAgent = "mayor" + case RoleDeacon: + targetAgent = "deacon" + case RoleWitness: + targetAgent = fmt.Sprintf("%s/witness", roleInfo.Rig) + case RoleRefinery: + targetAgent = fmt.Sprintf("%s/refinery", roleInfo.Rig) + case RolePolecat: + targetAgent = fmt.Sprintf("%s/polecats/%s", roleInfo.Rig, roleInfo.Polecat) + case RoleCrew: + targetAgent = fmt.Sprintf("%s/crew/%s", roleInfo.Rig, roleInfo.Polecat) + default: + return fmt.Errorf("cannot determine agent identity (role: %s)", roleInfo.Role) } targetPane = os.Getenv("TMUX_PANE") - } - - // Get clone root for wisp storage - cloneRoot, err := detectCloneRoot() - if err != nil { - return fmt.Errorf("detecting clone root: %w", err) + // Use role's home for hook storage + hookRoot = roleInfo.Home + if hookRoot == "" { + // Fallback to git root if home not determined + hookRoot, err = detectCloneRoot() + if err != nil { + return fmt.Errorf("detecting clone root: %w", err) + } + } } // Create the slung work wisp @@ -105,9 +132,10 @@ func runSling(cmd *cobra.Command, args []string) error { fmt.Printf("%s Slinging %s to %s...\n", style.Bold.Render("🎯"), beadID, targetAgent) if slingDryRun { - fmt.Printf("Would create wisp: %s\n", wisp.HookPath(cloneRoot, targetAgent)) + fmt.Printf("Would create wisp: %s\n", wisp.HookPath(hookRoot, targetAgent)) fmt.Printf(" bead_id: %s\n", beadID) fmt.Printf(" agent: %s\n", targetAgent) + fmt.Printf(" hook_root: %s\n", hookRoot) if slingSubject != "" { fmt.Printf(" subject: %s\n", slingSubject) } @@ -119,7 +147,7 @@ func runSling(cmd *cobra.Command, args []string) error { } // Write the wisp to the hook - if err := wisp.WriteSlungWork(cloneRoot, targetAgent, sw); err != nil { + if err := wisp.WriteSlungWork(hookRoot, targetAgent, sw); err != nil { return fmt.Errorf("writing wisp: %w", err) }