Documentation
¶
Overview ¶
Package daemon provides the town-level background service for Gas Town.
The daemon is a simple Go process (not an agent) that: 1. Pokes agents periodically (heartbeat) 2. Processes lifecycle requests (cycle, restart, shutdown) 3. Restarts sessions when agents request cycling
The daemon is a "dumb scheduler" - all intelligence is in agents.
Index ¶
- Constants
- func IsRunning(townRoot string) (bool, int, error)
- func SaveState(townRoot string, state *State) error
- func StateFile(townRoot string) string
- func StopDaemon(townRoot string) error
- type AgentBeadInfo
- type BeadsMessage
- type Config
- type Daemon
- type LifecycleAction
- type LifecycleBody
- type LifecycleRequest
- type NotificationManager
- func (m *NotificationManager) ClearSlot(session, slot string) error
- func (m *NotificationManager) ClearStaleSlots() error
- func (m *NotificationManager) GetSlot(session, slot string) (*NotificationSlot, error)
- func (m *NotificationManager) MarkConsumed(session, slot string) error
- func (m *NotificationManager) MarkSessionActive(session string) error
- func (m *NotificationManager) RecordSend(session, slot, message string) error
- func (m *NotificationManager) ShouldSend(session, slot string) (bool, error)
- type NotificationSlot
- type ParsedIdentity
- type State
Constants ¶
const ( SlotHeartbeat = "heartbeat" SlotStatus = "status" )
Common notification slots
const DeaconRole = "deacon"
DeaconRole is the role name for the Deacon's handoff bead.
const GUPPViolationTimeout = 30 * time.Minute
GUPPViolationTimeout is how long an agent can have work on hook without progressing before it's considered a GUPP (Gas Town Universal Propulsion Principle) violation. GUPP states: if you have work on your hook, you run it.
const MaxLifecycleMessageAge = 6 * time.Hour
MaxLifecycleMessageAge is the maximum age of a lifecycle message before it's ignored. Messages older than this are considered stale and deleted without execution.
Variables ¶
This section is empty.
Functions ¶
func IsRunning ¶
IsRunning checks if a daemon is running for the given town. It checks the PID file and verifies the process is alive. Note: The file lock in Run() is the authoritative mechanism for preventing duplicate daemons. This function is for status checks and cleanup.
func StopDaemon ¶
StopDaemon stops the running daemon for the given town. Note: The file lock in Run() prevents multiple daemons per town, so we only need to kill the process from the PID file.
Types ¶
type AgentBeadInfo ¶
type AgentBeadInfo struct {
ID string `json:"id"`
Type string `json:"issue_type"`
State string // Parsed from description: agent_state
HookBead string // Parsed from description: hook_bead
RoleBead string // Parsed from description: role_bead
RoleType string // Parsed from description: role_type
Rig string // Parsed from description: rig
LastUpdate string `json:"updated_at"`
}
AgentBeadInfo represents the parsed fields from an agent bead.
type BeadsMessage ¶
type BeadsMessage struct {
ID string `json:"id"`
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
Timestamp string `json:"timestamp"`
Read bool `json:"read"`
Priority string `json:"priority"`
Type string `json:"type"`
}
BeadsMessage represents a message from gt mail inbox --json.
type Config ¶
type Config struct {
// HeartbeatInterval is how often to poke agents.
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
// TownRoot is the Gas Town workspace root.
TownRoot string `json:"town_root"`
// LogFile is the path to the daemon log file.
LogFile string `json:"log_file"`
// PidFile is the path to the PID file.
PidFile string `json:"pid_file"`
}
Config holds daemon configuration.
func DefaultConfig ¶
DefaultConfig returns the default daemon configuration.
type Daemon ¶
type Daemon struct {
// contains filtered or unexported fields
}
Daemon is the town-level background service. It ensures patrol agents (Deacon, Witnesses) are running and detects failures. This is recovery-focused: normal wake is handled by feed subscription (bd activity --follow). The daemon is the safety net for dead sessions, GUPP violations, and orphaned work.
func (*Daemon) ProcessLifecycleRequests ¶
func (d *Daemon) ProcessLifecycleRequests()
ProcessLifecycleRequests checks for and processes lifecycle requests from the deacon inbox.
type LifecycleAction ¶
type LifecycleAction string
LifecycleAction represents a lifecycle request action.
const ( // ActionCycle restarts the session with handoff. ActionCycle LifecycleAction = "cycle" // ActionRestart does a fresh restart without handoff. ActionRestart LifecycleAction = "restart" // ActionShutdown terminates without restart. ActionShutdown LifecycleAction = "shutdown" )
type LifecycleBody ¶
type LifecycleBody struct {
Action string `json:"action"`
}
LifecycleBody is the structured body format for lifecycle requests. Agent should send mail with JSON body: {"action": "cycle"} or {"action": "shutdown"}
type LifecycleRequest ¶
type LifecycleRequest struct {
// From is the agent requesting the action (e.g., "mayor/", "gastown/witness").
From string `json:"from"`
// Action is what lifecycle action to perform.
Action LifecycleAction `json:"action"`
// Timestamp is when the request was made.
Timestamp time.Time `json:"timestamp"`
}
LifecycleRequest represents a request from an agent to the daemon.
type NotificationManager ¶
type NotificationManager struct {
// contains filtered or unexported fields
}
NotificationManager handles slot-based notification deduplication. It ensures that for a given (session, slot) pair, only one notification is pending at a time. Sending a new notification to the same slot replaces the previous one.
func NewNotificationManager ¶
func NewNotificationManager(stateDir string, maxAge time.Duration) *NotificationManager
NewNotificationManager creates a new notification manager. stateDir is where slot state files are stored (e.g., ~/gt/daemon/notifications/)
func (*NotificationManager) ClearSlot ¶
func (m *NotificationManager) ClearSlot(session, slot string) error
ClearSlot removes the state file for a slot.
func (*NotificationManager) ClearStaleSlots ¶
func (m *NotificationManager) ClearStaleSlots() error
ClearStaleSlots removes slot files older than maxAge.
func (*NotificationManager) GetSlot ¶
func (m *NotificationManager) GetSlot(session, slot string) (*NotificationSlot, error)
GetSlot reads the current state of a notification slot.
func (*NotificationManager) MarkConsumed ¶
func (m *NotificationManager) MarkConsumed(session, slot string) error
MarkConsumed marks a slot's notification as consumed (agent responded).
func (*NotificationManager) MarkSessionActive ¶
func (m *NotificationManager) MarkSessionActive(session string) error
MarkSessionActive marks all slots for a session as consumed. Call this when the session shows activity (keepalive update).
func (*NotificationManager) RecordSend ¶
func (m *NotificationManager) RecordSend(session, slot, message string) error
RecordSend records that a notification was sent for a slot.
func (*NotificationManager) ShouldSend ¶
func (m *NotificationManager) ShouldSend(session, slot string) (bool, error)
ShouldSend checks if a notification should be sent for this slot. Returns true if: - No pending notification exists for this slot - The pending notification is stale (older than maxAge) - The pending notification was consumed
type NotificationSlot ¶
type NotificationSlot struct {
Slot string `json:"slot"`
Session string `json:"session"`
Message string `json:"message"`
SentAt time.Time `json:"sent_at"`
Consumed bool `json:"consumed"`
ConsumedAt time.Time `json:"consumed_at,omitempty"`
}
NotificationSlot tracks a pending notification for deduplication. Only the latest notification per slot matters - earlier ones are replaced.
type ParsedIdentity ¶
type ParsedIdentity struct {
RoleType string // mayor, deacon, witness, refinery, crew, polecat
RigName string // Empty for town-level agents (mayor, deacon)
AgentName string // Empty for singletons (mayor, deacon, witness, refinery)
}
ParsedIdentity holds the components extracted from an agent identity string. This is used to look up the appropriate role bead for lifecycle config.
type State ¶
type State struct {
// Running indicates if the daemon is running.
Running bool `json:"running"`
// PID is the process ID of the daemon.
PID int `json:"pid"`
// StartedAt is when the daemon started.
StartedAt time.Time `json:"started_at"`
// LastHeartbeat is when the last heartbeat completed.
LastHeartbeat time.Time `json:"last_heartbeat"`
// HeartbeatCount is how many heartbeats have completed.
HeartbeatCount int64 `json:"heartbeat_count"`
}
State represents the daemon's runtime state.