daemon

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 27 Imported by: 0

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

View Source
const (
	SlotHeartbeat = "heartbeat"
	SlotStatus    = "status"
)

Common notification slots

View Source
const DeaconRole = "deacon"

DeaconRole is the role name for the Deacon's handoff bead.

View Source
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.

View Source
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

func IsRunning(townRoot string) (bool, int, error)

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 SaveState

func SaveState(townRoot string, state *State) error

SaveState saves daemon state to disk using atomic write.

func StateFile

func StateFile(townRoot string) string

StateFile returns the path to the state file.

func StopDaemon

func StopDaemon(townRoot string) error

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

func DefaultConfig(townRoot string) *Config

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 New

func New(config *Config) (*Daemon, error)

New creates a new daemon instance.

func (*Daemon) ProcessLifecycleRequests

func (d *Daemon) ProcessLifecycleRequests()

ProcessLifecycleRequests checks for and processes lifecycle requests from the deacon inbox.

func (*Daemon) Run

func (d *Daemon) Run() error

Run starts the daemon main loop.

func (*Daemon) Stop

func (d *Daemon) Stop()

Stop signals the daemon to stop.

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.

func LoadState

func LoadState(townRoot string) (*State, error)

LoadState loads daemon state from disk.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL