Documentation
¶
Index ¶
- Constants
- Variables
- func RunSessionIDFromContext(ctx context.Context) string
- func WithRunSessionID(ctx context.Context, sid string) context.Context
- type BuiltinJob
- type ChatFunc
- type Decision
- type HeartbeatConfig
- type Job
- type JobRun
- type ListActiveUsersFunc
- type OnJobFunc
- type Schedule
- type Service
- func (s *Service) AddJobForContext(ctx context.Context, name, message string, sched Schedule, sessionMode string) (Job, error)
- func (s *Service) AddJobWithOwner(name, message string, sched Schedule, sessionMode, agentID string, ...) (Job, error)
- func (s *Service) AddOnJobListener(fn OnJobFunc)
- func (s *Service) AddPluginJob(ctx context.Context, pluginID, key, runtimeName, name, description string, ...) (Job, error)
- func (s *Service) EnsureBuiltinJobs()
- func (s *Service) EnsureJob(name, message string, sched Schedule, sessionMode, agentID, execScope string) (Job, error)
- func (s *Service) ListJobRuns(ctx context.Context, jobID string, limit int) ([]JobRun, error)
- func (s *Service) ListJobs() []Job
- func (s *Service) RegisterBuiltin(job BuiltinJob) error
- func (s *Service) RemoveJob(id string) error
- func (s *Service) RunJobNow(ctx context.Context, jobID string) (string, error)
- func (s *Service) ScheduleEvery(ctx context.Context, every string, fn TaskFunc) error
- func (s *Service) SetHeartbeat(cfg HeartbeatConfig, chat ChatFunc, notifier notify.Notifier)
- func (s *Service) SetListActiveUsersFunc(fn ListActiveUsersFunc)
- func (s *Service) SetOnJob(fn OnJobFunc)
- func (s *Service) SetUserJobsEnabled(enabled bool)
- func (s *Service) Start(ctx context.Context) error
- func (s *Service) StartEphemeral(ctx context.Context) error
- func (s *Service) StartHeartbeat(ctx context.Context, every string) error
- func (s *Service) Stop() error
- type TaskFunc
Constants ¶
const ( // SessionReuse reuses the same session across job executions (default). SessionReuse = "reuse" // SessionNew creates a fresh session for each execution. SessionNew = "new" )
const ( // ExecScopeSystem runs once with no user context (system workspace). ExecScopeSystem = "system" // ExecScopeUser runs once with a specific user's context (UserID must be set). ExecScopeUser = "user" // ExecScopeAllUsers fans out: runs once per active user, each with that user's context. ExecScopeAllUsers = "all_users" )
ExecScope constants define how a job runs at execution time.
const ( JobOwnerUser = "user" JobOwnerPlugin = "plugin" JobOwnerSystem = "system" )
Job is the persisted job definition.
const ( RunStatusRunning = "running" RunStatusSuccess = "success" RunStatusError = "error" )
Variables ¶
var RecallyDigestBuiltin = BuiltinJob{ Name: "recally-digest", Message: `Load the recally skill. Then generate and send a daily reading digest: 1. Run "stella recally digest" to get today's digest data. 2. If saved_yesterday_count is 0 AND worth_revisiting_count is 0, stop — do NOT notify and do NOT save. 3. Check the user's language preference in memory. If no language preference is found, write the digest in English. 4. Write a newsletter-style narrative in the selected language. It should read like a personal curator wrote it — not a bullet list or a status report. Cover: - A short opening sentence that sets the tone for the day's reading (1–2 sentences). - For each article saved yesterday: weave the titles and summaries into 1–2 flowing sentences that explain why each piece is worth reading, grouping thematically where possible. - A brief "worth revisiting" section: mention the articles by title and why they might be relevant again now. - Close with the trending tags as a loose sentence: "Today's themes span X, Y, and Z." Keep the tone warm, curious, and concise — aim for 150–300 words total. No bullet points, no emoji, no section headers. 5. Call notify once with a short 1-sentence preview of the narrative (the opening sentence). 6. Save the full narrative through a private temporary file: tmpfile="$(mktemp -t stella_digest.XXXXXX)"; chmod 600 "$tmpfile"; write the narrative to "$tmpfile"; run stella recally digest save --narrative "$(cat "$tmpfile")"; then rm -f "$tmpfile".`, Schedule: Schedule{Every: "24h"}, SessionMode: SessionNew, ExecScope: ExecScopeAllUsers, }
RecallyDigestBuiltin is the spec for the daily recally reading digest builtin job. Registered from gateway wiring via (*Service).RegisterBuiltin.
var RecallyRSSBuiltin = BuiltinJob{ Name: "recally-rss", Message: `1. Poll all feeds: run "stella recally feed poll --limit 20 --json" (no feed-id polls every enabled feed). 2. Load the recally skill. For each pending entry, follow the RSS workflow defined in the recally skill (fetch → generate metadata → save → mark). 3. Notify only when at least one article was saved: count articles saved in step 2. If zero, do NOT call the notify tool — stop here. If one or more, call notify once: - For each article (up to 5): Worth-Reading label (emoji + text), title, author, and the "# Summary" section from the structured summary. - If more than 5 were saved, list the remaining as title + author only.`, Schedule: Schedule{Every: "6h"}, SessionMode: SessionNew, ExecScope: ExecScopeAllUsers, }
RecallyRSSBuiltin is the spec for the recurring recally RSS polling builtin job. Registered from gateway wiring via (*Service).RegisterBuiltin.
Functions ¶
func RunSessionIDFromContext ¶
Types ¶
type BuiltinJob ¶
type BuiltinJob struct {
Name string
Message string
Schedule Schedule
SessionMode string
AgentID string
// ExecScope controls how the job runs: ExecScopeSystem (once, no user context),
// ExecScopeUser (once for a specific user), or ExecScopeAllUsers (fan-out per active user).
// Defaults to ExecScopeSystem when empty.
ExecScope string
// Handler, when set, is invoked directly when the job fires instead of
// dispatching the Message through the default OnJob agent path.
Handler OnJobFunc
}
BuiltinJob defines a job that is automatically seeded on scheduler startup.
A spec must run in exactly one mode:
- Message mode: Message != "" — the scheduler fires the default OnJob callback, which routes the message through the agent pool. Used for prompt-driven builtins like recally-rss and recally-digest.
- Handler mode: Handler != nil — the scheduler invokes the Go callback directly, bypassing the agent dispatch. Used for internal subsystems (e.g. reflect) that run native Go code on a schedule.
Setting both, or neither, is rejected at registration time.
type HeartbeatConfig ¶
HeartbeatConfig holds heartbeat-specific settings.
type Job ¶
type Job struct {
ID string `json:"id"`
OwnerKind string `json:"owner_kind,omitempty"`
ExecScope string `json:"exec_scope,omitempty"`
PluginID string `json:"plugin_id,omitempty"`
JobKey string `json:"job_key,omitempty"`
RuntimeName string `json:"runtime_name,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Schedule Schedule `json:"schedule"`
Message string `json:"message,omitempty"`
Payload map[string]any `json:"payload,omitempty"`
SessionMode string `json:"session_mode"` // "reuse" (default) or "new"
Enabled bool `json:"enabled"`
AgentID string `json:"agent_id,omitempty"` // agent to route to (empty = default)
UserID string `json:"user_id,omitempty"` // user context (empty = none)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastRunAt *time.Time `json:"last_run_at,omitempty"`
LastError string `json:"last_error,omitempty"`
}
func (Job) SessionID ¶
SessionID returns the stable session identifier for system or single-user job executions. For all_users fan-out runs, use UserSessionID instead.
func (Job) UserSessionID ¶
UserSessionID returns a user-scoped session ID for all_users fan-out sub-runs. In reuse mode the ID is stable per user; in new mode a timestamp suffix ensures freshness.
type JobRun ¶
type JobRun struct {
ID string `json:"id"`
JobID string `json:"job_id"`
SessionID string `json:"session_id"`
UserID string `json:"user_id,omitempty"`
Status string `json:"status"`
StartedAt time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
Error string `json:"error,omitempty"`
}
type ListActiveUsersFunc ¶
ListActiveUsersFunc returns the IDs of all currently active users. Used by the scheduler to fan out ExecScopeAllUsers jobs.
type Schedule ¶
type Schedule struct {
Cron string `json:"cron,omitempty"` // "0 9 * * 1-5"
Every string `json:"every,omitempty"` // "30m", "2h"
At string `json:"at,omitempty"` // RFC3339: "2024-01-15T14:30:00+08:00"
}
Schedule defines when a job runs. Exactly one field must be set.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages scheduled jobs backed by gocron/v2 with database persistence.
func New ¶
New creates a scheduler service backed by the given database. Call Start to load persisted jobs and begin scheduling.
func NewFromPath ¶
NewFromPath creates a scheduler service that opens its own SQLite database at the given path. The database is closed when Stop is called.
func (*Service) AddJobForContext ¶
func (s *Service) AddJobForContext(ctx context.Context, name, message string, sched Schedule, sessionMode string) (Job, error)
AddJobForContext creates a user-owned job bound to the current execution context.
func (*Service) AddJobWithOwner ¶
func (s *Service) AddJobWithOwner(name, message string, sched Schedule, sessionMode, agentID string, userID string) (Job, error)
AddJobWithOwner creates a user-owned job with explicit owner parameters.
func (*Service) AddOnJobListener ¶
AddOnJobListener appends an additional callback invoked when a job fires.
func (*Service) AddPluginJob ¶
func (s *Service) AddPluginJob(ctx context.Context, pluginID, key, runtimeName, name, description string, sched Schedule, payload map[string]any) (Job, error)
AddPluginJob creates, persists, and schedules a plugin-owned job.
func (*Service) EnsureBuiltinJobs ¶
func (s *Service) EnsureBuiltinJobs()
EnsureBuiltinJobs creates or updates all registered builtin jobs. For ExecScopeAllUsers jobs the scheduler fans out to all active users at execution time.
func (*Service) EnsureJob ¶
func (s *Service) EnsureJob(name, message string, sched Schedule, sessionMode, agentID, execScope string) (Job, error)
EnsureJob creates a job if no job with the same name exists, or updates the existing job when any field has changed. It is intended for builtin jobs that should be seeded on startup. Jobs created by EnsureJob are owned by the system (JobOwnerSystem).
func (*Service) ListJobRuns ¶
ListJobRuns returns recent runs for a job. ListJobRuns returns the latest `limit` runs for a job (first page only, across all users). HTTP callers paginate via the handler's direct query instead; this accessor is for internal callers that just want recent runs.
func (*Service) RegisterBuiltin ¶ added in v0.38.0
func (s *Service) RegisterBuiltin(job BuiltinJob) error
RegisterBuiltin registers a builtin job spec on this Service instance.
Called from gateway wiring during setup. Returns an error on a malformed spec so the gateway can fail startup cleanly. Must be called BEFORE (*Service).Start — handler-mode dispatch is keyed on Name, so persisted jobs loaded by Start can only be routed correctly if the handler is already registered.
func (*Service) RunJobNow ¶
RunJobNow triggers an immediate execution of the given job asynchronously. For ExecScopeAllUsers jobs all user sub-runs are launched; no run ID is returned. For other scopes, returns the run ID of the newly created run record. Returns errJobAlreadyRunning (wrapped) if a run is already active for the job.
func (*Service) ScheduleEvery ¶
ScheduleEvery registers a non-persisted recurring task on the existing scheduler.
func (*Service) SetHeartbeat ¶
func (s *Service) SetHeartbeat(cfg HeartbeatConfig, chat ChatFunc, notifier notify.Notifier)
SetHeartbeat configures the heartbeat on the scheduler service.
func (*Service) SetListActiveUsersFunc ¶
func (s *Service) SetListActiveUsersFunc(fn ListActiveUsersFunc)
SetListActiveUsersFunc registers the function used to enumerate active users when fanning out ExecScopeAllUsers jobs.
func (*Service) SetUserJobsEnabled ¶
SetUserJobsEnabled controls whether persisted user-owned and all_users scheduler jobs are loaded.
func (*Service) StartEphemeral ¶
StartEphemeral starts the shared scheduler without loading persisted jobs. Use this when the scheduler is only needed for internal tasks such as heartbeat.
func (*Service) StartHeartbeat ¶
StartHeartbeat schedules the heartbeat poll on the shared scheduler.
func (*Service) Stop ¶
Stop shuts down the scheduler and closes the database if owned.
Service is single-shot: Start after Stop is not supported. gocron's Shutdown is terminal, and s.started is not reset, so any subsequent RegisterBuiltin will reject with "called after Start". Construct a new Service if you need a fresh lifecycle.