fix(mayor): match handoff priming for gt may at startup (hq-osbot)

When starting Mayor via 'gt may at', the session now:
1. Works from townRoot (~/gt) instead of mayorDir (~/gt/mayor)
2. Includes startup beacon with explicit instructions in initial prompt
3. Removes redundant post-start nudges (beacon has instructions)

This matches the 'gt handoff' behavior where the agent immediately
knows to check hook and mail on startup.

Fixes: hq-h3449 (P0 escalation - horrendous starting UX)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
mayor
2026-01-13 03:22:56 -08:00
committed by beads/crew/emma
parent 791b388a93
commit 278b2f2d4d
3 changed files with 21 additions and 21 deletions

View File

@@ -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() mayorDir := m.mayorDir()
if err := os.MkdirAll(mayorDir, 0755); err != nil { if err := os.MkdirAll(mayorDir, 0755); err != nil {
return fmt.Errorf("creating mayor directory: %w", err) 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) 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 // 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 { if err != nil {
return fmt.Errorf("building startup command: %w", err) return fmt.Errorf("building startup command: %w", err)
} }
// Create session with command directly to avoid send-keys race condition. // Create session in townRoot (not mayorDir) to match gt handoff behavior
// This runs the command as the pane's initial process, avoiding the shell // This ensures Mayor works from the town root where all tools work correctly
// readiness timing issues that cause "bad pattern" and command-not-found errors.
// See: https://github.com/anthropics/gastown/issues/280 // 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) return fmt.Errorf("creating tmux session: %w", err)
} }
@@ -119,18 +126,8 @@ func (m *Manager) Start(agentOverride string) error {
time.Sleep(constants.ShutdownNotifyDelay) time.Sleep(constants.ShutdownNotifyDelay)
// Inject startup nudge for predecessor discovery via /resume // Startup beacon with instructions is now included in the initial command,
_ = session.StartupNudge(t, sessionID, session.StartupNudgeConfig{ // so no separate nudge needed. The agent starts with full context immediately.
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
return nil return nil
} }

View File

@@ -65,9 +65,9 @@ func FormatStartupNudge(cfg StartupNudgeConfig) string {
beacon := fmt.Sprintf("[GAS TOWN] %s <- %s • %s • %s", beacon := fmt.Sprintf("[GAS TOWN] %s <- %s • %s • %s",
cfg.Recipient, cfg.Sender, timestamp, topic) 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 // 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" + beacon += "\n\nCheck your hook and mail, then act on the hook if present:\n" +
"1. `gt hook` - shows hooked work (if any)\n" + "1. `gt hook` - shows hooked work (if any)\n" +
"2. `gt mail inbox` - check for messages\n" + "2. `gt mail inbox` - check for messages\n" +

View File

@@ -41,6 +41,9 @@ func TestFormatStartupNudge(t *testing.T) {
"deacon", "deacon",
"<- mayor", "<- mayor",
"cold-start", "cold-start",
"Check your hook and mail", // cold-start includes explicit instructions (like handoff)
"gt hook",
"gt mail inbox",
}, },
// No wantNot - timestamp contains ":" // No wantNot - timestamp contains ":"
}, },