deacon

package
v0.1.1 Latest Latest
Warning

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

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

Documentation

Overview

Package deacon provides the Deacon agent infrastructure. The Deacon is an agent that monitors Mayor and Witnesses, handles lifecycle requests, and keeps Gas Town running.

Package deacon provides the Deacon agent infrastructure.

Package deacon provides the Deacon agent infrastructure.

Index

Constants

View Source
const (
	DefaultPingTimeout         = 30 * time.Second // How long to wait for response
	DefaultConsecutiveFailures = 3                // Failures before force-kill
	DefaultCooldown            = 5 * time.Minute  // Minimum time between force-kills
)

Default parameters for stuck-session detection.

Variables

View Source
var (
	ErrNotRunning     = errors.New("deacon not running")
	ErrAlreadyRunning = errors.New("deacon already running")
)

Common errors

View Source
var (
	ErrAgentInCooldown = errors.New("agent is in cooldown period after recent force-kill")
	ErrAgentNotFound   = errors.New("agent not found or session doesn't exist")
	ErrAgentResponsive = errors.New("agent is responsive, no action needed")
)

Common errors for stuck-session detection.

Functions

func HealthCheckStateFile

func HealthCheckStateFile(townRoot string) string

HealthCheckStateFile returns the path to the health check state file.

func HeartbeatFile

func HeartbeatFile(townRoot string) string

HeartbeatFile returns the path to the Deacon heartbeat file.

func SaveHealthCheckState

func SaveHealthCheckState(townRoot string, state *HealthCheckState) error

SaveHealthCheckState saves the health check state to disk.

func SessionName

func SessionName() string

SessionName returns the tmux session name for the deacon. This is a package-level function for convenience.

func Touch

func Touch(townRoot string) error

Touch writes a minimal heartbeat with just the timestamp. This is a convenience function for simple heartbeat updates.

func TouchWithAction

func TouchWithAction(townRoot, action string, healthy, unhealthy int) error

TouchWithAction writes a heartbeat with an action description.

func WriteHeartbeat

func WriteHeartbeat(townRoot string, hb *Heartbeat) error

WriteHeartbeat writes a new heartbeat to disk. Called by the Deacon at the start of each wake cycle.

Types

type AgentHealthState

type AgentHealthState struct {
	// AgentID is the identifier (e.g., "gastown/polecats/max" or "deacon")
	AgentID string `json:"agent_id"`

	// LastPingTime is when we last sent a HEALTH_CHECK nudge
	LastPingTime time.Time `json:"last_ping_time,omitempty"`

	// LastResponseTime is when the agent last updated their activity
	LastResponseTime time.Time `json:"last_response_time,omitempty"`

	// ConsecutiveFailures counts how many health checks failed in a row
	ConsecutiveFailures int `json:"consecutive_failures"`

	// LastForceKillTime is when we last force-killed this agent
	LastForceKillTime time.Time `json:"last_force_kill_time,omitempty"`

	// ForceKillCount is total number of force-kills for this agent
	ForceKillCount int `json:"force_kill_count"`
}

AgentHealthState tracks the health check state for a single agent.

func (*AgentHealthState) CooldownRemaining

func (s *AgentHealthState) CooldownRemaining(cooldown time.Duration) time.Duration

CooldownRemaining returns how long until cooldown expires.

func (*AgentHealthState) IsInCooldown

func (s *AgentHealthState) IsInCooldown(cooldown time.Duration) bool

IsInCooldown returns true if the agent was recently force-killed.

func (*AgentHealthState) RecordFailure

func (s *AgentHealthState) RecordFailure()

RecordFailure records that an agent failed to respond to a health check.

func (*AgentHealthState) RecordForceKill

func (s *AgentHealthState) RecordForceKill()

RecordForceKill records that an agent was force-killed.

func (*AgentHealthState) RecordPing

func (s *AgentHealthState) RecordPing()

RecordPing records that a health check ping was sent to an agent.

func (*AgentHealthState) RecordResponse

func (s *AgentHealthState) RecordResponse()

RecordResponse records that an agent responded to a health check. This resets the consecutive failure counter.

func (*AgentHealthState) ShouldForceKill

func (s *AgentHealthState) ShouldForceKill(threshold int) bool

ShouldForceKill returns true if the agent has exceeded the failure threshold.

type HealthCheckResult

type HealthCheckResult struct {
	AgentID             string        `json:"agent_id"`
	Responded           bool          `json:"responded"`
	ResponseTime        time.Duration `json:"response_time,omitempty"`
	ConsecutiveFailures int           `json:"consecutive_failures"`
	ShouldForceKill     bool          `json:"should_force_kill"`
	InCooldown          bool          `json:"in_cooldown"`
	CooldownRemaining   time.Duration `json:"cooldown_remaining,omitempty"`
}

HealthCheckResult represents the outcome of a health check.

type HealthCheckState

type HealthCheckState struct {
	// Agents maps agent ID to their health state
	Agents map[string]*AgentHealthState `json:"agents"`

	// LastUpdated is when this state was last written
	LastUpdated time.Time `json:"last_updated"`
}

HealthCheckState holds health check state for all monitored agents.

func LoadHealthCheckState

func LoadHealthCheckState(townRoot string) (*HealthCheckState, error)

LoadHealthCheckState loads the health check state from disk. Returns empty state if file doesn't exist.

func (*HealthCheckState) GetAgentState

func (s *HealthCheckState) GetAgentState(agentID string) *AgentHealthState

GetAgentState returns the health state for an agent, creating if needed.

type Heartbeat

type Heartbeat struct {
	// Timestamp is when the heartbeat was written.
	Timestamp time.Time `json:"timestamp"`

	// Cycle is the current wake cycle number.
	Cycle int64 `json:"cycle"`

	// LastAction describes what the Deacon did in this cycle.
	LastAction string `json:"last_action,omitempty"`

	// HealthyAgents is the count of healthy agents observed.
	HealthyAgents int `json:"healthy_agents"`

	// UnhealthyAgents is the count of unhealthy agents observed.
	UnhealthyAgents int `json:"unhealthy_agents"`
}

Heartbeat represents the Deacon's heartbeat file contents. Written by the Deacon on each wake cycle. Read by the Go daemon to decide whether to poke.

func ReadHeartbeat

func ReadHeartbeat(townRoot string) *Heartbeat

ReadHeartbeat reads the Deacon heartbeat from disk. Returns nil if the file doesn't exist or can't be read.

func (*Heartbeat) Age

func (hb *Heartbeat) Age() time.Duration

Age returns how old the heartbeat is. Returns a very large duration if the heartbeat is nil.

func (*Heartbeat) IsFresh

func (hb *Heartbeat) IsFresh() bool

IsFresh returns true if the heartbeat is less than 5 minutes old. A fresh heartbeat means the Deacon is actively working or recently finished.

func (*Heartbeat) IsStale

func (hb *Heartbeat) IsStale() bool

IsStale returns true if the heartbeat is 5-15 minutes old. A stale heartbeat may indicate the Deacon is doing a long operation.

func (*Heartbeat) IsVeryStale

func (hb *Heartbeat) IsVeryStale() bool

IsVeryStale returns true if the heartbeat is more than 15 minutes old. A very stale heartbeat means the Deacon should be poked.

func (*Heartbeat) ShouldPoke

func (hb *Heartbeat) ShouldPoke() bool

ShouldPoke returns true if the daemon should poke the Deacon. The Deacon should be poked if: - No heartbeat exists - Heartbeat is very stale (>5 minutes)

type HookedBead

type HookedBead struct {
	ID        string    `json:"id"`
	Title     string    `json:"title"`
	Status    string    `json:"status"`
	Assignee  string    `json:"assignee"`
	UpdatedAt time.Time `json:"updated_at"`
}

HookedBead represents a bead in hooked status from bd list output.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager handles deacon lifecycle operations.

func NewManager

func NewManager(townRoot string) *Manager

NewManager creates a new deacon manager for a town.

func (*Manager) IsRunning

func (m *Manager) IsRunning() (bool, error)

IsRunning checks if the deacon session is active.

func (*Manager) SessionName

func (m *Manager) SessionName() string

SessionName returns the tmux session name for the deacon.

func (*Manager) Start

func (m *Manager) Start() error

Start starts the deacon session. The deacon runs in a respawn loop for automatic recovery.

func (*Manager) Status

func (m *Manager) Status() (*tmux.SessionInfo, error)

Status returns information about the deacon session.

func (*Manager) Stop

func (m *Manager) Stop() error

Stop stops the deacon session.

type StaleHookConfig

type StaleHookConfig struct {
	// MaxAge is how long a bead can be hooked before being considered stale.
	MaxAge time.Duration `json:"max_age"`
	// DryRun if true, only reports what would be done without making changes.
	DryRun bool `json:"dry_run"`
}

StaleHookConfig holds configurable parameters for stale hook detection.

func DefaultStaleHookConfig

func DefaultStaleHookConfig() *StaleHookConfig

DefaultStaleHookConfig returns the default stale hook config.

type StaleHookResult

type StaleHookResult struct {
	BeadID     string `json:"bead_id"`
	Title      string `json:"title"`
	Assignee   string `json:"assignee"`
	Age        string `json:"age"`
	AgentAlive bool   `json:"agent_alive"`
	Unhooked   bool   `json:"unhooked"`
	Error      string `json:"error,omitempty"`
}

StaleHookResult represents the result of processing a stale hooked bead.

type StaleHookScanResult

type StaleHookScanResult struct {
	ScannedAt   time.Time          `json:"scanned_at"`
	TotalHooked int                `json:"total_hooked"`
	StaleCount  int                `json:"stale_count"`
	Unhooked    int                `json:"unhooked"`
	Results     []*StaleHookResult `json:"results"`
}

StaleHookScanResult contains the full results of a stale hook scan.

func ScanStaleHooks

func ScanStaleHooks(townRoot string, cfg *StaleHookConfig) (*StaleHookScanResult, error)

ScanStaleHooks finds hooked beads older than the threshold and optionally unhooks them.

type StuckConfig

type StuckConfig struct {
	PingTimeout         time.Duration `json:"ping_timeout"`
	ConsecutiveFailures int           `json:"consecutive_failures"`
	Cooldown            time.Duration `json:"cooldown"`
}

StuckConfig holds configurable parameters for stuck-session detection.

func DefaultStuckConfig

func DefaultStuckConfig() *StuckConfig

DefaultStuckConfig returns the default stuck detection config.

Jump to

Keyboard shortcuts

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