From 12543d14501a63edec7e0cf540fe91310fbf4c52 Mon Sep 17 00:00:00 2001 From: gastown/polecats/cheedo Date: Tue, 30 Dec 2025 22:23:58 -0800 Subject: [PATCH] fix: Make ParseAgentBeadID accept any valid prefix (gt-w0fqg) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- internal/beads/beads.go | 18 ++++++++++++------ internal/beads/beads_test.go | 19 ++++++++++++++++--- internal/cmd/sling.go | 6 +++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/internal/beads/beads.go b/internal/beads/beads.go index 3384d611..47c07301 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -1011,22 +1011,27 @@ func PolecatBeadID(rig, name string) string { // Returns rig, role, name, and whether parsing succeeded. // For town-level agents, rig 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) { - 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 } - rest := strings.TrimPrefix(id, "gt-") + + rest := id[hyphenIdx+1:] parts := strings.Split(rest, "-") switch len(parts) { case 1: - // Town-level: gt-mayor, gt-deacon + // Town-level: gt-mayor, bd-deacon return "", parts[0], "", true case 2: - // Rig-level singleton: gt-gastown-witness + // Rig-level singleton: gt-gastown-witness, bd-beads-witness return parts[0], parts[1], "", true 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 default: // 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. -// 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. func IsAgentSessionBead(beadID string) bool { _, role, _, ok := ParseAgentBeadID(beadID) diff --git a/internal/beads/beads_test.go b/internal/beads/beads_test.go index 67a09276..9ab444e1 100644 --- a/internal/beads/beads_test.go +++ b/internal/beads/beads_test.go @@ -1019,8 +1019,14 @@ func TestParseAgentBeadID(t *testing.T) { {"gt-gastown-polecat-my-agent", "gastown", "polecat", "my-agent", true}, // Parseable but not valid agent roles (IsAgentSessionBead will reject) {"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 - {"bd-gastown-crew-joe", "", "", "", false}, // Wrong prefix + {"x-mayor", "", "", "", false}, // Prefix too short (1 char) + {"abcd-mayor", "", "", "", false}, // Prefix too long (4 chars) {"", "", "", "", false}, } @@ -1049,19 +1055,26 @@ func TestIsAgentSessionBead(t *testing.T) { beadID string want bool }{ - // Agent session beads (should return true) + // Agent session beads with gt- prefix (should return true) {"gt-mayor", true}, {"gt-deacon", true}, {"gt-gastown-witness", true}, {"gt-gastown-refinery", true}, {"gt-gastown-crew-joe", 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) {"gt-abc123", false}, {"gt-sb6m4", false}, {"gt-u7dxq", false}, - // Invalid beads {"bd-abc123", false}, + // Invalid beads {"", false}, } diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index eb8e22df..9854f0ec 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -842,9 +842,9 @@ func detectActor() string { } // agentIDToBeadID converts an agent ID to its corresponding agent bead ID. -// Uses canonical naming: gt-rig-role-name -// Agent beads always use "gt-" prefix (required by beads validation). -// Only issue beads use rig-specific prefixes. +// Uses canonical naming: prefix-rig-role-name +// This function uses "gt-" prefix by default. For non-gastown rigs, use the +// appropriate *WithPrefix functions that accept the rig's configured prefix. func agentIDToBeadID(agentID string) string { // Handle simple cases (town-level agents) if agentID == "mayor" {