feat(skills): add grantable schedule skill + deploy runbook #84

Closed
weiwen wants to merge 1 commit from fm/evie-schedule-skill-impl into main
Owner

What

Adds a safe, grantable schedule skill so a vouched user's sandboxed agent can manage its own schedules (evie schedule / list / cancel) over $EVIE_SOCKET, plus a precise operator deploy runbook for the remaining morning-agenda-fix steps that live outside this repo.

Docs/skill-only — no Rust change. The skills_dir grant mechanism is already complete; this PR only supplies the versioned skill source + the runbook. just check passes clean (485 tests, fmt + clippy).

The bundled-vs-grantable boundary (why a new dir)

bundled_skill_dirs() in src/pi.rs reads exactly $CARGO_MANIFEST_DIR/skills, and those bundled skills are injected only on the /sudo path (pi_argv(..., include_bundled_skills = sudo)) and deliberately withheld from sandboxes. Dropping the schedule skill under skills/ would make it sudo-only and it would never reach a sandbox — defeating the purpose.

So the grantable source lives in a new, non-bundled location: grantable-skills/schedule/SKILL.md. It is not referenced by any evie code; the deployment seeds it into [pi] skills_dir (/var/lib/evie/skills), and it binds into a sandbox only when the user holds the skills/schedule capability (capability_dirs in src/session.rs). grantable-skills/README.md documents the distinction, and AGENTS.md gains a one-line pointer so no future agent re-introduces the landmine.

Safe-subset boundary

The SKILL.md documents only the self-scoped, safe surface:

  • evie schedule --in|--at|--cron [--name] [--update] -- <verb> ...
  • evie list [--json], evie cancel --name <name>
  • Scheduled verbs send / prompt / run <script> — a scheduled run is safe (it fires re-sandboxed as the user).

It deliberately does not document the dangerous immediate surface (immediate run/prompt/query, send to other chats). The immediate Run verb is rejected by scoped_allows regardless (src/control_plane.rs). Everything is scoped to the caller: chat/topic are id-locked and identity is frozen, so the skill tells the agent it never needs --chat/--topic. Every flag and duration/verb form was verified against the current main.rs clap parser and the scoped control-plane handlers.

Operator runbook (docs/schedule-skill-deploy-runbook.md)

Covers the out-of-repo steps firstmate cannot perform (operator's NixOS config + sandbox actions over Telegram), with exact commands/paths:

  1. Seed + grant — tmpfiles rule to seed grantable-skills/schedule/ into skills_dir, /grant 113665509 skills/schedule, then /clear to rebind.
  2. Relocate morning-briefing.py / evening-review.py off the 0700 /home/weiwen path (unreadable by the sandbox drop uid — the bwrap bind source is opened post-drop) to a sandbox-readable path, e.g. /var/lib/evie/homes/113665509/prompts/.
  3. Re-create morning-briefing / evening-review from the operator's sandbox as sudo=false schedules pointing at the relocated scripts, so they fire in-sandbox where gog is provisioned.
  4. 60 s test-fire to confirm in-sandbox execution + a populated agenda, and to check the related risk that the gog credential read_paths may have the same 0700 problem.

Precondition stated in the runbook: steps 3-4 depend on the pi opencode-parser repetition fix (separate PR) being merged and redeployed first, so conversational turns are reliable.

Scope

Skill source + runbook only. Does not relocate scripts, edit the operator's nix config, re-create schedules, or touch src/pi.rs's parser (the repetition-fix PR).

## What Adds a **safe, grantable `schedule` skill** so a vouched user's sandboxed agent can manage its own schedules (`evie schedule` / `list` / `cancel`) over `$EVIE_SOCKET`, plus a precise operator **deploy runbook** for the remaining morning-agenda-fix steps that live outside this repo. Docs/skill-only — no Rust change. The `skills_dir` grant mechanism is already complete; this PR only supplies the versioned skill source + the runbook. `just check` passes clean (485 tests, fmt + clippy). ## The bundled-vs-grantable boundary (why a new dir) `bundled_skill_dirs()` in `src/pi.rs` reads exactly `$CARGO_MANIFEST_DIR/skills`, and those bundled skills are injected **only on the `/sudo` path** (`pi_argv(..., include_bundled_skills = sudo)`) and deliberately withheld from sandboxes. Dropping the schedule skill under `skills/` would make it **sudo-only** and it would never reach a sandbox — defeating the purpose. So the grantable source lives in a **new, non-bundled** location: `grantable-skills/schedule/SKILL.md`. It is not referenced by any evie code; the deployment seeds it into `[pi] skills_dir` (`/var/lib/evie/skills`), and it binds into a sandbox only when the user holds the `skills/schedule` capability (`capability_dirs` in `src/session.rs`). `grantable-skills/README.md` documents the distinction, and `AGENTS.md` gains a one-line pointer so no future agent re-introduces the landmine. ## Safe-subset boundary The `SKILL.md` documents **only** the self-scoped, safe surface: - `evie schedule --in|--at|--cron [--name] [--update] -- <verb> ...` - `evie list [--json]`, `evie cancel --name <name>` - Scheduled verbs `send` / `prompt` / `run <script>` — a *scheduled* `run` is safe (it fires re-sandboxed as the user). It deliberately does **not** document the dangerous immediate surface (immediate `run`/`prompt`/`query`, `send` to other chats). The immediate `Run` verb is rejected by `scoped_allows` regardless (`src/control_plane.rs`). Everything is scoped to the caller: chat/topic are id-locked and identity is frozen, so the skill tells the agent it never needs `--chat`/`--topic`. Every flag and duration/verb form was verified against the current `main.rs` clap parser and the scoped control-plane handlers. ## Operator runbook (`docs/schedule-skill-deploy-runbook.md`) Covers the out-of-repo steps firstmate cannot perform (operator's NixOS config + sandbox actions over Telegram), with exact commands/paths: 1. **Seed + grant** — tmpfiles rule to seed `grantable-skills/schedule/` into `skills_dir`, `/grant 113665509 skills/schedule`, then `/clear` to rebind. 2. **Relocate** `morning-briefing.py` / `evening-review.py` off the `0700 /home/weiwen` path (unreadable by the sandbox drop uid — the bwrap bind source is opened post-drop) to a sandbox-readable path, e.g. `/var/lib/evie/homes/113665509/prompts/`. 3. **Re-create** `morning-briefing` / `evening-review` from the operator's sandbox as `sudo=false` schedules pointing at the relocated scripts, so they fire in-sandbox where `gog` is provisioned. 4. **60 s test-fire** to confirm in-sandbox execution + a populated agenda, and to check the related risk that the `gog` credential `read_paths` may have the same `0700` problem. **Precondition stated in the runbook:** steps 3-4 depend on the `pi` opencode-parser repetition fix (separate PR) being **merged and redeployed** first, so conversational turns are reliable. ## Scope Skill source + runbook only. Does not relocate scripts, edit the operator's nix config, re-create schedules, or touch `src/pi.rs`'s parser (the repetition-fix PR).
feat(skills): add grantable schedule skill + deploy runbook
All checks were successful
CI / check (pull_request) Successful in 1m26s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
99a18bf999
weiwen closed this pull request 2026-07-30 21:22:11 +08:00
All checks were successful
CI / check (pull_request) Successful in 1m26s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!84
No description provided.