feat: Update agent bead lookups to use correct tier (gt-eqptl)
Implement two-level beads architecture for agent lookups: - Town-level agents (Mayor, Deacon) now use hq- prefix and are looked up in town beads (~/.beads/) - Rig-level agents continue using rig prefix (e.g., gt-) and are looked up in rig beads Changes: - Add MayorBeadIDTown(), DeaconBeadIDTown(), DogBeadIDTown() helpers - Add GetTownBeadsPath() for town beads path resolution - Update gt status to pre-fetch town-level agent beads - Update agentIDToBeadID() to use town-level IDs - Update agent_beads_check.go to check/fix in correct tier - Update agentAddressToIDs() in deacon.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,13 @@ func WriteRoutes(beadsDir string, routes []Route) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTownBeadsPath returns the path to town-level beads directory.
|
||||||
|
// Town beads store hq-* prefixed issues including Mayor, Deacon, and role beads.
|
||||||
|
// The townRoot should be the Gas Town root directory (e.g., ~/gt).
|
||||||
|
func GetTownBeadsPath(townRoot string) string {
|
||||||
|
return filepath.Join(townRoot, ".beads")
|
||||||
|
}
|
||||||
|
|
||||||
// GetPrefixForRig returns the beads prefix for a given rig name.
|
// GetPrefixForRig returns the beads prefix for a given rig name.
|
||||||
// The prefix is returned without the trailing hyphen (e.g., "bd" not "bd-").
|
// The prefix is returned without the trailing hyphen (e.g., "bd" not "bd-").
|
||||||
// If the rig is not found in routes, returns "gt" as the default.
|
// If the rig is not found in routes, returns "gt" as the default.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/gastown/internal/beads"
|
||||||
"github.com/steveyegge/gastown/internal/config"
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
"github.com/steveyegge/gastown/internal/constants"
|
"github.com/steveyegge/gastown/internal/constants"
|
||||||
"github.com/steveyegge/gastown/internal/deacon"
|
"github.com/steveyegge/gastown/internal/deacon"
|
||||||
@@ -1118,14 +1119,13 @@ func notifyMayorOfWitnessFailure(townRoot string, zombies []zombieInfo) {
|
|||||||
|
|
||||||
// agentAddressToIDs converts an agent address to bead ID and session name.
|
// agentAddressToIDs converts an agent address to bead ID and session name.
|
||||||
// Supports formats: "gastown/polecats/max", "gastown/witness", "deacon", "mayor"
|
// Supports formats: "gastown/polecats/max", "gastown/witness", "deacon", "mayor"
|
||||||
|
// Note: Town-level agents (Mayor, Deacon) use hq- prefix bead IDs stored in town beads.
|
||||||
func agentAddressToIDs(address string) (beadID, sessionName string, err error) {
|
func agentAddressToIDs(address string) (beadID, sessionName string, err error) {
|
||||||
switch address {
|
switch address {
|
||||||
case "deacon":
|
case "deacon":
|
||||||
sessName := session.DeaconSessionName()
|
return beads.DeaconBeadIDTown(), session.DeaconSessionName(), nil
|
||||||
return sessName, sessName, nil
|
|
||||||
case "mayor":
|
case "mayor":
|
||||||
sessName := session.MayorSessionName()
|
return beads.MayorBeadIDTown(), session.MayorSessionName(), nil
|
||||||
return sessName, sessName, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(address, "/")
|
parts := strings.Split(address, "/")
|
||||||
|
|||||||
@@ -970,15 +970,15 @@ 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: prefix-rig-role-name
|
// Uses canonical naming: prefix-rig-role-name
|
||||||
// This function uses "gt-" prefix by default. For non-gastown rigs, use the
|
// Town-level agents (Mayor, Deacon) use hq- prefix and are stored in town beads.
|
||||||
// appropriate *WithPrefix functions that accept the rig's configured prefix.
|
// Rig-level agents use the rig's configured prefix (default "gt-").
|
||||||
func agentIDToBeadID(agentID string) string {
|
func agentIDToBeadID(agentID string) string {
|
||||||
// Handle simple cases (town-level agents)
|
// Handle simple cases (town-level agents with hq- prefix)
|
||||||
if agentID == "mayor" {
|
if agentID == "mayor" {
|
||||||
return beads.MayorBeadID()
|
return beads.MayorBeadIDTown()
|
||||||
}
|
}
|
||||||
if agentID == "deacon" {
|
if agentID == "deacon" {
|
||||||
return beads.DeaconBeadID()
|
return beads.DeaconBeadIDTown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse path-style agent IDs
|
// Parse path-style agent IDs
|
||||||
|
|||||||
+34
-2
@@ -170,6 +170,37 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
// Pre-fetch agent beads across all rig-specific beads DBs.
|
// Pre-fetch agent beads across all rig-specific beads DBs.
|
||||||
allAgentBeads := make(map[string]*beads.Issue)
|
allAgentBeads := make(map[string]*beads.Issue)
|
||||||
allHookBeads := make(map[string]*beads.Issue)
|
allHookBeads := make(map[string]*beads.Issue)
|
||||||
|
|
||||||
|
// Fetch town-level agent beads (Mayor, Deacon) from town beads
|
||||||
|
townBeadsPath := beads.GetTownBeadsPath(townRoot)
|
||||||
|
townBeadsClient := beads.New(townBeadsPath)
|
||||||
|
townAgentBeads, _ := townBeadsClient.ListAgentBeads()
|
||||||
|
for id, issue := range townAgentBeads {
|
||||||
|
allAgentBeads[id] = issue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch hook beads from town beads
|
||||||
|
var townHookIDs []string
|
||||||
|
for _, issue := range townAgentBeads {
|
||||||
|
hookID := issue.HookBead
|
||||||
|
if hookID == "" {
|
||||||
|
fields := beads.ParseAgentFields(issue.Description)
|
||||||
|
if fields != nil {
|
||||||
|
hookID = fields.HookBead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hookID != "" {
|
||||||
|
townHookIDs = append(townHookIDs, hookID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(townHookIDs) > 0 {
|
||||||
|
townHookBeads, _ := townBeadsClient.ShowMultiple(townHookIDs)
|
||||||
|
for id, issue := range townHookBeads {
|
||||||
|
allHookBeads[id] = issue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch rig-level agent beads
|
||||||
for _, r := range rigs {
|
for _, r := range rigs {
|
||||||
rigBeadsPath := filepath.Join(r.Path, "mayor", "rig")
|
rigBeadsPath := filepath.Join(r.Path, "mayor", "rig")
|
||||||
rigBeads := beads.New(rigBeadsPath)
|
rigBeads := beads.New(rigBeadsPath)
|
||||||
@@ -650,6 +681,7 @@ func discoverGlobalAgents(allSessions map[string]bool, allAgentBeads map[string]
|
|||||||
deaconSession := getDeaconSessionName()
|
deaconSession := getDeaconSessionName()
|
||||||
|
|
||||||
// Define agents to discover
|
// Define agents to discover
|
||||||
|
// Note: Mayor and Deacon are town-level agents with hq- prefix bead IDs
|
||||||
agentDefs := []struct {
|
agentDefs := []struct {
|
||||||
name string
|
name string
|
||||||
address string
|
address string
|
||||||
@@ -657,8 +689,8 @@ func discoverGlobalAgents(allSessions map[string]bool, allAgentBeads map[string]
|
|||||||
role string
|
role string
|
||||||
beadID string
|
beadID string
|
||||||
}{
|
}{
|
||||||
{"mayor", "mayor/", mayorSession, "coordinator", mayorSession},
|
{"mayor", "mayor/", mayorSession, "coordinator", beads.MayorBeadIDTown()},
|
||||||
{"deacon", "deacon/", deaconSession, "health-check", deaconSession},
|
{"deacon", "deacon/", deaconSession, "health-check", beads.DeaconBeadIDTown()},
|
||||||
}
|
}
|
||||||
|
|
||||||
agents := make([]AgentRuntime, len(agentDefs))
|
agents := make([]AgentRuntime, len(agentDefs))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// AgentBeadsCheck verifies that agent beads exist for all agents.
|
// AgentBeadsCheck verifies that agent beads exist for all agents.
|
||||||
// This includes:
|
// This includes:
|
||||||
// - Global agents (deacon, mayor) - stored in first rig's beads
|
// - Global agents (deacon, mayor) - stored in town beads with hq- prefix
|
||||||
// - Per-rig agents (witness, refinery) - stored in each rig's beads
|
// - Per-rig agents (witness, refinery) - stored in each rig's beads
|
||||||
// - Crew workers - stored in each rig's beads
|
// - Crew workers - stored in each rig's beads
|
||||||
//
|
//
|
||||||
@@ -68,26 +68,42 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prefixToRig) == 0 {
|
|
||||||
return &CheckResult{
|
|
||||||
Name: c.Name(),
|
|
||||||
Status: StatusOK,
|
|
||||||
Message: "No rigs with beads routes (agent beads created on rig add)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var missing []string
|
var missing []string
|
||||||
var checked int
|
var checked int
|
||||||
|
|
||||||
// Find the first rig (by name, alphabetically) for global agents
|
// Check global agents (Mayor, Deacon) in town beads
|
||||||
// Only consider gt-prefix rigs since other prefixes can't have agent beads yet
|
// These use hq- prefix and are stored in ~/gt/.beads/
|
||||||
var firstRigName string
|
townBeadsPath := beads.GetTownBeadsPath(ctx.TownRoot)
|
||||||
for prefix, info := range prefixToRig {
|
townBd := beads.New(townBeadsPath)
|
||||||
if prefix != "gt" {
|
|
||||||
continue // Skip non-gt prefixes for first rig selection
|
deaconID := beads.DeaconBeadIDTown()
|
||||||
|
mayorID := beads.MayorBeadIDTown()
|
||||||
|
|
||||||
|
if _, err := townBd.Show(deaconID); err != nil {
|
||||||
|
missing = append(missing, deaconID)
|
||||||
}
|
}
|
||||||
if firstRigName == "" || info.name < firstRigName {
|
checked++
|
||||||
firstRigName = info.name
|
|
||||||
|
if _, err := townBd.Show(mayorID); err != nil {
|
||||||
|
missing = append(missing, mayorID)
|
||||||
|
}
|
||||||
|
checked++
|
||||||
|
|
||||||
|
if len(prefixToRig) == 0 {
|
||||||
|
// No rigs to check, but we still checked global agents
|
||||||
|
if len(missing) == 0 {
|
||||||
|
return &CheckResult{
|
||||||
|
Name: c.Name(),
|
||||||
|
Status: StatusOK,
|
||||||
|
Message: fmt.Sprintf("All %d agent beads exist", checked),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &CheckResult{
|
||||||
|
Name: c.Name(),
|
||||||
|
Status: StatusError,
|
||||||
|
Message: fmt.Sprintf("%d agent bead(s) missing", len(missing)),
|
||||||
|
Details: missing,
|
||||||
|
FixHint: "Run 'gt doctor --fix' to create missing agent beads",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,22 +137,6 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
}
|
}
|
||||||
checked++
|
checked++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check global agents in first rig
|
|
||||||
if rigName == firstRigName {
|
|
||||||
deaconID := beads.DeaconBeadID()
|
|
||||||
mayorID := beads.MayorBeadID()
|
|
||||||
|
|
||||||
if _, err := bd.Show(deaconID); err != nil {
|
|
||||||
missing = append(missing, deaconID)
|
|
||||||
}
|
|
||||||
checked++
|
|
||||||
|
|
||||||
if _, err := bd.Show(mayorID); err != nil {
|
|
||||||
missing = append(missing, mayorID)
|
|
||||||
}
|
|
||||||
checked++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(missing) == 0 {
|
if len(missing) == 0 {
|
||||||
@@ -158,7 +158,40 @@ func (c *AgentBeadsCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
|
|
||||||
// Fix creates missing agent beads.
|
// Fix creates missing agent beads.
|
||||||
func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
|
func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
|
||||||
// Load routes to get prefixes (same as Run)
|
// Create global agents (Mayor, Deacon) in town beads
|
||||||
|
// These use hq- prefix and are stored in ~/gt/.beads/
|
||||||
|
townBeadsPath := beads.GetTownBeadsPath(ctx.TownRoot)
|
||||||
|
townBd := beads.New(townBeadsPath)
|
||||||
|
|
||||||
|
deaconID := beads.DeaconBeadIDTown()
|
||||||
|
if _, err := townBd.Show(deaconID); err != nil {
|
||||||
|
fields := &beads.AgentFields{
|
||||||
|
RoleType: "deacon",
|
||||||
|
Rig: "",
|
||||||
|
AgentState: "idle",
|
||||||
|
RoleBead: "hq-deacon-role",
|
||||||
|
}
|
||||||
|
desc := "Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring."
|
||||||
|
if _, err := townBd.CreateAgentBead(deaconID, desc, fields); err != nil {
|
||||||
|
return fmt.Errorf("creating %s: %w", deaconID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mayorID := beads.MayorBeadIDTown()
|
||||||
|
if _, err := townBd.Show(mayorID); err != nil {
|
||||||
|
fields := &beads.AgentFields{
|
||||||
|
RoleType: "mayor",
|
||||||
|
Rig: "",
|
||||||
|
AgentState: "idle",
|
||||||
|
RoleBead: "hq-mayor-role",
|
||||||
|
}
|
||||||
|
desc := "Mayor - global coordinator, handles cross-rig communication and escalations."
|
||||||
|
if _, err := townBd.CreateAgentBead(mayorID, desc, fields); err != nil {
|
||||||
|
return fmt.Errorf("creating %s: %w", mayorID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load routes to get prefixes for rig-level agents
|
||||||
beadsDir := filepath.Join(ctx.TownRoot, ".beads")
|
beadsDir := filepath.Join(ctx.TownRoot, ".beads")
|
||||||
routes, err := beads.LoadRoutes(beadsDir)
|
routes, err := beads.LoadRoutes(beadsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -180,18 +213,7 @@ func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(prefixToRig) == 0 {
|
if len(prefixToRig) == 0 {
|
||||||
return nil // Nothing to fix
|
return nil // No rigs to process
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first rig for global agents (only gt-prefix rigs)
|
|
||||||
var firstRigName string
|
|
||||||
for prefix, info := range prefixToRig {
|
|
||||||
if prefix != "gt" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if firstRigName == "" || info.name < firstRigName {
|
|
||||||
firstRigName = info.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create missing agents for each rig
|
// Create missing agents for each rig
|
||||||
@@ -247,37 +269,6 @@ func (c *AgentBeadsCheck) Fix(ctx *CheckContext) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create global agents in first rig if missing
|
|
||||||
if rigName == firstRigName {
|
|
||||||
deaconID := beads.DeaconBeadID()
|
|
||||||
if _, err := bd.Show(deaconID); err != nil {
|
|
||||||
fields := &beads.AgentFields{
|
|
||||||
RoleType: "deacon",
|
|
||||||
Rig: "",
|
|
||||||
AgentState: "idle",
|
|
||||||
RoleBead: "gt-deacon-role",
|
|
||||||
}
|
|
||||||
desc := "Deacon (daemon beacon) - receives mechanical heartbeats, runs town plugins and monitoring."
|
|
||||||
if _, err := bd.CreateAgentBead(deaconID, desc, fields); err != nil {
|
|
||||||
return fmt.Errorf("creating %s: %w", deaconID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mayorID := beads.MayorBeadID()
|
|
||||||
if _, err := bd.Show(mayorID); err != nil {
|
|
||||||
fields := &beads.AgentFields{
|
|
||||||
RoleType: "mayor",
|
|
||||||
Rig: "",
|
|
||||||
AgentState: "idle",
|
|
||||||
RoleBead: "gt-mayor-role",
|
|
||||||
}
|
|
||||||
desc := "Mayor - global coordinator, handles cross-rig communication and escalations."
|
|
||||||
if _, err := bd.CreateAgentBead(mayorID, desc, fields); err != nil {
|
|
||||||
return fmt.Errorf("creating %s: %w", mayorID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user