Routines
A routine is a task that runs on a schedule. Same prompt, same agent, same output shape, every Friday at 5pm. While heartbeats decide what to do at a moment, routines do the same thing reliably.
Where routines live
Each routine is a YAML file in .jobs/:
my-cabinet/
├── .agents/
│ └── gtm-lead/persona.md
└── .jobs/
├── monday-launch-room.yaml
├── friday-investor-update.yaml
└── weekly-competitor-sweep.yaml
The filename becomes the routine's slug. Add a file, the routine appears. Delete it, the routine disappears. Git tracks it.
A complete routine
schedule: "0 17 * * 5" # Fridays at 5pm
ownerAgent: revenue-analyst
enabled: true
title: "Friday investor update packet"
prompt: |
Compile this week's metrics into /investors/$WEEK/.
Pull from /metrics/, /sales/pipeline.csv, /support/tickets.csv.
End with a 3-sentence narrative for the cover email.
model: claude-opus-4-7
effort: high
budget:
maxTokens: 80000
maxCostUsd: 2.50
outputs:
- path: "investors/${YYYY-WW}/packet.md"
- path: "investors/${YYYY-WW}/cover.md"
Every field
| Field | Type | Required | What it does |
|---|---|---|---|
schedule | cron string | "manual" | yes | When the routine fires. Use manual for run-on-button. |
ownerAgent | agent slug | yes | Which persona runs it. Inherits their model, tools, and memory. |
enabled | boolean | no (default true) | Off without deleting. |
title | string | yes | Shown in the schedule UI and approval logs. |
prompt | string | yes | The instruction. Variables like $DATE, $WEEK, $YYYY-WW get interpolated. |
model | string | no | Override the agent's default model for this routine. |
effort | "low" | "medium" | "high" | no | Reasoning effort. High = slower, more careful, more expensive. |
budget | object | no | Cap tokens or dollars per run. |
outputs | array | no | Hint Cabinet which files this routine writes. Helps with link-checking. |
requiresApproval | boolean | no (default false) | If true, queue the result before it lands. |
Routines vs. heartbeats — which do I want?
Pick a routine when you can describe the output before it runs:
"Every Friday, summarize the week's metrics into a packet."
Pick a heartbeat when you can only describe the trigger:
"Every weekday morning, look at the launch room and tell me what changed — if anything."
A rough rule: if the output goes in a predictable folder structure, it's a routine. If the output might be "nothing today, sorry," it's a heartbeat.
Manual routines
Set schedule: "manual" for a routine you trigger from the UI:
schedule: "manual"
ownerAgent: research-lead
title: "Brief 10 competitors"
prompt: |
Take the company name from the trigger context.
Produce one /research/competitors/<slug>.md per competitor.
Use the brief template at /research/templates/competitor.md.
These show up as "Run" buttons in the agent's profile. Click to trigger, optionally pass arguments.
Variable interpolation
| Variable | Expands to | Example |
|---|---|---|
$DATE | YYYY-MM-DD | 2026-05-03 |
$WEEK | YYYY-WW (ISO week) | 2026-W18 |
$MONTH | YYYY-MM | 2026-05 |
$AGENT | owner agent's slug | gtm-lead |
$CABINET | current cabinet's id | my-startup |
$RUN_ID | unique id for this run | r_42a8 |
Useful for routing outputs into dated folders that won't collide.
Watching a routine run
Open the agent's profile (or Routines in the sidebar). Each scheduled run shows up as a row with:
- The trigger (cron / manual / heartbeat dispatch).
- The model and effort actually used.
- A live token counter.
- The output paths as they get written.
You can interrupt a long-running routine the same way you'd interrupt a chat — Cabinet writes a clean partial page so nothing is lost.
Read on
- Heartbeats — when a check-in is enough.
- Tasks — for one-offs, not schedules.
- Job schema reference — every YAML field, every default.