Zeno is experimental. Personal project, no SLA, breaking changes expected.

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.md

Folders 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.
KeyTypeRequiredWhat it does
namestringyesHuman-readable name surfaced in dashboard + CLI.
descriptionstringnoOne-line summary.
schedulestringyesCron expression (UTC). Validate at crontab.guru.
enabledbooleanyesfalse 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

  1. Scaffold with zeno cron create <slug> --schedule '<expr>'. The CLI writes CRON.md from the profile's _template/CRON.md and prints the path.
  2. Edit the body in your editor. The CLI never launches $EDITORzeno cron open <slug> reveals the folder in your OS file browser if you need the path.
  3. Reconciler picks up the change within 2 s and schedules the next fire.
  4. 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.
  5. zeno cron enable / disable <slug> flip the frontmatter enabled flag in place (atomic tmp-file + rename). Body bytes are untouched.
  6. 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.

On this page