feat: Standardize agent bead naming to prefix-rig-role-name (gt-zvte2)
Implements canonical naming convention for agent bead IDs: - Town-level: gt-mayor, gt-deacon (unchanged) - Rig-level: gt-<rig>-witness, gt-<rig>-refinery (was gt-witness-<rig>) - Named: gt-<rig>-crew-<name>, gt-<rig>-polecat-<name> (was gt-crew-<rig>-<name>) Changes: - Added AgentBeadID helper functions to internal/beads/beads.go - Updated all ID generation call sites to use helpers - Fixed session parsing in theme.go, statusline.go, agents.go - Updated doctor check and fix to use canonical format - Updated tests for new format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -143,7 +143,7 @@ func categorizeSession(name string) *AgentSession {
|
||||
return session
|
||||
}
|
||||
|
||||
// Witness sessions use different format: gt-witness-<rig>
|
||||
// Witness sessions: legacy format gt-witness-<rig> (fallback)
|
||||
if strings.HasPrefix(suffix, "witness-") {
|
||||
session.Type = AgentWitness
|
||||
session.Rig = strings.TrimPrefix(suffix, "witness-")
|
||||
|
||||
@@ -81,7 +81,7 @@ func runCrewAdd(cmd *cobra.Command, args []string) error {
|
||||
// Create agent bead for the crew worker
|
||||
rigBeadsPath := filepath.Join(r.Path, "mayor", "rig")
|
||||
bd := beads.New(rigBeadsPath)
|
||||
crewID := fmt.Sprintf("gt-crew-%s-%s", rigName, name)
|
||||
crewID := beads.CrewBeadID(rigName, name)
|
||||
if _, err := bd.Show(crewID); err != nil {
|
||||
// Agent bead doesn't exist, create it
|
||||
fields := &beads.AgentFields{
|
||||
|
||||
@@ -17,12 +17,14 @@ import (
|
||||
// Note: Agent field parsing is now in internal/beads/fields.go (AgentFields, ParseAgentFieldsFromDescription)
|
||||
|
||||
// buildAgentBeadID constructs the agent bead ID from an agent identity.
|
||||
// Uses canonical naming: prefix-rig-role-name
|
||||
// Examples:
|
||||
// - "mayor" -> "gt-mayor"
|
||||
// - "deacon" -> "gt-deacon"
|
||||
// - "gastown/witness" -> "gt-witness-gastown"
|
||||
// - "gastown/refinery" -> "gt-refinery-gastown"
|
||||
// - "gastown/nux" (polecat) -> "gt-polecat-gastown-nux"
|
||||
// - "gastown/witness" -> "gt-gastown-witness"
|
||||
// - "gastown/refinery" -> "gt-gastown-refinery"
|
||||
// - "gastown/nux" (polecat) -> "gt-gastown-polecat-nux"
|
||||
// - "gastown/crew/max" -> "gt-gastown-crew-max"
|
||||
//
|
||||
// If role is unknown, it tries to infer from the identity string.
|
||||
func buildAgentBeadID(identity string, role Role) string {
|
||||
@@ -32,22 +34,22 @@ func buildAgentBeadID(identity string, role Role) string {
|
||||
if role == RoleUnknown || role == Role("") {
|
||||
switch {
|
||||
case identity == "mayor":
|
||||
return "gt-mayor"
|
||||
return beads.MayorBeadID()
|
||||
case identity == "deacon":
|
||||
return "gt-deacon"
|
||||
return beads.DeaconBeadID()
|
||||
case len(parts) == 2 && parts[1] == "witness":
|
||||
return "gt-witness-" + parts[0]
|
||||
return beads.WitnessBeadID(parts[0])
|
||||
case len(parts) == 2 && parts[1] == "refinery":
|
||||
return "gt-refinery-" + parts[0]
|
||||
return beads.RefineryBeadID(parts[0])
|
||||
case len(parts) == 2:
|
||||
// Assume rig/name is a polecat
|
||||
return "gt-polecat-" + parts[0] + "-" + parts[1]
|
||||
return beads.PolecatBeadID(parts[0], parts[1])
|
||||
case len(parts) == 3 && parts[1] == "crew":
|
||||
// rig/crew/name - crew member (no agent bead)
|
||||
return ""
|
||||
// rig/crew/name - crew member
|
||||
return beads.CrewBeadID(parts[0], parts[2])
|
||||
case len(parts) == 3 && parts[1] == "polecats":
|
||||
// rig/polecats/name - explicit polecat
|
||||
return "gt-polecat-" + parts[0] + "-" + parts[2]
|
||||
return beads.PolecatBeadID(parts[0], parts[2])
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -55,28 +57,28 @@ func buildAgentBeadID(identity string, role Role) string {
|
||||
|
||||
switch role {
|
||||
case RoleMayor:
|
||||
return "gt-mayor"
|
||||
return beads.MayorBeadID()
|
||||
case RoleDeacon:
|
||||
return "gt-deacon"
|
||||
return beads.DeaconBeadID()
|
||||
case RoleWitness:
|
||||
if len(parts) >= 1 {
|
||||
return "gt-witness-" + parts[0]
|
||||
return beads.WitnessBeadID(parts[0])
|
||||
}
|
||||
return "gt-witness"
|
||||
return ""
|
||||
case RoleRefinery:
|
||||
if len(parts) >= 1 {
|
||||
return "gt-refinery-" + parts[0]
|
||||
return beads.RefineryBeadID(parts[0])
|
||||
}
|
||||
return "gt-refinery"
|
||||
return ""
|
||||
case RolePolecat:
|
||||
if len(parts) >= 2 {
|
||||
return "gt-polecat-" + parts[0] + "-" + parts[1]
|
||||
} else if len(parts) == 1 {
|
||||
return "gt-polecat-" + parts[0]
|
||||
return beads.PolecatBeadID(parts[0], parts[1])
|
||||
}
|
||||
return ""
|
||||
case RoleCrew:
|
||||
// Crew members may not have agent beads
|
||||
if len(parts) >= 3 && parts[1] == "crew" {
|
||||
return beads.CrewBeadID(parts[0], parts[2])
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/polecat"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
@@ -1208,7 +1209,7 @@ func runPolecatNuke(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf(" - Kill session: gt-%s-%s\n", p.rigName, p.polecatName)
|
||||
fmt.Printf(" - Delete worktree: %s/polecats/%s\n", p.r.Path, p.polecatName)
|
||||
fmt.Printf(" - Delete branch (if exists)\n")
|
||||
fmt.Printf(" - Close agent bead: gt-polecat-%s-%s\n", p.rigName, p.polecatName)
|
||||
fmt.Printf(" - Close agent bead: %s\n", beads.PolecatBeadID(p.rigName, p.polecatName))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1257,7 +1258,7 @@ func runPolecatNuke(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Step 5: Close agent bead (if exists)
|
||||
agentBeadID := fmt.Sprintf("gt-polecat-%s-%s", p.rigName, p.polecatName)
|
||||
agentBeadID := beads.PolecatBeadID(p.rigName, p.polecatName)
|
||||
closeCmd := exec.Command("bd", "close", agentBeadID, "--reason=nuked")
|
||||
closeCmd.Dir = filepath.Join(p.r.Path, "mayor", "rig")
|
||||
if err := closeCmd.Run(); err != nil {
|
||||
|
||||
@@ -1137,31 +1137,32 @@ func getAgentFields(ctx RoleContext, state string) *beads.AgentFields {
|
||||
}
|
||||
|
||||
// getAgentBeadID returns the agent bead ID for the current role.
|
||||
// Uses canonical naming: prefix-rig-role-name
|
||||
// Returns empty string for unknown roles.
|
||||
func getAgentBeadID(ctx RoleContext) string {
|
||||
switch ctx.Role {
|
||||
case RoleMayor:
|
||||
return "gt-mayor"
|
||||
return beads.MayorBeadID()
|
||||
case RoleDeacon:
|
||||
return "gt-deacon"
|
||||
return beads.DeaconBeadID()
|
||||
case RoleWitness:
|
||||
if ctx.Rig != "" {
|
||||
return fmt.Sprintf("gt-witness-%s", ctx.Rig)
|
||||
return beads.WitnessBeadID(ctx.Rig)
|
||||
}
|
||||
return ""
|
||||
case RoleRefinery:
|
||||
if ctx.Rig != "" {
|
||||
return fmt.Sprintf("gt-refinery-%s", ctx.Rig)
|
||||
return beads.RefineryBeadID(ctx.Rig)
|
||||
}
|
||||
return ""
|
||||
case RolePolecat:
|
||||
if ctx.Rig != "" && ctx.Polecat != "" {
|
||||
return fmt.Sprintf("gt-polecat-%s-%s", ctx.Rig, ctx.Polecat)
|
||||
return beads.PolecatBeadID(ctx.Rig, ctx.Polecat)
|
||||
}
|
||||
return ""
|
||||
case RoleCrew:
|
||||
if ctx.Rig != "" && ctx.Polecat != "" {
|
||||
return fmt.Sprintf("gt-crew-%s-%s", ctx.Rig, ctx.Polecat)
|
||||
return beads.CrewBeadID(ctx.Rig, ctx.Polecat)
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
|
||||
+11
-10
@@ -632,11 +632,11 @@ func runSlingFormula(args []string) error {
|
||||
// This enables the witness to see what each agent is working on.
|
||||
func updateAgentHookBead(agentID, beadID string) {
|
||||
// Convert agent ID to agent bead ID
|
||||
// Format examples:
|
||||
// gastown/crew/max -> gt-crew-gastown-max
|
||||
// gastown/polecats/Toast -> gt-polecat-gastown-Toast
|
||||
// Format examples (canonical: prefix-rig-role-name):
|
||||
// gastown/crew/max -> gt-gastown-crew-max
|
||||
// gastown/polecats/Toast -> gt-gastown-polecat-Toast
|
||||
// mayor -> gt-mayor
|
||||
// gastown/witness -> gt-witness-gastown
|
||||
// gastown/witness -> gt-gastown-witness
|
||||
agentBeadID := agentIDToBeadID(agentID)
|
||||
if agentBeadID == "" {
|
||||
return
|
||||
@@ -673,13 +673,14 @@ func wakeRigAgents(rigName string) {
|
||||
}
|
||||
|
||||
// agentIDToBeadID converts an agent ID to its corresponding agent bead ID.
|
||||
// Uses canonical naming: prefix-rig-role-name
|
||||
func agentIDToBeadID(agentID string) string {
|
||||
// Handle simple cases
|
||||
if agentID == "mayor" {
|
||||
return "gt-mayor"
|
||||
return beads.MayorBeadID()
|
||||
}
|
||||
if agentID == "deacon" {
|
||||
return "gt-deacon"
|
||||
return beads.DeaconBeadID()
|
||||
}
|
||||
|
||||
// Parse path-style agent IDs
|
||||
@@ -692,13 +693,13 @@ func agentIDToBeadID(agentID string) string {
|
||||
|
||||
switch {
|
||||
case len(parts) == 2 && parts[1] == "witness":
|
||||
return fmt.Sprintf("gt-witness-%s", rig)
|
||||
return beads.WitnessBeadID(rig)
|
||||
case len(parts) == 2 && parts[1] == "refinery":
|
||||
return fmt.Sprintf("gt-refinery-%s", rig)
|
||||
return beads.RefineryBeadID(rig)
|
||||
case len(parts) == 3 && parts[1] == "crew":
|
||||
return fmt.Sprintf("gt-crew-%s-%s", rig, parts[2])
|
||||
return beads.CrewBeadID(rig, parts[2])
|
||||
case len(parts) == 3 && parts[1] == "polecats":
|
||||
return fmt.Sprintf("gt-polecat-%s-%s", rig, parts[2])
|
||||
return beads.PolecatBeadID(rig, parts[2])
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
+14
-11
@@ -313,23 +313,26 @@ func renderAgentDetails(agent AgentRuntime, indent string, hooks []AgentHookInfo
|
||||
stateInfo = style.Dim.Render(fmt.Sprintf(" [%s]", agent.State))
|
||||
}
|
||||
|
||||
// Build agent bead ID
|
||||
// Build agent bead ID using canonical naming: prefix-rig-role-name
|
||||
agentBeadID := "gt-" + agent.Name
|
||||
if agent.Address != "" && agent.Address != agent.Name {
|
||||
// Use address for full path agents like gastown/crew/joe → gt-crew-gastown-joe
|
||||
// Use address for full path agents like gastown/crew/joe → gt-gastown-crew-joe
|
||||
addr := strings.TrimSuffix(agent.Address, "/") // Remove trailing slash for global agents
|
||||
parts := strings.Split(addr, "/")
|
||||
if len(parts) == 1 {
|
||||
// Global agent: mayor/, deacon/ → gt-mayor, gt-deacon
|
||||
agentBeadID = "gt-" + parts[0]
|
||||
agentBeadID = beads.AgentBeadID("", parts[0], "")
|
||||
} else if len(parts) >= 2 {
|
||||
rig := parts[0]
|
||||
if parts[1] == "crew" && len(parts) >= 3 {
|
||||
agentBeadID = fmt.Sprintf("gt-crew-%s-%s", parts[0], parts[2])
|
||||
} else if parts[1] == "witness" || parts[1] == "refinery" {
|
||||
agentBeadID = fmt.Sprintf("gt-%s-%s", parts[1], parts[0])
|
||||
agentBeadID = beads.CrewBeadID(rig, parts[2])
|
||||
} else if parts[1] == "witness" {
|
||||
agentBeadID = beads.WitnessBeadID(rig)
|
||||
} else if parts[1] == "refinery" {
|
||||
agentBeadID = beads.RefineryBeadID(rig)
|
||||
} else if len(parts) == 2 {
|
||||
// polecat: rig/name
|
||||
agentBeadID = fmt.Sprintf("gt-polecat-%s-%s", parts[0], parts[1])
|
||||
agentBeadID = beads.PolecatBeadID(rig, parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -531,7 +534,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
||||
Running: running,
|
||||
}
|
||||
// Look up agent bead
|
||||
agentID := fmt.Sprintf("gt-witness-%s", r.Name)
|
||||
agentID := beads.WitnessBeadID(r.Name)
|
||||
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
|
||||
witness.HookBead = fields.HookBead
|
||||
witness.State = fields.AgentState
|
||||
@@ -558,7 +561,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
||||
Running: running,
|
||||
}
|
||||
// Look up agent bead
|
||||
agentID := fmt.Sprintf("gt-refinery-%s", r.Name)
|
||||
agentID := beads.RefineryBeadID(r.Name)
|
||||
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
|
||||
refinery.HookBead = fields.HookBead
|
||||
refinery.State = fields.AgentState
|
||||
@@ -585,7 +588,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
||||
Running: running,
|
||||
}
|
||||
// Look up agent bead
|
||||
agentID := fmt.Sprintf("gt-polecat-%s-%s", r.Name, name)
|
||||
agentID := beads.PolecatBeadID(r.Name, name)
|
||||
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
|
||||
polecat.HookBead = fields.HookBead
|
||||
polecat.State = fields.AgentState
|
||||
@@ -612,7 +615,7 @@ func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string, agentBeads *bea
|
||||
Running: running,
|
||||
}
|
||||
// Look up agent bead
|
||||
agentID := fmt.Sprintf("gt-crew-%s-%s", r.Name, name)
|
||||
agentID := beads.CrewBeadID(r.Name, name)
|
||||
if issue, fields, err := agentBeads.GetAgentBead(agentID); err == nil && issue != nil {
|
||||
crewAgent.HookBead = fields.HookBead
|
||||
crewAgent.State = fields.AgentState
|
||||
|
||||
@@ -60,8 +60,8 @@ func runStatusLine(cmd *cobra.Command, args []string) error {
|
||||
return runDeaconStatusLine(t)
|
||||
}
|
||||
|
||||
// Witness status line (session naming: gt-witness-<rig>)
|
||||
if role == "witness" || strings.HasPrefix(statusLineSession, "gt-witness-") {
|
||||
// Witness status line (session naming: gt-<rig>-witness)
|
||||
if role == "witness" || strings.HasSuffix(statusLineSession, "-witness") {
|
||||
return runWitnessStatusLine(t, rigName)
|
||||
}
|
||||
|
||||
@@ -221,9 +221,9 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
|
||||
// Shows: polecat count, crew count, mail preview
|
||||
func runWitnessStatusLine(t *tmux.Tmux, rigName string) error {
|
||||
if rigName == "" {
|
||||
// Try to extract from session name: gt-witness-<rig>
|
||||
if strings.HasPrefix(statusLineSession, "gt-witness-") {
|
||||
rigName = strings.TrimPrefix(statusLineSession, "gt-witness-")
|
||||
// Try to extract from session name: gt-<rig>-witness
|
||||
if strings.HasSuffix(statusLineSession, "-witness") && strings.HasPrefix(statusLineSession, "gt-") {
|
||||
rigName = strings.TrimPrefix(strings.TrimSuffix(statusLineSession, "-witness"), "gt-")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ func TestCategorizeSessionRig(t *testing.T) {
|
||||
{"gt-gastown-crew-max", "gastown"},
|
||||
{"gt-myrig-crew-user", "myrig"},
|
||||
|
||||
// Witness sessions (actual format: gt-witness-<rig>)
|
||||
{"gt-witness-gastown", "gastown"},
|
||||
{"gt-witness-myrig", "myrig"},
|
||||
// Legacy format still works as fallback
|
||||
// Witness sessions (canonical format: gt-<rig>-witness)
|
||||
{"gt-gastown-witness", "gastown"},
|
||||
{"gt-myrig-witness", "myrig"},
|
||||
// Legacy format still works as fallback
|
||||
{"gt-witness-gastown", "gastown"},
|
||||
{"gt-witness-myrig", "myrig"},
|
||||
|
||||
// Refinery sessions
|
||||
{"gt-gastown-refinery", "gastown"},
|
||||
@@ -61,8 +61,8 @@ func TestCategorizeSessionType(t *testing.T) {
|
||||
{"gt-a-b", AgentPolecat},
|
||||
|
||||
// Non-polecat sessions
|
||||
{"gt-witness-gastown", AgentWitness}, // actual format
|
||||
{"gt-gastown-witness", AgentWitness}, // legacy fallback
|
||||
{"gt-gastown-witness", AgentWitness}, // canonical format
|
||||
{"gt-witness-gastown", AgentWitness}, // legacy fallback
|
||||
{"gt-gastown-refinery", AgentRefinery},
|
||||
{"gt-gastown-crew-max", AgentCrew},
|
||||
{"gt-myrig-crew-user", AgentCrew},
|
||||
|
||||
@@ -135,9 +135,9 @@ func runThemeApply(cmd *cobra.Command, args []string) error {
|
||||
theme = tmux.DeaconTheme()
|
||||
worker = "Deacon"
|
||||
role = "health-check"
|
||||
} else if strings.HasPrefix(session, "gt-witness-") {
|
||||
// Witness sessions: gt-witness-<rig>
|
||||
rig = strings.TrimPrefix(session, "gt-witness-")
|
||||
} else if strings.HasSuffix(session, "-witness") && strings.HasPrefix(session, "gt-") {
|
||||
// Witness sessions: gt-<rig>-witness
|
||||
rig = strings.TrimPrefix(strings.TrimSuffix(session, "-witness"), "gt-")
|
||||
theme = getThemeForRole(rig, "witness")
|
||||
worker = "witness"
|
||||
role = "witness"
|
||||
|
||||
Reference in New Issue
Block a user