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 ¶
- func Names() []tools.ToolName
- func ParseFireTime(s string, loc *time.Location) (time.Time, error)
- type Alarm
- type CancelTool
- type Config
- type CreateTool
- type Fired
- type ListTool
- type Scheduler
- func (s *Scheduler) Arm(a Alarm) (Alarm, error)
- func (s *Scheduler) Cancel(id string) bool
- func (s *Scheduler) List() []Alarm
- func (s *Scheduler) LoadAndRearm() error
- func (s *Scheduler) Pending() int
- func (s *Scheduler) Rearm() ([]Fired, error)
- func (s *Scheduler) SetCronNext(fn func(expr string, after time.Time) (time.Time, error))
- func (s *Scheduler) Stop()
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseFireTime ¶
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.
type ListTool ¶
type ListTool struct {
// contains filtered or unexported fields
}
ListTool implements ALARM_LIST.
func (*ListTool) Description ¶
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 ¶
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 ¶
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 ¶
Cancel removes a pending alarm by id, stopping its timer. Reports whether an alarm with that id existed.
func (*Scheduler) LoadAndRearm ¶
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) Rearm ¶
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
SetCronNext installs the cron expression evaluator. Call this before arming any recurring jobs. It is safe to call once at startup.