diff --git a/internal/cmd/crew_at.go b/internal/cmd/crew_at.go index a19c4346..04d30773 100644 --- a/internal/cmd/crew_at.go +++ b/internal/cmd/crew_at.go @@ -8,6 +8,7 @@ import ( "github.com/steveyegge/gastown/internal/constants" "github.com/steveyegge/gastown/internal/crew" "github.com/steveyegge/gastown/internal/runtime" + "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/tmux" "github.com/steveyegge/gastown/internal/workspace" @@ -162,11 +163,20 @@ func runCrewAt(cmd *cobra.Command, args []string) error { return fmt.Errorf("getting pane ID: %w", err) } + // Build startup beacon for predecessor discovery via /resume + // Use FormatStartupNudge instead of bare "gt prime" which confuses agents + // The SessionStart hook handles context injection (gt prime --hook) + address := fmt.Sprintf("%s/crew/%s", r.Name, name) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: address, + Sender: "human", + Topic: "start", + }) + // Use respawn-pane to replace shell with runtime directly // This gives cleaner lifecycle: runtime exits → session ends (no intermediate shell) - // Pass "gt prime" as initial prompt if supported // Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes - startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride) + startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, beacon, crewAgentOverride) if err != nil { return fmt.Errorf("building startup command: %w", err) } @@ -198,10 +208,18 @@ func runCrewAt(cmd *cobra.Command, args []string) error { return fmt.Errorf("getting pane ID: %w", err) } + // Build startup beacon for predecessor discovery via /resume + // Use FormatStartupNudge instead of bare "gt prime" which confuses agents + address := fmt.Sprintf("%s/crew/%s", r.Name, name) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: address, + Sender: "human", + Topic: "restart", + }) + // Use respawn-pane to replace shell with runtime directly - // Pass "gt prime" as initial prompt if supported // Export GT_ROLE and BD_ACTOR since tmux SetEnvironment only affects new panes - startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, "gt prime", crewAgentOverride) + startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(r.Name, name, r.Path, beacon, crewAgentOverride) if err != nil { return fmt.Errorf("building startup command: %w", err) } @@ -218,13 +236,19 @@ func runCrewAt(cmd *cobra.Command, args []string) error { // Check if we're already in the target session if isInTmuxSession(sessionID) { // We're in the session at a shell prompt - just start the agent directly - // Pass "gt prime" as initial prompt so it loads context immediately + // Build startup beacon for predecessor discovery via /resume + address := fmt.Sprintf("%s/crew/%s", r.Name, name) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: address, + Sender: "human", + Topic: "start", + }) agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, r.Path, crewAgentOverride) if err != nil { return fmt.Errorf("resolving agent: %w", err) } fmt.Printf("Starting %s in current session...\n", agentCfg.Command) - return execAgent(agentCfg, "gt prime") + return execAgent(agentCfg, beacon) } // If inside tmux (but different session), don't switch - just inform user diff --git a/internal/cmd/handoff.go b/internal/cmd/handoff.go index 971b4b09..7bf7437e 100644 --- a/internal/cmd/handoff.go +++ b/internal/cmd/handoff.go @@ -340,19 +340,29 @@ func buildRestartCommand(sessionName string) (string, error) { return "", err } - // Determine GT_ROLE and BD_ACTOR values for this session - gtRole := sessionToGTRole(sessionName) + // Parse the session name to get the identity (used for GT_ROLE and beacon) + identity, err := session.ParseSessionName(sessionName) + if err != nil { + return "", fmt.Errorf("cannot parse session name %q: %w", sessionName, err) + } + gtRole := identity.GTRole() + + // Build startup beacon for predecessor discovery via /resume + // Use FormatStartupNudge instead of bare "gt prime" which confuses agents + // The SessionStart hook handles context injection (gt prime --hook) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: identity.Address(), + Sender: "self", + Topic: "handoff", + }) // For respawn-pane, we: // 1. cd to the right directory (role's canonical home) // 2. export GT_ROLE and BD_ACTOR so role detection works correctly // 3. export Claude-related env vars (not inherited by fresh shell) - // 4. run claude with "gt prime" as initial prompt (triggers GUPP) + // 4. run claude with the startup beacon (triggers immediate context loading) // Use exec to ensure clean process replacement. - // IMPORTANT: Passing "gt prime" as argument injects it as the first prompt, - // which triggers the agent to execute immediately. Without this, agents - // wait for user input despite all GUPP prompting in hooks. - runtimeCmd := config.GetRuntimeCommandWithPrompt("", "gt prime") + runtimeCmd := config.GetRuntimeCommandWithPrompt("", beacon) // Build environment exports - role vars first, then Claude vars var exports []string diff --git a/internal/cmd/start.go b/internal/cmd/start.go index 9851ef0c..5c1c795a 100644 --- a/internal/cmd/start.go +++ b/internal/cmd/start.go @@ -19,6 +19,7 @@ import ( "github.com/steveyegge/gastown/internal/polecat" "github.com/steveyegge/gastown/internal/refinery" "github.com/steveyegge/gastown/internal/rig" + "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/tmux" "github.com/steveyegge/gastown/internal/witness" @@ -277,7 +278,14 @@ func startConfiguredCrew(t *tmux.Tmux, townRoot string) { if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) { // Claude has exited, restart it fmt.Printf(" %s %s/%s session exists, restarting Claude...\n", style.Dim.Render("○"), r.Name, crewName) - claudeCmd := config.BuildCrewStartupCommand(r.Name, crewName, r.Path, "gt prime") + // Build startup beacon for predecessor discovery via /resume + address := fmt.Sprintf("%s/crew/%s", r.Name, crewName) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: address, + Sender: "human", + Topic: "restart", + }) + claudeCmd := config.BuildCrewStartupCommand(r.Name, crewName, r.Path, beacon) if err := t.SendKeys(sessionID, claudeCmd); err != nil { fmt.Printf(" %s %s/%s restart failed: %v\n", style.Dim.Render("○"), r.Name, crewName, err) } else {