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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" +
|
||||||
|
|||||||
@@ -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 ":"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user