Crons
Recurring agent runs declared as markdown files on disk. CLI scaffolds + manages lifecycle; dashboard renders read-only.
A cron is a scheduled run of the agent declared as a markdown file under ~/.zeno/profiles/<name>/crons/<slug>/CRON.md. The worker watches the folder; when a schedule fires the agent runs with the file's body as its prompt and /app/crons/<slug>/ as its working directory.
Folder layout
~/.zeno/profiles/<name>/crons/
├── _README.md # operator-facing how-it-works (scaffolded on profile create)
├── _template/ # blank scaffold; `zeno cron create` copies CRON.md from here
│ └── CRON.md
├── send-hello/ # operator-created cron
│ ├── CRON.md
│ └── scripts/ # optional aux files the agent reads via Bash
└── daily-digest/
└── CRON.mdFolders prefixed with _ (or .) are meta — the reconciler skips them silently. Reserved slugs: _template, _README.md, .disabled, .tmp. Other slugs must match ^[a-z][a-z0-9-]*$ (lowercase, kebab-case, ≤ 63 chars).
CRON.md format
YAML frontmatter + markdown body. The body is the prompt the agent receives on every fire.
---
name: Send hello # required, human-readable
description: Daily greeting # optional
schedule: 0 9 * * 1-5 # required, cron expression (UTC)
enabled: true # required boolean
---
Say hello to the workspace. Pull 3 open Linear issues I should focus on today
and list them with their priorities.| Key | Type | Required | What it does |
|---|---|---|---|
name | string | yes | Human-readable name surfaced in dashboard + CLI. |
description | string | no | One-line summary. |
schedule | string | yes | Cron expression (UTC). Validate at crontab.guru. |
enabled | boolean | yes | false skips firing without deleting the cron. |
Invalid schedule or missing required keys → the cron is registered as disabled with the parse error in lastError. Fix the file and the reconciler retries within 2 s.
Lifecycle
- Scaffold with
zeno cron create <slug> --schedule '<expr>'. The CLI writesCRON.mdfrom the profile's_template/CRON.mdand prints the path. - Edit the body in your editor. The CLI never launches
$EDITOR—zeno cron open <slug>reveals the folder in your OS file browser if you need the path. - Reconciler picks up the change within 2 s and schedules the next fire.
zeno cron test <slug>fires the cron once synchronously and returns the agent session id, so you can verify the prompt does what you want before letting it run on schedule.zeno cron enable / disable <slug>flip the frontmatterenabledflag in place (atomic tmp-file + rename). Body bytes are untouched.zeno cron delete <slug>removes the folder; the reconciler clears the DB row and run history within 4 s.
The CLI reference at /docs/cli#cron-management has every flag table.
Dashboard
The dashboard /crons page is read-only. Each row shows the cron's status, next run, and last error (if any). Every action chip — [NEW CRON], [OPEN], [ENABLE] / [DISABLE], [TEST], [DELETE] — opens a <CommandModal> with the exact zeno cron … command to copy and run in your terminal. The dashboard never mutates state directly; mutation flows entirely through the CLI per vault/rules/cli-only-mutations.md.
The detail page /crons/:slug renders the parsed frontmatter, the markdown body, and the run history table (with the agent session id of each fire).
Aux scripts
Drop arbitrary files into crons/<slug>/scripts/ and reference them from your prompt — the agent's working directory is /app/crons/<slug>/ so cat scripts/payload.json and friends just work. No special declaration in CRON.md is needed.
Persistence
The folder lives entirely under ~/.zeno/profiles/<name>/ on your machine. It is not committed to the Zeno repository. If you want a backup, init your own git repo inside it:
cd ~/.zeno/profiles/<name>/crons
git init && git add . && git commit -m "initial"Audit before committing — your prompts may reference secrets or internal context.
What crons are not
- Crons are not skills. A skill is a playbook that runs when the agent's request description matches. A cron is a fixed prompt fired on a schedule.
- Crons are not background workers. Each fire is a single agent turn — request in, response out. There is no long-running process the cron creates.
- Crons no longer live in the runtime DB. The DB stores only derived state (next run, last run, error message, content hash for poll dedup) and the run history (
cron_runs). All operator-authored content is on disk.
Migration from the legacy DB-backed crons
Before spec crons-cli-first (2026-05-22), crons lived in the runtime SQLite database, created and edited through the dashboard. The migration that ships with this rework clears every existing row (crons + cron_runs) and slims the schema. If you had crons in production, scaffold them again from the CLI; the previous prompts are not preserved by this spec. The single-operator scope of Zeno made the clean-slate approach acceptable; a future migration is not planned.