fix: Make ParseAgentBeadID accept any valid prefix (gt-w0fqg)

Agent bead ID validation was hardcoded to only accept "gt-" prefix, which
caused errors when spawning beads polecats (which use "bd-" prefix):

  Error: invalid agent ID: agent ID must start with 'gt-' (got "bd-beads-polecat-pearl")

Changed ParseAgentBeadID to accept any 2-3 character prefix (gt-, bd-, hq-)
instead of hardcoding "gt-". Updated tests to cover other prefixes.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/polecats/cheedo
2025-12-30 22:23:58 -08:00
committed by Steve Yegge
parent e539ea3cc8
commit 12543d1450
3 changed files with 31 additions and 12 deletions

View File

@@ -1011,22 +1011,27 @@ func PolecatBeadID(rig, name string) string {
// Returns rig, role, name, and whether parsing succeeded. // Returns rig, role, name, and whether parsing succeeded.
// For town-level agents, rig will be empty. // For town-level agents, rig will be empty.
// For singletons, name will be empty. // For singletons, name will be empty.
// Accepts any valid prefix (e.g., "gt-", "bd-"), not just "gt-".
func ParseAgentBeadID(id string) (rig, role, name string, ok bool) { func ParseAgentBeadID(id string) (rig, role, name string, ok bool) {
if !strings.HasPrefix(id, "gt-") { // Find the prefix (everything before the first hyphen)
// Valid prefixes are 2-3 characters (e.g., "gt", "bd", "hq")
hyphenIdx := strings.Index(id, "-")
if hyphenIdx < 2 || hyphenIdx > 3 {
return "", "", "", false return "", "", "", false
} }
rest := strings.TrimPrefix(id, "gt-")
rest := id[hyphenIdx+1:]
parts := strings.Split(rest, "-") parts := strings.Split(rest, "-")
switch len(parts) { switch len(parts) {
case 1: case 1:
// Town-level: gt-mayor, gt-deacon // Town-level: gt-mayor, bd-deacon
return "", parts[0], "", true return "", parts[0], "", true
case 2: case 2:
// Rig-level singleton: gt-gastown-witness // Rig-level singleton: gt-gastown-witness, bd-beads-witness
return parts[0], parts[1], "", true return parts[0], parts[1], "", true
case 3: case 3:
// Rig-level named: gt-gastown-crew-max // Rig-level named: gt-gastown-crew-max, bd-beads-polecat-pearl
return parts[0], parts[1], parts[2], true return parts[0], parts[1], parts[2], true
default: default:
// Handle names with hyphens: gt-gastown-polecat-my-agent-name // Handle names with hyphens: gt-gastown-polecat-my-agent-name
@@ -1038,7 +1043,8 @@ func ParseAgentBeadID(id string) (rig, role, name string, ok bool) {
} }
// IsAgentSessionBead returns true if the bead ID represents an agent session molecule. // IsAgentSessionBead returns true if the bead ID represents an agent session molecule.
// Agent session beads follow patterns like gt-mayor, gt-gastown-witness, gt-gastown-crew-joe. // Agent session beads follow patterns like gt-mayor, bd-beads-witness, gt-gastown-crew-joe.
// Supports any valid prefix (e.g., "gt-", "bd-"), not just "gt-".
// These are used to track agent state and update frequently, which can create noise. // These are used to track agent state and update frequently, which can create noise.
func IsAgentSessionBead(beadID string) bool { func IsAgentSessionBead(beadID string) bool {
_, role, _, ok := ParseAgentBeadID(beadID) _, role, _, ok := ParseAgentBeadID(beadID)

View File

@@ -1019,8 +1019,14 @@ func TestParseAgentBeadID(t *testing.T) {
{"gt-gastown-polecat-my-agent", "gastown", "polecat", "my-agent", true}, {"gt-gastown-polecat-my-agent", "gastown", "polecat", "my-agent", true},
// Parseable but not valid agent roles (IsAgentSessionBead will reject) // Parseable but not valid agent roles (IsAgentSessionBead will reject)
{"gt-abc123", "", "abc123", "", true}, // Parses as town-level but not valid role {"gt-abc123", "", "abc123", "", true}, // Parses as town-level but not valid role
// Other prefixes (bd-, hq-)
{"bd-mayor", "", "mayor", "", true}, // bd prefix town-level
{"bd-beads-witness", "beads", "witness", "", true}, // bd prefix rig-level singleton
{"bd-beads-polecat-pearl", "beads", "polecat", "pearl", true}, // bd prefix rig-level named
{"hq-mayor", "", "mayor", "", true}, // hq prefix town-level
// Truly invalid patterns // Truly invalid patterns
{"bd-gastown-crew-joe", "", "", "", false}, // Wrong prefix {"x-mayor", "", "", "", false}, // Prefix too short (1 char)
{"abcd-mayor", "", "", "", false}, // Prefix too long (4 chars)
{"", "", "", "", false}, {"", "", "", "", false},
} }
@@ -1049,19 +1055,26 @@ func TestIsAgentSessionBead(t *testing.T) {
beadID string beadID string
want bool want bool
}{ }{
// Agent session beads (should return true) // Agent session beads with gt- prefix (should return true)
{"gt-mayor", true}, {"gt-mayor", true},
{"gt-deacon", true}, {"gt-deacon", true},
{"gt-gastown-witness", true}, {"gt-gastown-witness", true},
{"gt-gastown-refinery", true}, {"gt-gastown-refinery", true},
{"gt-gastown-crew-joe", true}, {"gt-gastown-crew-joe", true},
{"gt-gastown-polecat-capable", true}, {"gt-gastown-polecat-capable", true},
// Agent session beads with bd- prefix (should return true)
{"bd-mayor", true},
{"bd-deacon", true},
{"bd-beads-witness", true},
{"bd-beads-refinery", true},
{"bd-beads-crew-joe", true},
{"bd-beads-polecat-pearl", true},
// Regular work beads (should return false) // Regular work beads (should return false)
{"gt-abc123", false}, {"gt-abc123", false},
{"gt-sb6m4", false}, {"gt-sb6m4", false},
{"gt-u7dxq", false}, {"gt-u7dxq", false},
// Invalid beads
{"bd-abc123", false}, {"bd-abc123", false},
// Invalid beads
{"", false}, {"", false},
} }

View File

@@ -842,9 +842,9 @@ func detectActor() string {
} }
// agentIDToBeadID converts an agent ID to its corresponding agent bead ID. // agentIDToBeadID converts an agent ID to its corresponding agent bead ID.
// Uses canonical naming: gt-rig-role-name // Uses canonical naming: prefix-rig-role-name
// Agent beads always use "gt-" prefix (required by beads validation). // This function uses "gt-" prefix by default. For non-gastown rigs, use the
// Only issue beads use rig-specific prefixes. // appropriate *WithPrefix functions that accept the rig's configured prefix.
func agentIDToBeadID(agentID string) string { func agentIDToBeadID(agentID string) string {
// Handle simple cases (town-level agents) // Handle simple cases (town-level agents)
if agentID == "mayor" { if agentID == "mayor" {