Scheduler
This page covers automated scheduling and how to recover a scheduler skill after a bad edit.
Scheduler skills (isolated namespace)
Scheduler-triggered skills live in a separate tree, not scanned by agents.Scanner():
~/.config/agenvoy/skills/scheduler/<short>-<hash8>/SKILL.md
| Aspect | Regular skill | Scheduler skill |
|---|---|---|
| Path | ~/.config/agenvoy/skills/<name>/SKILL.md |
~/.config/agenvoy/skills/scheduler/<short>-<hash8>/SKILL.md |
Frontmatter name |
<name> |
<short>-<hash8> (no scheduler- prefix) |
/<name> autocompletion |
yes | no — surfaced as /sched-<name> (warn-purple) at the bottom of the picker |
| Trigger | runtime.MatchSkill (in exec.Prepare) then a synthesized run_skill |
(a) cron / one-shot fire from daemon runtime.SetRunner, (b) manual /sched-<name> or /schedule from TUI |
Creation flow
The scheduler-skill-creator skill is the canonical entry for new schedules. It:
- Pre-flight gate (Step 0): if the user message lacks a time token (
+5m/HH:MM/ etc.) or task token, it must callask_userfirst — no defaulting to+10m, no inferring "probably 9am". When both are missing they go out as twoquestionsin oneask_usercall rather than one round each. - Runs
python3 ~/.config/agenvoy/skills/.system/scheduler-skill-creator/scripts/init_scheduler_skill.py <short>to create the skill dir with hash suffix. The path is absolute on purpose:run_commandruns in the user's work directory, not the skill directory, so a relativescripts/...never resolves. - Delegates the body to
/skill-creator(its "edit an existing skill" path) instead of patching the SKILL.md inline. The directory and name are already fixed by step 2, so this step only fills in content. - Calls
schedules(mode=write)to bind the schedule.
Direct schedules(mode=patch) calls are allowed only for rebinding existing schedules (changing the time of an already-created scheduler skill). mode=write is internal to the creator flow — its skill_name carries a generated hash suffix, so a hand-made one always fails.
Manual execution (/sched-<name>)
The TUI command picker lists each directory under scheduler/ whose cron or one-shot entry is bound to the current session as /sched-<name>. Selecting one reads the body and dispatches it to the current agent with a preamble that blocks weaker models from misreading the SKILL.md-shaped body as a schedule-creation request and re-running the creator. /schedule lists every cron and one-shot entry in one popup: enter fires the selected one through this same path, d deletes it; adding or editing a schedule is done by asking the agent (the former /cron and /task commands were removed).
The preamble enforces:
- Execute the existing scheduler skill immediately and output results
- Do not activate
scheduler-skill-creator - Do not run
init_scheduler_skill.py - Do not call
schedules(mode=write)
Daemon fire
The daemon calls runtime.SetRunner(app.RunSkill), where app.RunSkill(ctx, sessionID, skillName) is the runner. When the scheduler fires (cron tick or one-shot deadline), the runner:
- Reads body via
skill.GetSchedule(skillName). - Ensures the session directory and its SQLite row exist.
- Runs the body through
exec.ExecWithSubagent— an in-process subagent in schedule context.
One-shot tasks are removed and the skill dir is trashed after a successful fire.
Recovering from a bad edit
Automatic post-run skill rewriting was removed — a failed run no longer edits the skill behind your back. Recovery runs on the file-level snapshot layer: every change the tools make to a file is recorded, so recovery is explicit:
| Step | Tool |
|---|---|
| Inspect the recorded versions of a skill or tool file | file_history(mode=list) |
| Diff the newest recorded version against what is on disk | file_history(mode=read) |
| Put one version back | edit_file(mode=restore, version=...) |
| Undo everything one task changed | edit_file(mode=restore, task_id=...) |
A removed skill lands in .Trash/ and stays recoverable from there.