# Scheduler & AI Automation

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:

1. Pre-flight gate (Step 0): if the user message lacks a time token (`+5m` / `HH:MM` / etc.) or task token, it must call `ask_user` first — no defaulting to `+10m`, no inferring "probably 9am". When both are missing they go out as two `questions` in one `ask_user` call rather than one round each.
2. 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_command` runs in the user's work directory, not the skill directory, so a relative `scripts/...` never resolves.
3. 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.
4. 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). Since v1.2.0 the TUI sends this run to the daemon over its Unix socket, like any other input; while the TUI shows `Connecting` it is not sent.

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:

1. Reads body via `skill.GetSchedule(skillName)`.
2. Ensures the session directory and its SQLite row exist.
3. Runs the body through `exec/subagent.Exec` — an in-process run in its top-level mode, so it keeps the full tool set except `edit_tool`, `test_tool`, `edit_skill`, `schedules` and `store_secret`, and runs as allow-all, not limited to the working directory.

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.
