feat: gt rig add creates agent beads (gt-h3hak, gt-pinkq)

Bootstrap now creates agent beads for ZFC compliance:
- Always: <prefix>-witness-<rig>, <prefix>-refinery-<rig>
- First rig only: <prefix>-deacon, <prefix>-mayor

Agent beads are created in the rig's beads database (not town beads)
because the daemon looks up beads by prefix routing.

Changes:
- internal/rig/manager.go: Added initAgentBeads() function
- internal/cmd/install.go: Added comment explaining why beads aren't
  created here (no rig exists yet at install time)

🤖 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-28 02:26:16 -08:00
parent 703234cf80
commit 38b6878c02
2 changed files with 92 additions and 0 deletions

View File

@@ -174,6 +174,11 @@ func runInstall(cmd *cobra.Command, args []string) error {
} else { } else {
fmt.Printf(" ✓ Initialized .beads/ (town-level beads with gm- prefix)\n") fmt.Printf(" ✓ Initialized .beads/ (town-level beads with gm- prefix)\n")
} }
// NOTE: Agent beads (gt-deacon, gt-mayor) are created by gt rig add,
// not here. This is because the daemon looks up beads by prefix routing,
// and no rig exists yet at install time. The first rig added will get
// these global agent beads in its beads database.
} }
// Initialize git if requested (--git or --github implies --git) // Initialize git if requested (--git or --github implies --git)
@@ -246,3 +251,4 @@ func initTownBeads(townPath string) error {
return nil return nil
} }

View File

@@ -10,6 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/git" "github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/templates" "github.com/steveyegge/gastown/internal/templates"
@@ -291,6 +292,14 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
return nil, fmt.Errorf("initializing beads: %w", err) return nil, fmt.Errorf("initializing beads: %w", err)
} }
// Create agent beads for this rig (witness, refinery) and
// global agents (deacon, mayor) if this is the first rig.
isFirstRig := len(m.config.Rigs) == 0
if err := m.initAgentBeads(rigPath, opts.Name, opts.BeadsPrefix, isFirstRig); err != nil {
// Non-fatal: log warning but continue
fmt.Printf(" Warning: Could not create agent beads: %v\n", err)
}
// Seed patrol molecules for this rig // Seed patrol molecules for this rig
if err := m.seedPatrolMolecules(rigPath); err != nil { if err := m.seedPatrolMolecules(rigPath); err != nil {
// Non-fatal: log warning but continue // Non-fatal: log warning but continue
@@ -376,6 +385,83 @@ func (m *Manager) initBeads(rigPath, prefix string) error {
return nil return nil
} }
// initAgentBeads creates agent beads for this rig and optionally global agents.
// - Always creates: <prefix>-witness-<rig>, <prefix>-refinery-<rig>
// - First rig only: <prefix>-deacon, <prefix>-mayor
//
// Agent beads track lifecycle state for ZFC compliance (gt-h3hak, gt-pinkq).
func (m *Manager) initAgentBeads(rigPath, rigName, prefix string, isFirstRig bool) error {
// Run bd commands from mayor/rig which has the beads database
mayorRigPath := filepath.Join(rigPath, "mayor", "rig")
bd := beads.New(mayorRigPath)
// Define agents to create
type agentDef struct {
id string
roleType string
rig string
desc string
}
var agents []agentDef
// Always create rig-specific agents
agents = append(agents,
agentDef{
id: fmt.Sprintf("%s-witness-%s", prefix, rigName),
roleType: "witness",
rig: rigName,
desc: fmt.Sprintf("Witness for %s - monitors polecat health and progress.", rigName),
},
agentDef{
id: fmt.Sprintf("%s-refinery-%s", prefix, rigName),
roleType: "refinery",
rig: rigName,
desc: fmt.Sprintf("Refinery for %s - processes merge queue.", rigName),
},
)
// First rig also gets global agents (deacon, mayor)
if isFirstRig {
agents = append(agents,
agentDef{
id: prefix + "-deacon",
roleType: "deacon",
rig: "",
desc: "Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring.",
},
agentDef{
id: prefix + "-mayor",
roleType: "mayor",
rig: "",
desc: "Mayor - global coordinator, handles cross-rig communication and escalations.",
},
)
}
for _, agent := range agents {
// Check if already exists
if _, err := bd.Show(agent.id); err == nil {
continue // Already exists
}
fields := &beads.AgentFields{
RoleType: agent.roleType,
Rig: agent.rig,
AgentState: "idle",
HookBead: "",
RoleBead: agent.id + "-role",
}
if _, err := bd.CreateAgentBead(agent.id, agent.desc, fields); err != nil {
return fmt.Errorf("creating %s: %w", agent.id, err)
}
fmt.Printf(" ✓ Created agent bead: %s\n", agent.id)
}
return nil
}
// ensureGitignoreEntry adds an entry to .gitignore if it doesn't already exist. // ensureGitignoreEntry adds an entry to .gitignore if it doesn't already exist.
func (m *Manager) ensureGitignoreEntry(gitignorePath, entry string) error { func (m *Manager) ensureGitignoreEntry(gitignorePath, entry string) error {
// Read existing content // Read existing content