forked from admin/VolcanicAgenticCore
refactor(scheduler): drop the reminder job kind
Jobs are prompts or watchers. A reminder is a prompt the agent passes on, so
it lands in the transcript. Stored reminder jobs load as prompts
("Remind me: <text>"). Also records the Coolify deploy target in CLAUDE.md.
This commit is contained in:
@@ -21,8 +21,8 @@ directories.
|
||||
`GoogleCalendar` is a feature extension: a tool and three commands, per-chat accounts through process-wide
|
||||
`GoogleCalendarConnections` (see [google-calendar.md](google-calendar.md)).
|
||||
|
||||
`Scheduler` is a feature extension with a process-wide `JobScheduler` (reminders, scheduled prompts and watcher
|
||||
scripts on a `TimeProvider` timer, jobs in `<AgentDir>/scheduler/jobs.json`), a per-session `schedule` tool bound to a
|
||||
`Scheduler` is a feature extension with a process-wide `JobScheduler` (scheduled prompts and watcher scripts
|
||||
on a `TimeProvider` timer, jobs in `<AgentDir>/scheduler/jobs.json`), a per-session `schedule` tool bound to a
|
||||
host target, and an `IJobDelivery` the host implements (see [telegram-bot.md](telegram-bot.md#scheduling)).
|
||||
|
||||
## Vertical slices
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
# Scheduler: reminders, scheduled prompts and watcher scripts (design)
|
||||
|
||||
Date: 2026-09-26
|
||||
Status: implemented
|
||||
Status: implemented; the `reminder` kind was removed on 2026-09-29 (see "Update" below)
|
||||
Depends on: `2026-09-26-telegram-attachments-design.md` (`IChatChannel`, `ChatContext.Channel`, the renderer's
|
||||
`RunInOrderAsync`); `2026-09-26-process-environment-hook-design.md` (`IProcessSpawnPolicy`, which builds the watcher
|
||||
scripts' environment, and `ISecretRedactor`, which cleans their output); `2026-09-26-telegram-message-context-design.md`
|
||||
(`MessageContext.TimestampHeader`, the local-time header on every prompt).
|
||||
|
||||
## Update (2026-09-29): prompts and watchers only
|
||||
|
||||
The `reminder` kind is gone: a reminder is a `prompt` job whose text tells the agent what to remind the user of, so
|
||||
the agent writes it and it lands in the transcript like any other scheduled prompt. `JobKind` is `Prompt | Watcher`,
|
||||
the `schedule` tool accepts only `prompt` and `watcher`, and `IJobDelivery.SendTextAsync` is used only for watcher
|
||||
failure alerts. `JobStore` loads a stored `"kind": "reminder"` entry as a prompt with the text `Remind me: <text>`
|
||||
(the file is rewritten in the new form on the next save). The rest of this document describes the original
|
||||
three-kind design.
|
||||
|
||||
## Problem
|
||||
|
||||
The Telegram bot only speaks when spoken to. The owner wants it to act on a clock: "remind me at 9 tomorrow to call
|
||||
|
||||
@@ -35,7 +35,7 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
|
||||
| `Telegram:InputDebounce` | `00:00:00.6` | How long the bot waits after a message for more before sending them to the agent as one request. `0` disables the wait; messages still being read together (e.g. a photo still downloading and a text after it) are still sent as one request. Must not be negative |
|
||||
| `Telegram:ReconnectInitialDelay` / `ReconnectMaxDelay` | `1 s` / `60 s` | Backoff after lost connection |
|
||||
| `DEFAULT_TIMEZONE` / `Telegram:DefaultTimeZone` | — (the machine's zone) | IANA zone for chats that haven't set one with `/timezone`, e.g. `Europe/Berlin`. An unknown name stops startup. Also the default for the core (`AgenticCoreOptions.DefaultTimeZone`) |
|
||||
| `Scheduler:Enabled` | `true` | Reminders, scheduled prompts and watcher scripts (see [Scheduling](#scheduling)) |
|
||||
| `Scheduler:Enabled` | `true` | Scheduled prompts and watcher scripts (see [Scheduling](#scheduling)) |
|
||||
| `Scheduler:StorePath` | `<agent dir>/scheduler/jobs.json` | One file for every chat's jobs, outside the workspaces |
|
||||
| `Scheduler:DefaultScriptTimeout` | `00:05:00` | Watcher timeout when a job sets none (1 s to 1 h) |
|
||||
| `Scheduler:MaxJobsPerTarget` | `50` | Jobs per chat |
|
||||
@@ -147,12 +147,11 @@ chat's workspace and delivers only to that chat.
|
||||
|
||||
### Scheduling
|
||||
|
||||
Ask the agent in plain words. It has a `schedule` tool with three kinds of job:
|
||||
Ask the agent in plain words. It has a `schedule` tool with two kinds of job:
|
||||
|
||||
- "Remind me at 9 tomorrow to call the dentist" is a **reminder**: at 9 the bot sends the text as is,
|
||||
`⏰ Call the dentist`, with no model call.
|
||||
- "Every weekday at 8, summarise what's in `inbox/`" is a **prompt**: when it fires, the agent handles it like a
|
||||
message from you, with the same time header.
|
||||
- "Remind me at 9 tomorrow to call the dentist" or "Every weekday at 8, summarise what's in `inbox/`" is a
|
||||
**prompt**: when it fires, the agent handles it like a message from you, with the same time header, and writes the
|
||||
reminder or the summary.
|
||||
- "Check this page every 10 minutes and tell me only if the price drops below 50" is a **watcher**.
|
||||
|
||||
For a watcher the agent writes a script (usually in `scripts/`, tested with `bash` first). Every run the bot runs the
|
||||
@@ -175,12 +174,12 @@ tool) changes it for the date in the agent's instructions, `bash`, scheduled scr
|
||||
Code. Recurring jobs follow the new zone ("every day at 9" means 9 where you are now); one-off jobs keep their moment
|
||||
and are just shown in the new zone. The reply says how many of each.
|
||||
|
||||
After a restart, a one-off that came due while the bot was down is delivered as soon as it's back, marked
|
||||
`(due …, delivered late)` (for prompts: "This was due at …; the bot was offline."). Recurring jobs don't catch up:
|
||||
After a restart, a one-off that came due while the bot was down is delivered as soon as it's back, with
|
||||
"This was due at …; the bot was offline." in the prompt. Recurring jobs don't catch up:
|
||||
missed occurrences are skipped.
|
||||
|
||||
Jobs are per chat: `/jobs` and the agent only see this chat's jobs. Direct reminders and failure alerts aren't part
|
||||
of the conversation, so the agent can't see them later; use a prompt job for things that should be. With
|
||||
Jobs are per chat: `/jobs` and the agent only see this chat's jobs. Watcher failure alerts aren't part of the
|
||||
conversation, so the agent can't see them later. With
|
||||
`Scheduler:Enabled=false`, `/jobs` and `/cancel` reply that scheduling is turned off, and `/timezone` still works.
|
||||
|
||||
### Google Calendar
|
||||
|
||||
@@ -192,7 +192,7 @@ public sealed class ChatDeliveryException(string message, Exception? inner = nul
|
||||
- `Limits`: your platform's upload limits (Telegram: photo 10 MB, document 50 MB, caption 1024 characters). `send`
|
||||
checks them before calling you, and its errors name them.
|
||||
- `SendTextAsync`: markdown, rendered like assistant text and split when long. It is not added to the session
|
||||
transcript, and it must work while no run is active: schedulers use it for reminders.
|
||||
transcript, and it must work while no run is active: schedulers use it for failure alerts.
|
||||
- `SendFileAsync`: the core has already confined the path to the workspace (symlinks included), checked that the file
|
||||
exists and applied the limits. `Auto` lets you choose, and you may fall back (Telegram resends a rejected photo as a
|
||||
document); report what you did in `SentAs`. Throw only `ChatDeliveryException`, with a message the model or the
|
||||
|
||||
Reference in New Issue
Block a user