diff --git a/internal/mayor/manager.go b/internal/mayor/manager.go index 896ffcab..9391ab8d 100644 --- a/internal/mayor/manager.go +++ b/internal/mayor/manager.go @@ -68,7 +68,7 @@ func (m *Manager) Start(agentOverride string) error { } } - // Ensure mayor directory exists + // Ensure mayor directory exists (for Claude settings) mayorDir := m.mayorDir() if err := os.MkdirAll(mayorDir, 0755); err != nil { return fmt.Errorf("creating mayor directory: %w", err) @@ -79,18 +79,25 @@ func (m *Manager) Start(agentOverride string) error { return fmt.Errorf("ensuring Claude settings: %w", err) } - // Build startup command first - the startup hook handles 'gt prime' automatically + // Build startup beacon with explicit instructions (matches gt handoff behavior) + // This ensures the agent has clear context immediately, not after nudges arrive + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: "mayor", + Sender: "human", + Topic: "cold-start", + }) + + // Build startup command WITH the beacon prompt - the startup hook handles 'gt prime' automatically // Export GT_ROLE and BD_ACTOR in the command since tmux SetEnvironment only affects new panes - startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", "", agentOverride) + startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", beacon, agentOverride) if err != nil { return fmt.Errorf("building startup command: %w", err) } - // Create session with command directly to avoid send-keys race condition. - // This runs the command as the pane's initial process, avoiding the shell - // readiness timing issues that cause "bad pattern" and command-not-found errors. + // Create session in townRoot (not mayorDir) to match gt handoff behavior + // This ensures Mayor works from the town root where all tools work correctly // See: https://github.com/anthropics/gastown/issues/280 - if err := t.NewSessionWithCommand(sessionID, mayorDir, startupCmd); err != nil { + if err := t.NewSessionWithCommand(sessionID, m.townRoot, startupCmd); err != nil { return fmt.Errorf("creating tmux session: %w", err) } @@ -119,18 +126,8 @@ func (m *Manager) Start(agentOverride string) error { time.Sleep(constants.ShutdownNotifyDelay) - // Inject startup nudge for predecessor discovery via /resume - _ = session.StartupNudge(t, sessionID, session.StartupNudgeConfig{ - Recipient: "mayor", - Sender: "human", - Topic: "cold-start", - }) // Non-fatal - - // GUPP: Gas Town Universal Propulsion Principle - // Send the propulsion nudge to trigger autonomous coordination. - // Wait for beacon to be fully processed (needs to be separate prompt) - time.Sleep(2 * time.Second) - _ = t.NudgeSession(sessionID, session.PropulsionNudgeForRole("mayor", mayorDir)) // Non-fatal + // Startup beacon with instructions is now included in the initial command, + // so no separate nudge needed. The agent starts with full context immediately. return nil } diff --git a/internal/session/startup.go b/internal/session/startup.go index e22096e0..0dfd4724 100644 --- a/internal/session/startup.go +++ b/internal/session/startup.go @@ -65,9 +65,9 @@ func FormatStartupNudge(cfg StartupNudgeConfig) string { beacon := fmt.Sprintf("[GAS TOWN] %s <- %s • %s • %s", cfg.Recipient, cfg.Sender, timestamp, topic) - // For handoff, add explicit instructions so the agent knows what to do + // For handoff and cold-start, add explicit instructions so the agent knows what to do // even if hooks haven't loaded CLAUDE.md yet - if cfg.Topic == "handoff" { + if cfg.Topic == "handoff" || cfg.Topic == "cold-start" { beacon += "\n\nCheck your hook and mail, then act on the hook if present:\n" + "1. `gt hook` - shows hooked work (if any)\n" + "2. `gt mail inbox` - check for messages\n" + diff --git a/internal/session/startup_test.go b/internal/session/startup_test.go index a1e05b7a..4d8ac373 100644 --- a/internal/session/startup_test.go +++ b/internal/session/startup_test.go @@ -41,6 +41,9 @@ func TestFormatStartupNudge(t *testing.T) { "deacon", "<- mayor", "cold-start", + "Check your hook and mail", // cold-start includes explicit instructions (like handoff) + "gt hook", + "gt mail inbox", }, // No wantNot - timestamp contains ":" },