Unify agent startup with Manager pattern
Refactors all agent startup paths (witness, refinery, crew, polecat) to use a consistent Manager interface with Start(), Stop(), IsRunning(), and SessionName() methods. Includes: - Witness manager with GUPP propulsion nudge for startup - Refinery manager for engineer sessions - Crew manager for worker agents - Session/polecat manager updates - claude_settings_check doctor check for settings validation - Settings management consolidated from rig/manager.go - Settings location moved outside source repos to prevent conflicts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,9 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/claude"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
"github.com/steveyegge/gastown/internal/crew"
|
||||
"github.com/steveyegge/gastown/internal/mail"
|
||||
"github.com/steveyegge/gastown/internal/session"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
"github.com/steveyegge/gastown/internal/townlog"
|
||||
@@ -201,7 +198,7 @@ func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the crew worker
|
||||
// Get the crew worker (must exist for refresh)
|
||||
worker, err := crewMgr.Get(name)
|
||||
if err != nil {
|
||||
if err == crew.ErrCrewNotFound {
|
||||
@@ -210,12 +207,6 @@ func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("getting crew worker: %w", err)
|
||||
}
|
||||
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(r.Name, name)
|
||||
|
||||
// Check if session exists
|
||||
hasSession, _ := t.HasSession(sessionID)
|
||||
|
||||
// Create handoff message
|
||||
handoffMsg := crewMessage
|
||||
if handoffMsg == "" {
|
||||
@@ -243,47 +234,14 @@ func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
fmt.Printf("Sent handoff mail to %s/%s\n", r.Name, name)
|
||||
|
||||
// Kill existing session if running
|
||||
if hasSession {
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
return fmt.Errorf("killing old session: %w", err)
|
||||
}
|
||||
fmt.Printf("Killed old session %s\n", sessionID)
|
||||
}
|
||||
|
||||
// Start new session
|
||||
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Wait for shell to be ready
|
||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||
return fmt.Errorf("waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
// Build the startup beacon for predecessor discovery via /resume
|
||||
// Pass it as Claude's initial prompt - processed when Claude is ready
|
||||
address := fmt.Sprintf("%s/crew/%s", r.Name, name)
|
||||
beacon := session.FormatStartupNudge(session.StartupNudgeConfig{
|
||||
Recipient: address,
|
||||
Sender: "human",
|
||||
Topic: "refresh",
|
||||
// Use manager's Start() with refresh options
|
||||
err = crewMgr.Start(name, crew.StartOptions{
|
||||
KillExisting: true, // Kill old session if running
|
||||
Topic: "refresh", // Startup nudge topic
|
||||
Interactive: true, // No --dangerously-skip-permissions
|
||||
})
|
||||
|
||||
// Start claude with environment exports and beacon as initial prompt
|
||||
// Refresh uses regular permissions (no --dangerously-skip-permissions)
|
||||
// SessionStart hook handles context loading (gt prime --hook)
|
||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, beacon)
|
||||
// Remove --dangerously-skip-permissions for refresh (interactive mode)
|
||||
claudeCmd = strings.Replace(claudeCmd, " --dangerously-skip-permissions", "", 1)
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Claude to start (optional, for status feedback)
|
||||
shells := constants.SupportedShells
|
||||
if err := t.WaitForCommand(sessionID, shells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting crew session: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Refreshed crew workspace: %s/%s\n",
|
||||
@@ -385,81 +343,18 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the crew worker, create if not exists (idempotent)
|
||||
worker, err := crewMgr.Get(name)
|
||||
if err == crew.ErrCrewNotFound {
|
||||
fmt.Printf("Creating crew workspace %s in %s...\n", name, r.Name)
|
||||
worker, err = crewMgr.Add(name, false) // No feature branch for crew
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Created crew workspace: %s/%s\n", r.Name, name)
|
||||
} else if err != nil {
|
||||
fmt.Printf("Error getting %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(r.Name, name)
|
||||
|
||||
// Kill existing session if running
|
||||
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
fmt.Printf("Error killing session for %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Killed session %s\n", sessionID)
|
||||
}
|
||||
|
||||
// Start new session
|
||||
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
||||
fmt.Printf("Error creating session for %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// Set environment
|
||||
_ = t.SetEnvironment(sessionID, "GT_ROLE", "crew")
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := getThemeForRig(r.Name)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, r.Name, name, "crew")
|
||||
|
||||
// Wait for shell to be ready
|
||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||
fmt.Printf("Error waiting for shell for %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the startup beacon for predecessor discovery via /resume
|
||||
// Pass it as Claude's initial prompt - processed when Claude is ready
|
||||
address := fmt.Sprintf("%s/crew/%s", r.Name, name)
|
||||
beacon := session.FormatStartupNudge(session.StartupNudgeConfig{
|
||||
Recipient: address,
|
||||
Sender: "human",
|
||||
Topic: "restart",
|
||||
// Use manager's Start() with restart options
|
||||
// Start() will create workspace if needed (idempotent)
|
||||
err = crewMgr.Start(name, crew.StartOptions{
|
||||
KillExisting: true, // Kill old session if running
|
||||
Topic: "restart", // Startup nudge topic
|
||||
})
|
||||
|
||||
// Start claude with environment exports and beacon as initial prompt
|
||||
// SessionStart hook handles context loading (gt prime --hook)
|
||||
// The startup protocol tells agent to check mail/hook, no explicit prompt needed
|
||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, name, r.Path, beacon)
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
fmt.Printf("Error starting claude for %s: %v\n", arg, err)
|
||||
if err != nil {
|
||||
fmt.Printf("Error restarting %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// Wait for Claude to start (optional, for status feedback)
|
||||
shells := constants.SupportedShells
|
||||
if err := t.WaitForCommand(sessionID, shells, constants.ClaudeStartTimeout); err != nil {
|
||||
style.PrintWarning("Timeout waiting for Claude to start for %s: %v", arg, err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Restarted crew workspace: %s/%s\n",
|
||||
style.Bold.Render("✓"), r.Name, name)
|
||||
fmt.Printf("Attach with: %s\n", style.Dim.Render(fmt.Sprintf("gt crew at %s", name)))
|
||||
@@ -519,7 +414,7 @@ func runCrewRestartAll() error {
|
||||
savedRig := crewRig
|
||||
crewRig = agent.Rig
|
||||
|
||||
crewMgr, r, err := getCrewManager(crewRig)
|
||||
crewMgr, _, err := getCrewManager(crewRig)
|
||||
if err != nil {
|
||||
failed++
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||
@@ -528,20 +423,15 @@ func runCrewRestartAll() error {
|
||||
continue
|
||||
}
|
||||
|
||||
worker, err := crewMgr.Get(agent.AgentName)
|
||||
// Use manager's Start() with restart options
|
||||
err = crewMgr.Start(agent.AgentName, crew.StartOptions{
|
||||
KillExisting: true, // Kill old session if running
|
||||
Topic: "restart", // Startup nudge topic
|
||||
})
|
||||
if err != nil {
|
||||
failed++
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||
crewRig = savedRig
|
||||
continue
|
||||
}
|
||||
|
||||
// Restart the session
|
||||
if err := restartCrewSession(r.Name, agent.AgentName, worker.ClonePath); err != nil {
|
||||
failed++
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||
} else {
|
||||
succeeded++
|
||||
fmt.Printf(" %s %s\n", style.SuccessPrefix, agentName)
|
||||
@@ -567,62 +457,6 @@ func runCrewRestartAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// restartCrewSession handles the core restart logic for a single crew session.
|
||||
func restartCrewSession(rigName, crewName, clonePath string) error {
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(rigName, crewName)
|
||||
|
||||
// Kill existing session if running
|
||||
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
return fmt.Errorf("killing old session: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Claude settings exist (crew is interactive role)
|
||||
if err := claude.EnsureSettingsForRole(clonePath, "crew"); err != nil {
|
||||
return fmt.Errorf("ensuring Claude settings: %w", err)
|
||||
}
|
||||
|
||||
// Start new session
|
||||
if err := t.NewSession(sessionID, clonePath); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Apply rig-based theming
|
||||
theme := getThemeForRig(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, crewName, "crew")
|
||||
|
||||
// Wait for shell to be ready
|
||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||
return fmt.Errorf("waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
// Build the startup beacon for predecessor discovery via /resume
|
||||
// Pass it as Claude's initial prompt - processed when Claude is ready
|
||||
address := fmt.Sprintf("%s/crew/%s", rigName, crewName)
|
||||
beacon := session.FormatStartupNudge(session.StartupNudgeConfig{
|
||||
Recipient: address,
|
||||
Sender: "human",
|
||||
Topic: "restart",
|
||||
})
|
||||
|
||||
// Start claude with environment exports and beacon as initial prompt
|
||||
// SessionStart hook handles context loading (gt prime --hook)
|
||||
claudeCmd := config.BuildCrewStartupCommand(rigName, crewName, "", beacon)
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
}
|
||||
|
||||
// Wait for Claude to start (optional, for status feedback)
|
||||
shells := constants.SupportedShells
|
||||
if err := t.WaitForCommand(sessionID, shells, constants.ClaudeStartTimeout); err != nil {
|
||||
// Non-fatal warning
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runCrewStop stops one or more crew workers.
|
||||
// Supports: "name", "rig/name" formats, "rig" (to stop all in rig), or --all.
|
||||
func runCrewStop(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -344,8 +345,10 @@ func ensureRefinerySession(rigName string, r *rig.Rig) (bool, error) {
|
||||
refineryRigDir = r.Path
|
||||
}
|
||||
|
||||
// Ensure Claude settings exist (autonomous role needs mail in SessionStart)
|
||||
if err := claude.EnsureSettingsForRole(refineryRigDir, "refinery"); err != nil {
|
||||
// Ensure Claude settings exist in refinery/ (not refinery/rig/) so we don't
|
||||
// write into the source repo. Claude walks up the tree to find settings.
|
||||
refineryParentDir := filepath.Join(r.Path, "refinery")
|
||||
if err := claude.EnsureSettingsForRole(refineryParentDir, "refinery"); err != nil {
|
||||
return false, fmt.Errorf("ensuring Claude settings: %w", err)
|
||||
}
|
||||
|
||||
@@ -762,25 +765,6 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
crewGit := git.NewGit(r.Path)
|
||||
crewMgr := crew.NewManager(r, crewGit)
|
||||
|
||||
// Check if crew exists, create if not
|
||||
worker, err := crewMgr.Get(name)
|
||||
if err == crew.ErrCrewNotFound {
|
||||
fmt.Printf("Creating crew workspace %s in %s...\n", name, rigName)
|
||||
worker, err = crewMgr.Add(name, false) // No feature branch for crew
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating crew workspace: %w", err)
|
||||
}
|
||||
fmt.Printf("%s Created crew workspace: %s/%s\n",
|
||||
style.Bold.Render("✓"), rigName, name)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("getting crew worker: %w", err)
|
||||
} else {
|
||||
fmt.Printf("Crew workspace %s/%s exists\n", rigName, name)
|
||||
}
|
||||
|
||||
// Ensure crew workspace is on default branch
|
||||
ensureDefaultBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, name), r.Path)
|
||||
|
||||
// Resolve account for Claude config
|
||||
accountsPath := constants.MayorAccountsPath(townRoot)
|
||||
claudeConfigDir, accountHandle, err := config.ResolveAccountConfigDir(accountsPath, startCrewAccount)
|
||||
@@ -791,68 +775,18 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Using account: %s\n", accountHandle)
|
||||
}
|
||||
|
||||
// Check if session exists
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(rigName, name)
|
||||
hasSession, err := t.HasSession(sessionID)
|
||||
// Use manager's Start() method - handles workspace creation, settings, and session
|
||||
err = crewMgr.Start(name, crew.StartOptions{
|
||||
Account: startCrewAccount,
|
||||
ClaudeConfigDir: claudeConfigDir,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking session: %w", err)
|
||||
}
|
||||
|
||||
if hasSession {
|
||||
// Session exists - check if Claude is still running
|
||||
agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, r.Path, startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving agent: %w", err)
|
||||
}
|
||||
if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) {
|
||||
// Claude has exited, restart it with "gt prime" as initial prompt
|
||||
fmt.Printf("Session exists, restarting Claude...\n")
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeys(sessionID, startupCmd); err != nil {
|
||||
return fmt.Errorf("restarting claude: %w", err)
|
||||
}
|
||||
if errors.Is(err, crew.ErrSessionRunning) {
|
||||
fmt.Printf("%s Session already running: %s\n", style.Dim.Render("○"), crewMgr.SessionName(name))
|
||||
} else {
|
||||
fmt.Printf("%s Session already running: %s\n", style.Dim.Render("○"), sessionID)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Create new session
|
||||
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", rigName)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
// Set CLAUDE_CONFIG_DIR for account selection (non-fatal)
|
||||
if claudeConfigDir != "" {
|
||||
_ = t.SetEnvironment(sessionID, "CLAUDE_CONFIG_DIR", claudeConfigDir)
|
||||
}
|
||||
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
// Note: ConfigureGasTownSession includes cycle bindings
|
||||
theme := getThemeForRig(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, name, "crew")
|
||||
|
||||
// Wait for shell to be ready after session creation
|
||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||
return fmt.Errorf("waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
// Start claude with skip permissions and proper env vars for seance
|
||||
// Pass "gt prime" as initial prompt so context is loaded immediately
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building startup command: %w", err)
|
||||
}
|
||||
if err := t.SendKeys(sessionID, startupCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Started crew workspace: %s/%s\n",
|
||||
style.Bold.Render("✓"), rigName, name)
|
||||
}
|
||||
@@ -926,54 +860,14 @@ func startCrewMember(rigName, crewName, townRoot string) error {
|
||||
return fmt.Errorf("rig '%s' not found", rigName)
|
||||
}
|
||||
|
||||
// Create crew manager
|
||||
// Create crew manager and use Start() method
|
||||
crewGit := git.NewGit(r.Path)
|
||||
crewMgr := crew.NewManager(r, crewGit)
|
||||
|
||||
// Check if crew exists, create if not
|
||||
worker, err := crewMgr.Get(crewName)
|
||||
if err == crew.ErrCrewNotFound {
|
||||
worker, err = crewMgr.Add(crewName, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating crew workspace: %w", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("getting crew worker: %w", err)
|
||||
}
|
||||
|
||||
// Ensure crew workspace is on default branch
|
||||
ensureDefaultBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, crewName), r.Path)
|
||||
|
||||
// Create tmux session
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(rigName, crewName)
|
||||
|
||||
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
||||
return fmt.Errorf("creating session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", rigName)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", crewName)
|
||||
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := getThemeForRig(rigName)
|
||||
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, crewName, "crew")
|
||||
|
||||
// Set up C-b n/p keybindings for crew session cycling (non-fatal)
|
||||
_ = t.SetCrewCycleBindings(sessionID)
|
||||
|
||||
// Wait for shell to be ready
|
||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||
return fmt.Errorf("waiting for shell: %w", err)
|
||||
}
|
||||
|
||||
// Start claude with proper env vars for seance
|
||||
// Pass "gt prime" as initial prompt so context is loaded immediately
|
||||
// (SessionStart hook fires, then Claude processes "gt prime" as first user message)
|
||||
claudeCmd := config.BuildCrewStartupCommand(rigName, crewName, r.Path, "gt prime")
|
||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||
return fmt.Errorf("starting claude: %w", err)
|
||||
// Start handles workspace creation, settings, and session all in one
|
||||
err = crewMgr.Start(crewName, crew.StartOptions{})
|
||||
if err != nil && !errors.Is(err, crew.ErrSessionRunning) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -85,16 +85,21 @@ func runUp(cmd *cobra.Command, args []string) error {
|
||||
deaconSession := getDeaconSessionName()
|
||||
mayorSession := getMayorSessionName()
|
||||
|
||||
// 2. Deacon (Claude agent)
|
||||
if err := ensureSession(t, deaconSession, townRoot, "deacon"); err != nil {
|
||||
// 2. Deacon (Claude agent) - runs from townRoot/deacon/
|
||||
deaconDir := filepath.Join(townRoot, "deacon")
|
||||
if err := ensureSession(t, deaconSession, deaconDir, "deacon"); err != nil {
|
||||
printStatus("Deacon", false, err.Error())
|
||||
allOK = false
|
||||
} else {
|
||||
printStatus("Deacon", true, deaconSession)
|
||||
}
|
||||
|
||||
// 3. Mayor (Claude agent)
|
||||
if err := ensureSession(t, mayorSession, townRoot, "mayor"); err != nil {
|
||||
// 3. Mayor (Claude agent) - runs from townRoot/mayor/
|
||||
// IMPORTANT: Both settings.json and CLAUDE.md must be in ~/gt/mayor/, NOT ~/gt/
|
||||
// Files at town root would be inherited by ALL agents via directory traversal,
|
||||
// causing crew/polecat/etc to receive Mayor-specific context.
|
||||
mayorDir := filepath.Join(townRoot, "mayor")
|
||||
if err := ensureSession(t, mayorSession, mayorDir, "mayor"); err != nil {
|
||||
printStatus("Mayor", false, err.Error())
|
||||
allOK = false
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user