From b5c695075a2f35bfd0d94fc133c0eaf8b4bbc916 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 27 Dec 2025 23:44:39 -0800 Subject: [PATCH] Add agent-specific fields to bead schema (gt-v2gkv) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent identity fields on Issue struct: - HookBead: reference to current work (0..1 cardinality) - RoleBead: reference to role definition bead - AgentState: self-reported state (idle|spawning|running|working|stuck|done|stopped|dead) - LastActivity: timestamp for heartbeat/timeout detection - RoleType: agent type (polecat|crew|witness|refinery|mayor|deacon) - Rig: rig name (empty for town-level agents) Also adds: - AgentState type with IsValid() method - Validation for agent state in Issue.Validate() - Agent fields included in content hash This enables the agent-as-bead architecture where agents are tracked as first-class beads with self-reported state. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/types/types.go | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/internal/types/types.go b/internal/types/types.go index d5bf846a..d7d0e521 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -69,8 +69,16 @@ type Issue struct { Waiters []string `json:"waiters,omitempty"` // Mail addresses to notify when gate clears // Source tracing fields (gt-8tmz.18): track where this issue came from during cooking - SourceFormula string `json:"source_formula,omitempty"` // Formula name where this step was defined - SourceLocation string `json:"source_location,omitempty"` // Path within source: "steps[0]", "advice[0].after" + SourceFormula string `json:"source_formula,omitempty"` // Formula name where this step was defined + SourceLocation string `json:"source_location,omitempty"` // Path within source: "steps[0]", "advice[0].after" + + // Agent identity fields (gt-v2gkv): agent-as-bead support + HookBead string `json:"hook_bead,omitempty"` // Current work attached to agent's hook (0..1) + RoleBead string `json:"role_bead,omitempty"` // Role definition bead (required for agents) + AgentState AgentState `json:"agent_state,omitempty"` // Agent-reported state (idle|running|stuck|stopped) + LastActivity *time.Time `json:"last_activity,omitempty"` // Updated on each agent action (for timeout detection) + RoleType string `json:"role_type,omitempty"` // Agent role: polecat|crew|witness|refinery|mayor|deacon + Rig string `json:"rig,omitempty"` // Rig name (empty for town-level agents like mayor/deacon) } // ComputeContentHash creates a deterministic hash of the issue's content. @@ -165,6 +173,18 @@ func (i *Issue) ComputeContentHash() string { h.Write([]byte(waiter)) h.Write([]byte{0}) } + // Hash agent identity fields (gt-v2gkv) + h.Write([]byte(i.HookBead)) + h.Write([]byte{0}) + h.Write([]byte(i.RoleBead)) + h.Write([]byte{0}) + h.Write([]byte(i.AgentState)) + h.Write([]byte{0}) + h.Write([]byte(i.RoleType)) + h.Write([]byte{0}) + h.Write([]byte(i.Rig)) + h.Write([]byte{0}) + // Note: LastActivity is a timestamp, excluded from hash like other timestamps return fmt.Sprintf("%x", h.Sum(nil)) } @@ -263,6 +283,10 @@ func (i *Issue) ValidateWithCustomStatuses(customStatuses []string) error { if i.Status != StatusTombstone && i.DeletedAt != nil { return fmt.Errorf("non-tombstone issues cannot have deleted_at timestamp") } + // Validate agent state if set (gt-v2gkv) + if !i.AgentState.IsValid() { + return fmt.Errorf("invalid agent state: %s", i.AgentState) + } return nil } @@ -353,6 +377,30 @@ func (t IssueType) IsValid() bool { return false } +// AgentState represents the self-reported state of an agent (gt-v2gkv) +type AgentState string + +// Agent state constants +const ( + StateIdle AgentState = "idle" // Agent is waiting for work + StateSpawning AgentState = "spawning" // Agent is starting up + StateRunning AgentState = "running" // Agent is executing (general) + StateWorking AgentState = "working" // Agent is actively working on a task + StateStuck AgentState = "stuck" // Agent is blocked and needs help + StateDone AgentState = "done" // Agent completed its current work + StateStopped AgentState = "stopped" // Agent has cleanly shut down + StateDead AgentState = "dead" // Agent died without clean shutdown (timeout detection) +) + +// IsValid checks if the agent state value is valid +func (s AgentState) IsValid() bool { + switch s { + case StateIdle, StateSpawning, StateRunning, StateWorking, StateStuck, StateDone, StateStopped, StateDead, "": + return true // empty is valid (non-agent beads) + } + return false +} + // Dependency represents a relationship between issues type Dependency struct { IssueID string `json:"issue_id"`