feat(scheduler): store jobs per chat and import VolcanicSecretary job files

Each chat's jobs live in <workspace>/.volcanic/scheduler/jobs.json. On start the
scheduler loads every chat's file, moves the retired shared
<agentDir>/scheduler/jobs.json into them and renames it .migrated-<ts>.
A VolcanicSecretary jobs.json (bare array) in a chat's folder is converted
in place for that chat; the original is kept as .secretary-<ts>.

Also adds the VolcanicSecretary data migration plan.
This commit is contained in:
EugeneTes
2026-10-02 07:29:48 +00:00
parent 28c6f90d91
commit 28bbfbc759
19 changed files with 609 additions and 58 deletions

View File

@@ -36,7 +36,7 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
| `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` | 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:LegacyStorePath` | `<agent dir>/scheduler/jobs.json` | The retired shared job file. Jobs live in each chat's `<workspace>/.volcanic/scheduler/jobs.json`; on start the jobs found here move into their chats' files and this file is renamed `jobs.json.migrated-<timestamp>` |
| `Scheduler:DefaultScriptTimeout` | `00:05:00` | Watcher timeout when a job sets none (1 s to 1 h) |
| `Scheduler:MaxJobsPerTarget` | `50` | Jobs per chat |
| `Scheduler:Shell` | `/bin/sh -c` (`cmd.exe /d /s /c` on Windows) | The watcher shell as a list, command appended; same as `bash` by default |
@@ -178,10 +178,29 @@ After a restart, a one-off that came due while the bot was down is delivered as
"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. Watcher failure alerts aren't part of the
Jobs are per chat: each chat's jobs are stored in its workspace, in `.volcanic/scheduler/jobs.json`, and `/jobs` and
the agent only see this chat's jobs. The file is read on start and rewritten on every change, so edit it only while the
bot is stopped; a job in a chat's file always belongs to that chat. 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.
#### Importing VolcanicSecretary jobs
Copy VolcanicSecretary's `.secretary/jobs.json` to the chat's `.volcanic/scheduler/jobs.json`
(`/data/workspaces/<chatId>/.volcanic/scheduler/jobs.json` in the container) and start the bot. The scheduler
recognises the old format (a bare array without chat ids), converts it for the chat whose folder it is in, saves it in
the current format, and keeps the original next to it as `jobs.json.secretary-<timestamp>`. Each job keeps its id
(a new one if another chat already uses it), schedule and last result:
- a `text` job becomes a prompt. With `deliver: direct` (sent as is, no model call) the text gets `Remind me: `, so the
agent passes it on, as it does for the retired reminder kind;
- a `script` job becomes a watcher with the same command and timeout. Its output now always goes to the agent;
- a 6-field cron with seconds `0` loses that field. Other jobs our scheduler can't express (seconds, invalid fields)
are dropped with a warning in the log.
Copy the workspace's `scripts/` into the owner's workspace too: watcher commands such as `python scripts/check.py` run
from there. A one-off that is already due fires on the first start.
### Google Calendar
Each chat connects its own Google account with `/calendar_connect`, and the agent's `calendar` tool only uses that