fix: use rig prefixes for agent bead IDs

Align rig-scoped agent beads with route prefixes so crew add/prime/status resolve the same IDs across rigs. Add tests that assert rig-prefixed agent IDs in prime and status.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dan Shapiro
2026-01-01 18:07:46 -08:00
parent b0c7c89be1
commit b91dd43697
5 changed files with 257 additions and 42 deletions
+52 -34
View File
@@ -163,27 +163,42 @@ func runStatus(cmd *cobra.Command, args []string) error {
return fmt.Errorf("discovering rigs: %w", err)
}
// Create beads instance for agent bead lookups (gastown rig holds gt- prefix beads)
gastownBeadsPath := filepath.Join(townRoot, "gastown", "mayor", "rig")
agentBeads := beads.New(gastownBeadsPath)
// Pre-fetch all agent beads in a single query for performance
allAgentBeads, _ := agentBeads.ListAgentBeads()
if allAgentBeads == nil {
allAgentBeads = make(map[string]*beads.Issue)
}
// Pre-fetch all hook beads (referenced in agent beads) in a single query
// Use the HookBead field from the database column, not parsed from description.
var allHookIDs []string
for _, issue := range allAgentBeads {
if issue.HookBead != "" {
allHookIDs = append(allHookIDs, issue.HookBead)
// Pre-fetch agent beads across all rig-specific beads DBs.
allAgentBeads := make(map[string]*beads.Issue)
allHookBeads := make(map[string]*beads.Issue)
for _, r := range rigs {
rigBeadsPath := filepath.Join(r.Path, "mayor", "rig")
rigBeads := beads.New(rigBeadsPath)
rigAgentBeads, _ := rigBeads.ListAgentBeads()
if rigAgentBeads == nil {
continue
}
for id, issue := range rigAgentBeads {
allAgentBeads[id] = issue
}
var hookIDs []string
for _, issue := range rigAgentBeads {
// Use the HookBead field from the database column; fall back for legacy beads.
hookID := issue.HookBead
if hookID == "" {
fields := beads.ParseAgentFields(issue.Description)
if fields != nil {
hookID = fields.HookBead
}
}
if hookID != "" {
hookIDs = append(hookIDs, hookID)
}
}
if len(hookIDs) == 0 {
continue
}
hookBeads, _ := rigBeads.ShowMultiple(hookIDs)
for id, issue := range hookBeads {
allHookBeads[id] = issue
}
}
allHookBeads, _ := agentBeads.ShowMultiple(allHookIDs)
if allHookBeads == nil {
allHookBeads = make(map[string]*beads.Issue)
}
// Create mail router for inbox lookups
@@ -336,7 +351,7 @@ func outputStatusText(status TownStatus) error {
icon = roleIcons[agent.Name]
}
fmt.Printf("%s %s\n", icon, style.Bold.Render(capitalizeFirst(agent.Name)))
renderAgentDetails(agent, " ", nil)
renderAgentDetails(agent, " ", nil, status.Location)
fmt.Println()
}
@@ -369,7 +384,7 @@ func outputStatusText(status TownStatus) error {
if len(witnesses) > 0 {
fmt.Printf("%s %s\n", roleIcons["witness"], style.Bold.Render("Witness"))
for _, agent := range witnesses {
renderAgentDetails(agent, " ", r.Hooks)
renderAgentDetails(agent, " ", r.Hooks, status.Location)
}
fmt.Println()
}
@@ -378,7 +393,7 @@ func outputStatusText(status TownStatus) error {
if len(refineries) > 0 {
fmt.Printf("%s %s\n", roleIcons["refinery"], style.Bold.Render("Refinery"))
for _, agent := range refineries {
renderAgentDetails(agent, " ", r.Hooks)
renderAgentDetails(agent, " ", r.Hooks, status.Location)
}
// MQ summary (shown under refinery)
if r.MQ != nil {
@@ -416,7 +431,7 @@ func outputStatusText(status TownStatus) error {
if len(crews) > 0 {
fmt.Printf("%s %s (%d)\n", roleIcons["crew"], style.Bold.Render("Crew"), len(crews))
for _, agent := range crews {
renderAgentDetails(agent, " ", r.Hooks)
renderAgentDetails(agent, " ", r.Hooks, status.Location)
}
fmt.Println()
}
@@ -425,7 +440,7 @@ func outputStatusText(status TownStatus) error {
if len(polecats) > 0 {
fmt.Printf("%s %s (%d)\n", roleIcons["polecat"], style.Bold.Render("Polecats"), len(polecats))
for _, agent := range polecats {
renderAgentDetails(agent, " ", r.Hooks)
renderAgentDetails(agent, " ", r.Hooks, status.Location)
}
fmt.Println()
}
@@ -440,7 +455,7 @@ func outputStatusText(status TownStatus) error {
}
// renderAgentDetails renders full agent bead details
func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo) {
func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo, townRoot string) {
// Line 1: Agent bead ID + status
statusStr := style.Success.Render("running")
if !agent.Running {
@@ -463,15 +478,16 @@ func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo
agentBeadID = beads.AgentBeadID("", parts[0], "")
} else if len(parts) >= 2 {
rig := parts[0]
prefix := beads.GetPrefixForRig(townRoot, rig)
if parts[1] == "crew" && len(parts) >= 3 {
agentBeadID = beads.CrewBeadID(rig, parts[2])
agentBeadID = beads.CrewBeadIDWithPrefix(prefix, rig, parts[2])
} else if parts[1] == "witness" {
agentBeadID = beads.WitnessBeadID(rig)
agentBeadID = beads.WitnessBeadIDWithPrefix(prefix, rig)
} else if parts[1] == "refinery" {
agentBeadID = beads.RefineryBeadID(rig)
agentBeadID = beads.RefineryBeadIDWithPrefix(prefix, rig)
} else if len(parts) == 2 {
// polecat: rig/name
agentBeadID = beads.PolecatBeadID(rig, parts[1])
agentBeadID = beads.PolecatBeadIDWithPrefix(prefix, rig, parts[1])
}
}
}
@@ -695,6 +711,8 @@ type agentDef struct {
func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string, allAgentBeads map[string]*beads.Issue, allHookBeads map[string]*beads.Issue, mailRouter *mail.Router, skipMail bool) []AgentRuntime {
// Build list of all agents to discover
var defs []agentDef
townRoot := filepath.Dir(r.Path)
prefix := beads.GetPrefixForRig(townRoot, r.Name)
// Witness
if r.HasWitness {
@@ -703,7 +721,7 @@ func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string,
address: r.Name + "/witness",
session: witnessSessionName(r.Name),
role: "witness",
beadID: beads.WitnessBeadID(r.Name),
beadID: beads.WitnessBeadIDWithPrefix(prefix, r.Name),
})
}
@@ -714,7 +732,7 @@ func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string,
address: r.Name + "/refinery",
session: fmt.Sprintf("gt-%s-refinery", r.Name),
role: "refinery",
beadID: beads.RefineryBeadID(r.Name),
beadID: beads.RefineryBeadIDWithPrefix(prefix, r.Name),
})
}
@@ -725,7 +743,7 @@ func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string,
address: r.Name + "/" + name,
session: fmt.Sprintf("gt-%s-%s", r.Name, name),
role: "polecat",
beadID: beads.PolecatBeadID(r.Name, name),
beadID: beads.PolecatBeadIDWithPrefix(prefix, r.Name, name),
})
}
@@ -736,7 +754,7 @@ func discoverRigAgents(allSessions map[string]bool, r *rig.Rig, crews []string,
address: r.Name + "/crew/" + name,
session: crewSessionName(r.Name, name),
role: "crew",
beadID: beads.CrewBeadID(r.Name, name),
beadID: beads.CrewBeadIDWithPrefix(prefix, r.Name, name),
})
}