Files
gastown/internal/beads/agent_ids.go
gastown/crew/dennis b60f016955 refactor(beads,mail): split large files into focused modules
Break down monolithic beads.go and mail.go into smaller, single-purpose files:

beads package:
- beads_agent.go: Agent-related bead operations
- beads_delegation.go: Delegation bead handling
- beads_dog.go: Dog pool operations
- beads_merge_slot.go: Merge slot management
- beads_mr.go: Merge request operations
- beads_redirect.go: Redirect bead handling
- beads_rig.go: Rig bead operations
- beads_role.go: Role bead management

cmd package:
- mail_announce.go: Announcement subcommand
- mail_check.go: Mail check subcommand
- mail_identity.go: Identity management
- mail_inbox.go: Inbox operations
- mail_queue.go: Queue subcommand
- mail_search.go: Search functionality
- mail_send.go: Send subcommand
- mail_thread.go: Thread operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 23:01:55 -08:00

247 lines
8.5 KiB
Go

// Package beads provides a wrapper for the bd (beads) CLI.
package beads
import (
"fmt"
"strings"
)
// TownBeadsPrefix is the prefix used for town-level agent beads stored in ~/gt/.beads/.
// This distinguishes them from rig-level beads (which use project prefixes like "gt-").
const TownBeadsPrefix = "hq"
// Town-level agent bead IDs use the "hq-" prefix and are stored in town beads.
// These are global agents that operate at the town level (mayor, deacon, dogs).
//
// The naming convention is:
// - hq-<role> for singletons (mayor, deacon)
// - hq-dog-<name> for named agents (dogs)
// - hq-<role>-role for role definition beads
// MayorBeadIDTown returns the Mayor agent bead ID for town-level beads.
// This uses the "hq-" prefix for town-level storage.
func MayorBeadIDTown() string {
return TownBeadsPrefix + "-mayor"
}
// DeaconBeadIDTown returns the Deacon agent bead ID for town-level beads.
// This uses the "hq-" prefix for town-level storage.
func DeaconBeadIDTown() string {
return TownBeadsPrefix + "-deacon"
}
// DogBeadIDTown returns a Dog agent bead ID for town-level beads.
// Dogs are town-level agents, so they follow the pattern: hq-dog-<name>
func DogBeadIDTown(name string) string {
return fmt.Sprintf("%s-dog-%s", TownBeadsPrefix, name)
}
// RoleBeadIDTown returns the role bead ID for town-level storage.
// Role beads define lifecycle configuration for each agent type.
// Uses "hq-" prefix for town-level storage: hq-<role>-role
func RoleBeadIDTown(role string) string {
return fmt.Sprintf("%s-%s-role", TownBeadsPrefix, role)
}
// MayorRoleBeadIDTown returns the Mayor role bead ID for town-level storage.
func MayorRoleBeadIDTown() string {
return RoleBeadIDTown("mayor")
}
// DeaconRoleBeadIDTown returns the Deacon role bead ID for town-level storage.
func DeaconRoleBeadIDTown() string {
return RoleBeadIDTown("deacon")
}
// DogRoleBeadIDTown returns the Dog role bead ID for town-level storage.
func DogRoleBeadIDTown() string {
return RoleBeadIDTown("dog")
}
// WitnessRoleBeadIDTown returns the Witness role bead ID for town-level storage.
func WitnessRoleBeadIDTown() string {
return RoleBeadIDTown("witness")
}
// RefineryRoleBeadIDTown returns the Refinery role bead ID for town-level storage.
func RefineryRoleBeadIDTown() string {
return RoleBeadIDTown("refinery")
}
// PolecatRoleBeadIDTown returns the Polecat role bead ID for town-level storage.
func PolecatRoleBeadIDTown() string {
return RoleBeadIDTown("polecat")
}
// CrewRoleBeadIDTown returns the Crew role bead ID for town-level storage.
func CrewRoleBeadIDTown() string {
return RoleBeadIDTown("crew")
}
// ===== Rig-level agent bead ID helpers (gt- prefix) =====
// Agent bead ID naming convention:
// prefix-rig-role-name
//
// Examples:
// - gt-mayor (town-level, no rig)
// - gt-deacon (town-level, no rig)
// - gt-gastown-witness (rig-level singleton)
// - gt-gastown-refinery (rig-level singleton)
// - gt-gastown-crew-max (rig-level named agent)
// - gt-gastown-polecat-Toast (rig-level named agent)
// 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 AgentBeadIDWithPrefix(prefix, rig, role, name string) string {
if rig == "" {
// Town-level agent: prefix-mayor, prefix-deacon
return prefix + "-" + role
}
if name == "" {
// Rig-level singleton: prefix-rig-witness, prefix-rig-refinery
return prefix + "-" + rig + "-" + role
}
// 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.
//
// Deprecated: Use MayorBeadIDTown() for town-level beads (hq- prefix).
// This function returns "gt-mayor" which is for rig-level storage.
// Town-level agents like Mayor should use the hq- prefix.
func MayorBeadID() string {
return "gt-mayor"
}
// DeaconBeadID returns the Deacon agent bead ID.
//
// Deprecated: Use DeaconBeadIDTown() for town-level beads (hq- prefix).
// This function returns "gt-deacon" which is for rig-level storage.
// Town-level agents like Deacon should use the hq- prefix.
func DeaconBeadID() string {
return "gt-deacon"
}
// DogBeadID returns a Dog agent bead ID.
// Dogs are town-level agents, so they follow the pattern: gt-dog-<name>
// Deprecated: Use DogBeadIDTown() for town-level beads with hq- prefix.
// Dogs are town-level agents and should use hq-dog-<name>, not gt-dog-<name>.
func DogBeadID(name string) string {
return "gt-dog-" + name
}
// 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 WitnessBeadIDWithPrefix("gt", 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 RefineryBeadIDWithPrefix("gt", rig)
}
// 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 CrewBeadIDWithPrefix("gt", rig, name)
}
// 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 PolecatBeadIDWithPrefix("gt", rig, name)
}
// ParseAgentBeadID parses an agent bead ID into its components.
// 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) {
// 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 := id[hyphenIdx+1:]
parts := strings.Split(rest, "-")
switch len(parts) {
case 1:
// Town-level: gt-mayor, bd-deacon
return "", parts[0], "", true
case 2:
// Could be rig-level singleton (gt-gastown-witness) or
// town-level named (gt-dog-alpha for dogs)
if parts[0] == "dog" {
// Dogs are town-level named agents: gt-dog-<name>
return "", "dog", parts[1], true
}
// Rig-level singleton: gt-gastown-witness
return parts[0], parts[1], "", true
case 3:
// 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
// or gt-dog-my-agent-name
if len(parts) >= 3 {
if parts[0] == "dog" {
// Dog with hyphenated name: gt-dog-my-dog-name
return "", "dog", strings.Join(parts[1:], "-"), true
}
return parts[0], parts[1], strings.Join(parts[2:], "-"), true
}
return "", "", "", false
}
}
// IsAgentSessionBead returns true if the bead ID represents an agent session molecule.
// 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)
if !ok {
return false
}
// Known agent roles
switch role {
case "mayor", "deacon", "witness", "refinery", "crew", "polecat", "dog":
return true
default:
return false
}
}