alarm

package
v1.11.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package alarm provides a one-shot, absolute-time wake scheduler and the alarm_create / alarm_list / alarm_cancel tools built on it.

Unlike schedule_wakeup — a blocking, relative sleep capped at one hour — an alarm fires at an absolute wall-clock instant arbitrarily far in the future, survives process restarts (when durable), and never holds a goroutine: it registers a time.AfterFunc and returns immediately. On fire the scheduler invokes OnFire, which the agent wires to WakeupQueue.Enqueue plus a loop wake-up signal — reusing the exact delivery path schedule_wakeup and daemons already use (drainWakeupPrompts at the top of every loop iteration, and the signal-wake that starts a fresh run when the agent is idle).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Names

func Names() []tools.ToolName

Names lists every tool name this package contributes.

func ParseFireTime

func ParseFireTime(s string, loc *time.Location) (time.Time, error)

ParseFireTime parses a user-supplied timestamp into an absolute instant. Accepted forms, tried in order: RFC3339 (explicit offset honored), then the local layouts "2006-01-02 15:04:05" / "...T..." and their minute-only variants (seconds default to :00), interpreted in loc.

Types

type Alarm

type Alarm struct {
	ID      string    `json:"id"`
	FireAt  time.Time `json:"fire_at"`
	Prompt  string    `json:"prompt"`
	Label   string    `json:"label,omitempty"`
	Durable bool      `json:"durable"`
	Created time.Time `json:"created"`

	// Target is the agent to wake (swarm). "" => the caller/self.
	Target string `json:"target,omitempty"`
	// Origin is the agent that set the alarm (swarm). "" in single-agent use.
	Origin string `json:"origin,omitempty"`

	// CronExpr is a 5-field cron expression. When empty, the alarm is a plain
	// one-shot fired at FireAt. When set, FireAt is derived from the expression
	// and the entry is a cron job (shown by cron_list, banner says "Cron job").
	CronExpr string `json:"cron_expr,omitempty"`

	// Recurring distinguishes the two cron modes (ignored when CronExpr == "").
	// true => re-arm at the next match after each fire until deleted or expired.
	// false => a one-shot cron: fire once at the next match, then self-remove.
	Recurring bool `json:"recurring,omitempty"`

	// Expiry is the time after which a recurring cron job auto-expires. Zero
	// means no expiry; set to Created + 7 days for recurring jobs (one-shot
	// crons and plain alarms leave it zero — they fire once regardless).
	Expiry time.Time `json:"expiry,omitzero"`
}

Alarm is one scheduled wake — either a one-shot alarm or a recurring cron job.

Target and Origin are only meaningful in a multi-agent (swarm) context: the OnFire wiring routes the wake to Target and labels it as coming from Origin. In single-agent use both are empty and the wake goes to the caller itself.

type CancelTool

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

CancelTool implements ALARM_CANCEL.

func NewCancel

func NewCancel(s *Scheduler) *CancelTool

NewCancel constructs an ALARM_CANCEL tool bound to the given scheduler.

func (*CancelTool) Description

func (t *CancelTool) Description() string

func (*CancelTool) Execute

func (t *CancelTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)

func (*CancelTool) Name

func (t *CancelTool) Name() string

func (*CancelTool) Schema

func (t *CancelTool) Schema() json.RawMessage

type Config

type Config struct {
	// OnFire is invoked on a background goroutine each time an alarm fires.
	// nil is allowed — fires become no-ops, useful in tests that only assert
	// scheduling state. The agent wires this to WakeupQueue.Enqueue + a wake
	// signal.
	OnFire func(Fired)

	// StorePath is the JSON file durable alarms persist to. "" disables
	// persistence entirely (every alarm becomes session-only regardless of its
	// Durable flag — typical for tests and config-less harnesses).
	StorePath string

	// Now injects the clock for tests. nil => time.Now.
	Now func() time.Time

	// MaxPending overrides defaultMaxPending when > 0.
	MaxPending int

	// CronNext computes the next fire time for a cron expression strictly after
	// `after`. Injected by the cron tools package to decouple the scheduler from
	// the cron engine (avoids a circular import). nil disables recurring jobs
	// (Arm rejects any Alarm with a non-empty CronExpr).
	CronNext func(expr string, after time.Time) (time.Time, error)

	// Jitter returns the extra delay added to each cron fire to avoid a
	// thundering herd when jobs share an expression. nil => a uniform random in
	// [0, maxJitter). Tests inject a deterministic function (e.g. always 0).
	Jitter func() time.Duration
}

Config configures a Scheduler.

type CreateTool

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

CreateTool implements ALARM_CREATE. Execute is non-blocking: it arms a one-shot timer on the shared Scheduler and returns immediately.

func NewCreate

func NewCreate(s *Scheduler) *CreateTool

NewCreate constructs an ALARM_CREATE tool bound to the given scheduler — the same instance the agent's fire callback (WakeupQueue + wake signal) is wired into.

func (*CreateTool) Description

func (t *CreateTool) Description() string

func (*CreateTool) Execute

func (t *CreateTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)

func (*CreateTool) Name

func (t *CreateTool) Name() string

func (*CreateTool) Schema

func (t *CreateTool) Schema() json.RawMessage

type Fired

type Fired struct {
	Alarm
	Late      bool
	Recurring bool
	NextFire  time.Time // for recurring jobs, the next scheduled fire time
}

Fired is the payload handed to OnFire when an alarm triggers. Late is true when the alarm's instant had already passed by the time it fired — the restart-recovery path (LoadAndRearm) fires past-due durable alarms once so a wake set before a downtime is not silently dropped.

func (Fired) Message

func (f Fired) Message() string

Message renders the user-message body injected into the conversation when this alarm fires: an ⏰ banner carrying the fire instant (offset-stamped, so the woken agent knows what time it is) plus label and an optional late marker, followed by the alarm's own prompt.

type ListTool

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

ListTool implements ALARM_LIST.

func NewList

func NewList(s *Scheduler) *ListTool

NewList constructs an ALARM_LIST tool bound to the given scheduler.

func (*ListTool) Description

func (t *ListTool) Description() string

func (*ListTool) Execute

func (t *ListTool) Execute(_ context.Context, _ *slog.Logger, _ json.RawMessage) (tools.Result, error)

func (*ListTool) Name

func (t *ListTool) Name() string

func (*ListTool) Schema

func (t *ListTool) Schema() json.RawMessage

type Scheduler

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

Scheduler owns the set of pending alarms and their timers. All methods are safe for concurrent use; fires run on time.AfterFunc goroutines.

func New

func New(cfg Config) *Scheduler

New constructs a Scheduler from cfg. It does not read the durable store — call LoadAndRearm once at startup to reinstate persisted alarms.

func (*Scheduler) Arm

func (s *Scheduler) Arm(a Alarm) (Alarm, error)

Arm validates and schedules an alarm. ID and Created are assigned here when empty. For one-shot alarms (CronExpr == ""), FireAt must be strictly in the future. For recurring cron jobs, FireAt is computed from CronExpr if zero. Returns the stored alarm with its assigned ID.

func (*Scheduler) Cancel

func (s *Scheduler) Cancel(id string) bool

Cancel removes a pending alarm by id, stopping its timer. Reports whether an alarm with that id existed.

func (*Scheduler) List

func (s *Scheduler) List() []Alarm

List returns every pending alarm, sorted by fire time (soonest first).

func (*Scheduler) LoadAndRearm

func (s *Scheduler) LoadAndRearm() error

LoadAndRearm is Rearm plus firing each past-due alarm through OnFire immediately. Use it where immediate delivery at startup is safe — e.g. the swarm, where a fire is a durable bus message the target drains when its loop starts.

func (*Scheduler) Pending

func (s *Scheduler) Pending() int

Pending reports the number of armed alarms.

func (*Scheduler) Rearm

func (s *Scheduler) Rearm() ([]Fired, error)

Rearm reinstates persisted alarms at startup: every future alarm gets a fresh timer, and every already-due one-shot alarm (its instant passed while the process was down) is returned — flagged late — for the caller to deliver, WITHOUT firing OnFire here. Past-due one-shot alarms are dropped from the store before returning.

For recurring cron jobs that are past-due, the next fire time is computed from now and the job is re-armed (no "late" fire — catching up on missed recurring fires would be wasteful).

Splitting "re-arm" from "fire past-due" lets a host that must not start work before its UI is live (the solo TUI) deliver missed alarms on the next run instead of autonomously at boot; callers for whom immediate delivery is safe (durable bus mail in the swarm) use LoadAndRearm. No-op (nil) when no store path is configured or the file is absent.

func (*Scheduler) SetCronNext added in v1.8.0

func (s *Scheduler) SetCronNext(fn func(expr string, after time.Time) (time.Time, error))

SetCronNext installs the cron expression evaluator. Call this before arming any recurring jobs. It is safe to call once at startup.

func (*Scheduler) Stop

func (s *Scheduler) Stop()

Stop halts every pending timer so no alarm fires during shutdown. Durable alarms remain on disk and are reinstated by LoadAndRearm on the next start; session-only alarms are simply lost. Safe to call multiple times.

Jump to

Keyboard shortcuts

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