fix: Use rig's configured prefix for agent bead IDs (gt-kdy77, gt-ihvq0)

Added WithPrefix variants to agent bead ID functions:
- AgentBeadIDWithPrefix(prefix, rig, role, name)
- WitnessBeadIDWithPrefix, RefineryBeadIDWithPrefix
- CrewBeadIDWithPrefix, PolecatBeadIDWithPrefix

Updated callers to use rig's configured prefix:
- crew_add.go: reads r.Config.Prefix for crew worker beads
- rig/manager.go: uses prefix param for witness/refinery beads
- doctor/agent_beads_check.go: uses prefix from routes.jsonl

This allows non-gastown rigs (like beads with bd- prefix) to have
properly-prefixed agent beads.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-29 15:28:53 -08:00
parent 5260a9ca08
commit 87bc02b009
4 changed files with 60 additions and 57 deletions

View File

@@ -719,21 +719,28 @@ func (b *Beads) GetAgentBead(id string) (*Issue, *AgentFields, error) {
// - gt-gastown-crew-max (rig-level named agent)
// - gt-gastown-polecat-Toast (rig-level named agent)
// AgentBeadID generates the canonical agent bead ID.
// AgentBeadIDWithPrefix generates an agent bead ID using the specified prefix.
// The prefix should NOT include the hyphen (e.g., "gt", "bd", not "gt-", "bd-").
// For town-level agents (mayor, deacon), pass empty rig and name.
// For rig-level singletons (witness, refinery), pass empty name.
// For named agents (crew, polecat), pass all three.
func AgentBeadID(rig, role, name string) string {
func AgentBeadIDWithPrefix(prefix, rig, role, name string) string {
if rig == "" {
// Town-level agent: gt-mayor, gt-deacon
return "gt-" + role
// Town-level agent: prefix-mayor, prefix-deacon
return prefix + "-" + role
}
if name == "" {
// Rig-level singleton: gt-gastown-witness, gt-gastown-refinery
return "gt-" + rig + "-" + role
// Rig-level singleton: prefix-rig-witness, prefix-rig-refinery
return prefix + "-" + rig + "-" + role
}
// Rig-level named agent: gt-gastown-crew-max, gt-gastown-polecat-Toast
return "gt-" + rig + "-" + role + "-" + name
// Rig-level named agent: prefix-rig-role-name
return prefix + "-" + rig + "-" + role + "-" + name
}
// AgentBeadID generates the canonical agent bead ID using "gt" prefix.
// For non-gastown rigs, use AgentBeadIDWithPrefix with the rig's configured prefix.
func AgentBeadID(rig, role, name string) string {
return AgentBeadIDWithPrefix("gt", rig, role, name)
}
// MayorBeadID returns the Mayor agent bead ID.
@@ -746,24 +753,44 @@ func DeaconBeadID() string {
return "gt-deacon"
}
// WitnessBeadID returns the Witness agent bead ID for a rig.
// WitnessBeadIDWithPrefix returns the Witness agent bead ID for a rig using the specified prefix.
func WitnessBeadIDWithPrefix(prefix, rig string) string {
return AgentBeadIDWithPrefix(prefix, rig, "witness", "")
}
// WitnessBeadID returns the Witness agent bead ID for a rig using "gt" prefix.
func WitnessBeadID(rig string) string {
return AgentBeadID(rig, "witness", "")
return WitnessBeadIDWithPrefix("gt", rig)
}
// RefineryBeadID returns the Refinery agent bead ID for a rig.
// RefineryBeadIDWithPrefix returns the Refinery agent bead ID for a rig using the specified prefix.
func RefineryBeadIDWithPrefix(prefix, rig string) string {
return AgentBeadIDWithPrefix(prefix, rig, "refinery", "")
}
// RefineryBeadID returns the Refinery agent bead ID for a rig using "gt" prefix.
func RefineryBeadID(rig string) string {
return AgentBeadID(rig, "refinery", "")
return RefineryBeadIDWithPrefix("gt", rig)
}
// CrewBeadID returns a Crew worker agent bead ID.
// CrewBeadIDWithPrefix returns a Crew worker agent bead ID using the specified prefix.
func CrewBeadIDWithPrefix(prefix, rig, name string) string {
return AgentBeadIDWithPrefix(prefix, rig, "crew", name)
}
// CrewBeadID returns a Crew worker agent bead ID using "gt" prefix.
func CrewBeadID(rig, name string) string {
return AgentBeadID(rig, "crew", name)
return CrewBeadIDWithPrefix("gt", rig, name)
}
// PolecatBeadID returns a Polecat agent bead ID.
// PolecatBeadIDWithPrefix returns a Polecat agent bead ID using the specified prefix.
func PolecatBeadIDWithPrefix(prefix, rig, name string) string {
return AgentBeadIDWithPrefix(prefix, rig, "polecat", name)
}
// PolecatBeadID returns a Polecat agent bead ID using "gt" prefix.
func PolecatBeadID(rig, name string) string {
return AgentBeadID(rig, "polecat", name)
return PolecatBeadIDWithPrefix("gt", rig, name)
}
// ParseAgentBeadID parses an agent bead ID into its components.

View File

@@ -81,7 +81,12 @@ 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 := beads.CrewBeadID(rigName, name)
// Use the rig's configured prefix, defaulting to "gt" for backward compatibility
prefix := "gt"
if r.Config != nil && r.Config.Prefix != "" {
prefix = r.Config.Prefix
}
crewID := beads.CrewBeadIDWithPrefix(prefix, rigName, name)
if _, err := bd.Show(crewID); err != nil {
// Agent bead doesn't exist, create it
fields := &beads.AgentFields{

View File

@@ -16,10 +16,7 @@ import (
// - Crew workers - stored in each rig's beads
//
// Agent beads are created by gt rig add (see gt-h3hak, gt-pinkq) and gt crew add.
//
// NOTE: Currently, the beads library validates that agent IDs must start
// with 'gt-'. Rigs with different prefixes (like 'bd-') cannot have agent
// beads created until that validation is fixed in the beads repo.
// Each rig uses its configured prefix (e.g., "gt-" for gastown, "bd-" for beads).
type AgentBeadsCheck struct {
FixableCheck
}
@@ -86,22 +83,14 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
}
// Check each rig for its agents
var skipped []string
for prefix, rigName := range prefixToRig {
// Skip non-gt prefixes - beads library currently requires gt- prefix for agents
// TODO: Remove this once beads validation is fixed to accept any prefix
if prefix != "gt" {
skipped = append(skipped, fmt.Sprintf("%s (%s-*)", rigName, prefix))
continue
}
// Get beads client for this rig
rigBeadsPath := filepath.Join(ctx.TownRoot, rigName, "mayor", "rig")
bd := beads.New(rigBeadsPath)
// Check rig-specific agents (using canonical naming: prefix-rig-role-name)
witnessID := beads.WitnessBeadID(rigName)
refineryID := beads.RefineryBeadID(rigName)
witnessID := beads.WitnessBeadIDWithPrefix(prefix, rigName)
refineryID := beads.RefineryBeadIDWithPrefix(prefix, rigName)
if _, err := bd.Show(witnessID); err != nil {
missing = append(missing, witnessID)
@@ -116,7 +105,7 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
// Check crew worker agents
crewWorkers := listCrewWorkers(ctx.TownRoot, rigName)
for _, workerName := range crewWorkers {
crewID := beads.CrewBeadID(rigName, workerName)
crewID := beads.CrewBeadIDWithPrefix(prefix, rigName, workerName)
if _, err := bd.Show(crewID); err != nil {
missing = append(missing, crewID)
}
@@ -141,31 +130,18 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
}
if len(missing) == 0 {
msg := fmt.Sprintf("All %d agent beads exist", checked)
var details []string
if len(skipped) > 0 {
details = append(details, fmt.Sprintf("Skipped %d rig(s) with non-gt prefix (beads library limitation): %s",
len(skipped), strings.Join(skipped, ", ")))
}
return &CheckResult{
Name: c.Name(),
Status: StatusOK,
Message: msg,
Details: details,
Message: fmt.Sprintf("All %d agent beads exist", checked),
}
}
details := missing
if len(skipped) > 0 {
details = append(details, fmt.Sprintf("Skipped %d rig(s) with non-gt prefix: %s",
len(skipped), strings.Join(skipped, ", ")))
}
return &CheckResult{
Name: c.Name(),
Status: StatusError,
Message: fmt.Sprintf("%d agent bead(s) missing", len(missing)),
Details: details,
Details: missing,
FixHint: "Run 'gt doctor --fix' to create missing agent beads",
}
}
@@ -207,16 +183,11 @@ func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
// Create missing agents for each rig
for prefix, rigName := range prefixToRig {
// Skip non-gt prefixes - beads library currently requires gt- prefix for agents
if prefix != "gt" {
continue
}
rigBeadsPath := filepath.Join(ctx.TownRoot, rigName, "mayor", "rig")
bd := beads.New(rigBeadsPath)
// Create rig-specific agents if missing (using canonical naming: prefix-rig-role-name)
witnessID := beads.WitnessBeadID(rigName)
witnessID := beads.WitnessBeadIDWithPrefix(prefix, rigName)
if _, err := bd.Show(witnessID); err != nil {
fields := &beads.AgentFields{
RoleType: "witness",
@@ -230,7 +201,7 @@ func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
}
}
refineryID := beads.RefineryBeadID(rigName)
refineryID := beads.RefineryBeadIDWithPrefix(prefix, rigName)
if _, err := bd.Show(refineryID); err != nil {
fields := &beads.AgentFields{
RoleType: "refinery",
@@ -247,7 +218,7 @@ func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
// Create crew worker agents if missing
crewWorkers := listCrewWorkers(ctx.TownRoot, rigName)
for _, workerName := range crewWorkers {
crewID := beads.CrewBeadID(rigName, workerName)
crewID := beads.CrewBeadIDWithPrefix(prefix, rigName, workerName)
if _, err := bd.Show(crewID); err != nil {
fields := &beads.AgentFields{
RoleType: "crew",

View File

@@ -417,13 +417,13 @@ func (m *Manager) initAgentBeads(rigPath, rigName, prefix string, isFirstRig boo
// Always create rig-specific agents (using canonical naming: prefix-rig-role-name)
agents = append(agents,
agentDef{
id: beads.WitnessBeadID(rigName),
id: beads.WitnessBeadIDWithPrefix(prefix, rigName),
roleType: "witness",
rig: rigName,
desc: fmt.Sprintf("Witness for %s - monitors polecat health and progress.", rigName),
},
agentDef{
id: beads.RefineryBeadID(rigName),
id: beads.RefineryBeadIDWithPrefix(prefix, rigName),
roleType: "refinery",
rig: rigName,
desc: fmt.Sprintf("Refinery for %s - processes merge queue.", rigName),