Schedule Recurring Agent
Inputs
$job: the recurring job description, optionally followed by a schedule ("weekday 9am", "hourly")
that seeds the cadence choice in Phase 2.
Overview
Stand up an agent that does a recurring job on a schedule, correctly: a self-contained idempotent brief,
a cadence matched to the work, a per-run bound, clear reporting, an escalation rule for anything
irreversible, and a teardown condition. Getting the brief and the bounds right is what separates a useful
routine from one that spams, grinds, or acts beyond its mandate.
Phase 0: Confirm it should be a recurring routine
- Is this genuinely RECURRING standing work (arrives repeatedly on its own timeline), or a one-off? A
one-off wait on a signal is
watch-and-act; work to do once is just done now.
- Does it act autonomously in a way the user must authorize? Confirm scope (especially any action beyond
read/report) with
AskUserQuestion before creating it.
Success criteria: confirmed recurring + the autonomy scope the user authorized.
Phase 1: Write the self-contained, idempotent brief
The brief is what the routine wakes into with zero memory — make it complete and repeat-safe:
- What to do, precisely, and where (repo/paths/queries) — no reliance on session context.
- Idempotency rule — how it recognizes and skips work already done (a marker label, a state file, a
"since last run" query, a dedup key). This is the load-bearing part: without it, the routine repeats
itself every run.
- Per-run bound (
budget-guard) — max items handled, token/time cap, so a busy day can't turn one run
into a grind.
- Report + escalate — the channel it reports through, and the triggers that make it stop-and-ask a
human instead of acting (irreversible/ambiguous/high-volume).
- Teardown condition — when it should stop (job complete, N consecutive empty runs, a date, user
cancel).
Success criteria: a brief a fresh, memory-less agent can execute correctly and repeatedly without
duplicating work or exceeding its mandate.
Phase 2: Pick the cadence and the mechanism, then create the routine
- Choose the cron cadence from the work's real arrival rate: issue triage → weekday mornings; CVE watch →
weekly; PR babysitting → a few times a day; nightly audit → once daily. Prefer the LEAST frequent
cadence that still catches the work in time.
- Pick the mechanism honestly — this is the load-bearing choice:
- Durable / unattended (the usual case) — work that must run on its own timeline even while you are
offline (nightly audits, CVE watch, weekday triage): stand it up as a claude.ai Routine via the
/schedule skill (backed by the RemoteTrigger API). Routines run on Anthropic's infrastructure
on a cron schedule, persist across sessions, and their runs count against your plan's usage/rate
limits. This is the ONLY mechanism that actually delivers "runs when no one's kicking it off."
- In-session only (the lighter alternative) — a recurrence you want ONLY while a session stays
open:
CronCreate. It is NOT a standing cloud agent — see the constraints below before choosing it.
- Create it, then confirm it registered (
/schedule list for a Routine; CronList for an in-session cron).
Constraints of each mechanism (state these to the user — they change what "recurring" means)
|
claude.ai Routine (/schedule · RemoteTrigger) |
In-session cron (CronCreate) |
| Runs while you're offline |
Yes — on Anthropic infra |
No — only while this session's REPL is idle |
| Survives closing Claude |
Yes — persistent |
No — in-memory; gone when the session exits |
| Lifetime |
until you disable/remove it |
auto-expires after 7 days (one final fire, then deleted) |
| Per-run token budget |
no native cap — bound via brief + budget-guard; runs count against plan usage |
same — no native cap |
There is NO native per-run token budget on either — the per-run bound is enforced by the brief +
budget-guard, not a platform parameter.
Success criteria: created on the mechanism that matches its durability need, on a justified cadence;
registration confirmed; the mechanism's constraints stated to the user.
The validated job schema (the gate)
Both the brief (Phase 1) and the creation (Phase 2) are enforced deterministically by
scripts/validate-job.mjs — the guardrails above are mechanically checkable, so they do NOT ship as
prose only. A recurring job is a JSON object that MUST declare all nine fields; missing/empty any → the
gate exits nonzero and names it:
| Field |
What it pins |
Shape |
key |
a STABLE idempotency + registration key (dedup and the automation id are built from it) |
[A-Za-z0-9][A-Za-z0-9_-]* |
repo |
where it operates — no reliance on session context |
string |
cadence |
a TIMEZONE-anchored recurrence (a cron with no tz drifts) |
{ timezone, cron|expression } |
prompt |
the self-contained brief a memory-less run executes |
string (≥ 20 chars) |
dedup |
the idempotency rule — marker/state/since-query so repeats don't re-file/re-spam |
string or object |
perRunCap |
a POSITIVE per-run bound so one run can't grind or fan out |
{ maxItems|maxTokens|maxMinutes|maxActions } or a positive number |
reporting |
the channel each run reports through (incl. "nothing to do") |
string or object |
escalation |
the stop-and-ask rule for irreversible/ambiguous/high-volume work |
string or object |
teardown |
the off-switch (job done, N empty runs, a date, user cancel) |
string or object |
node schedule-recurring-agent/scripts/validate-job.mjs validate <job.json>
The capability ladder (honest creation — never a false registration)
Creation runs through the same gate in create mode, and the ORDER is load-bearing:
node schedule-recurring-agent/scripts/validate-job.mjs create <job.json> \
[--capability RemoteTrigger|CronCreate] [--authorize] [--existing <registry.json>]
- Validate the schema. Invalid →
created:false, exit 2, nothing registered.
- List + DEDUP FIRST. Before any capability check, the job's
key is looked up in the registry. A
match is a correct idempotent NO-OP (created:false, reason:duplicate, the existing id echoed, exit
- — a re-run never stacks a second routine. This is why the key must be stable.
- Capability + authorization. A verifiable automation id (
<capability>:<key>) is minted ONLY when
a SUPPORTED capability AND explicit --authorize are both present:
RemoteTrigger — durable claude.ai Routine (the /schedule skill). Claude Code only.
CronCreate — in-session cron. Claude Code only.
- anything else (Codex, plain CLIs) → NO capability. The gate returns
created:false,
registered:false, exit 3, and a ready brief for manual/other-platform registration. It NEVER
claims a RemoteTrigger/CronCreate registration on a platform that has neither — it degrades and says
so. Missing --authorize on a supported platform degrades the same way.
- On success the id is appended to the
--existing registry, so a later list/create sees it — the
id is VERIFIABLE, not invented.
Phase 3: Manage the lifecycle
- List/inspect existing routines before creating a near-duplicate — extend one rather than stacking
overlapping schedules (
/schedule list for claude.ai Routines; CronList for in-session crons).
- When the teardown condition is met (or the user asks), tear it down (disable/remove via
/schedule
for a Routine; CronDelete for an in-session cron). Don't leave dead routines running.
Success criteria: no duplicate/overlapping routines; completed/cancelled routines are torn down.
Common Rationalizations
| Rationalization |
Reality |
| "The routine will remember what it did last time." |
It won't — each run is memory-less. Put an idempotency rule in the brief or it re-does/re-spams every run. |
| "Run it every 15 minutes so nothing's missed." |
Over-frequent routines burn tokens and create noise. Match the cadence to how often the work actually arrives. |
| "Let it auto-merge / auto-deploy when it looks good." |
Irreversible actions need explicit per-action authorization. A routine prepares and reports; it doesn't pull irreversible triggers unbidden. |
| "No need for a per-run cap, it's a small job." |
Small-usually is not small-always. A busy day turns an unbounded run into a grind. Bound every run. |
| "I'll leave it running; it's harmless." |
A routine with no teardown runs forever, spending tokens for nothing. Give it an off-switch. |
| "Report only when there's something interesting." |
Silent runs hide failures. Report what it did, including 'nothing to do' — just don't flood. |
Red Flags
- A brief that assumes memory of prior runs (no idempotency/dedup rule).
- A cadence far more frequent than the work arrives.
- A routine authorized to take irreversible actions without explicit per-action sign-off.
- No per-run bound (one run can grind or fan out unboundedly).
- No teardown condition; overlapping duplicate routines accumulating.
- A run that fabricates activity or floods the report channel.
Guardrails
- Never write a brief that relies on prior-run memory; make it self-contained + idempotent.
- Never over-schedule; match the cron cadence to the work's arrival rate.
- Never let a routine take irreversible/external actions without explicit authorization.
- Never create an unbounded per-run job; declare a per-run cap.
- Never leave a completed/duplicate routine running; tear it down.
Native goal/loop routing
On Claude Code, a routine's BRIEF should itself be goal-shaped: state the per-run done-condition the
way /goal would ("triage every issue opened since the last run; done when each has a label and a
priority"), so each scheduled invocation runs as a bounded goal loop rather than an open-ended prompt.
Routines are the scheduled form; /loop is the in-session form; this skill is about choosing and
briefing the former correctly.
When To Load References
scripts/validate-job.mjs — the deterministic job-schema + honest-creation gate (run it in validate
mode on the brief, in create mode to stand the routine up dedup-first with a verifiable id).
budget-guard (skill) — the per-run bound + escalation contract each invocation enforces.
watch-and-act (skill) — for a one-off in-session wait instead of a standing routine.
- The phase/loop skills (
auto-*, converge-loop, …) — the actual work a routine's brief invokes.
Output Contract
Report:
- the routine created — its job, cadence (with the rationale), the MECHANISM (claude.ai Routine vs
in-session cron) and why, and the idempotency rule
- the per-run bound and the report/escalation channel
- the teardown condition + how to list/cancel it (via
/schedule for a Routine, or the cron tools for
an in-session cron)
- confirmation it registered (or the duplicate it extended instead)
1---2name: schedule-recurring-agent3description: Stand up a recurring scheduled agent for standing work (triage, monitoring, audits, digests): a self-contained IDEMPOTENT brief (each run wakes memory-less and must dedup prior work), a cadence matched to how often work actually arrives, a per-run BOUND, escalation rules, and a teardown condition. For durable unattended work that runs while you're offline, use claude.ai Routines (the /schedule skill / RemoteTrigger); an in-session CronCreate is the lighter, session-scoped alternative. Use for scheduled repeat work — not one-off waits (watch-and-act).4---56<EXTREMELY-IMPORTANT>7A recurring agent acts unattended, repeatedly, forever — the failure modes compound. Non-negotiable:81. THE BRIEF IS SELF-CONTAINED AND IDEMPOTENT. Each run wakes with NO memory of prior runs. The task brief9 must carry all its own context (what to do, where, how to report) and be SAFE TO RUN REPEATEDLY —10 deduplicate against prior work (don't re-file the same issue, re-ping the same PR, re-send the same11 digest). A non-idempotent routine spams.122. EVERY RUN IS BOUNDED. Declare a per-run budget/cap (`budget-guard`) so one invocation can't grind for13 hours or fan out unboundedly. The schedule bounds frequency; the brief bounds each run.143. CADENCE MATCHES THE WORK. Pick the cron interval from how often the work actually arrives — not "as15 often as possible". Over-frequent routines burn tokens and create noise; too-rare ones miss the window.164. IRREVERSIBLE ACTIONS STILL ESCALATE. A routine may triage, monitor, and PREPARE, but it does not deploy,17 delete, merge, or send externally-visible messages on its own unless the user explicitly authorized18 that specific action. When in doubt, it produces a draft/report and asks.195. HAS AN OFF-SWITCH. Define when the routine should stop or tear itself down (job done, N empty runs,20 user cancels). A routine with no teardown condition runs forever for no reason. List/inspect/delete it21 via the cron tools.226. HONEST, LOW-NOISE REPORTING. Each run reports what it actually did (including "nothing to do") through23 the agreed channel — never fabricates activity, never floods.24</EXTREMELY-IMPORTANT>2526# Schedule Recurring Agent2728## Inputs2930- `$job`: the recurring job description, optionally followed by a schedule ("weekday 9am", "hourly")31 that seeds the cadence choice in Phase 2.3233## Overview3435Stand up an agent that does a recurring job on a schedule, correctly: a self-contained idempotent brief,36a cadence matched to the work, a per-run bound, clear reporting, an escalation rule for anything37irreversible, and a teardown condition. Getting the brief and the bounds right is what separates a useful38routine from one that spams, grinds, or acts beyond its mandate.3940## Phase 0: Confirm it should be a recurring routine4142- Is this genuinely RECURRING standing work (arrives repeatedly on its own timeline), or a one-off? A43 one-off wait on a signal is `watch-and-act`; work to do once is just done now.44- Does it act autonomously in a way the user must authorize? Confirm scope (especially any action beyond45 read/report) with `AskUserQuestion` before creating it.4647**Success criteria:** confirmed recurring + the autonomy scope the user authorized.4849## Phase 1: Write the self-contained, idempotent brief5051The brief is what the routine wakes into with zero memory — make it complete and repeat-safe:5253- **What to do**, precisely, and where (repo/paths/queries) — no reliance on session context.54- **Idempotency rule** — how it recognizes and skips work already done (a marker label, a state file, a55 "since last run" query, a dedup key). This is the load-bearing part: without it, the routine repeats56 itself every run.57- **Per-run bound** (`budget-guard`) — max items handled, token/time cap, so a busy day can't turn one run58 into a grind.59- **Report + escalate** — the channel it reports through, and the triggers that make it stop-and-ask a60 human instead of acting (irreversible/ambiguous/high-volume).61- **Teardown condition** — when it should stop (job complete, N consecutive empty runs, a date, user62 cancel).6364**Success criteria:** a brief a fresh, memory-less agent can execute correctly and repeatedly without65duplicating work or exceeding its mandate.6667## Phase 2: Pick the cadence and the mechanism, then create the routine6869- Choose the cron cadence from the work's real arrival rate: issue triage → weekday mornings; CVE watch →70 weekly; PR babysitting → a few times a day; nightly audit → once daily. Prefer the LEAST frequent71 cadence that still catches the work in time.72- **Pick the mechanism honestly — this is the load-bearing choice:**73 - **Durable / unattended (the usual case)** — work that must run on its own timeline even while you are74 offline (nightly audits, CVE watch, weekday triage): stand it up as a **claude.ai Routine** via the75 **`/schedule` skill** (backed by the `RemoteTrigger` API). Routines run on Anthropic's infrastructure76 on a cron schedule, persist across sessions, and their runs count against your plan's usage/rate77 limits. This is the ONLY mechanism that actually delivers "runs when no one's kicking it off."78 - **In-session only (the lighter alternative)** — a recurrence you want ONLY while a session stays79 open: `CronCreate`. It is NOT a standing cloud agent — see the constraints below before choosing it.80- Create it, then confirm it registered (`/schedule` list for a Routine; `CronList` for an in-session cron).8182### Constraints of each mechanism (state these to the user — they change what "recurring" means)8384| | claude.ai Routine (`/schedule` · `RemoteTrigger`) | In-session cron (`CronCreate`) |85|---|---|---|86| Runs while you're offline | **Yes** — on Anthropic infra | **No** — only while this session's REPL is idle |87| Survives closing Claude | **Yes** — persistent | **No** — in-memory; gone when the session exits |88| Lifetime | until you disable/remove it | **auto-expires after 7 days** (one final fire, then deleted) |89| Per-run token budget | **no native cap** — bound via brief + `budget-guard`; runs count against plan usage | same — no native cap |9091There is NO native per-run token budget on either — the per-run bound is enforced by the brief +92`budget-guard`, not a platform parameter.9394**Success criteria:** created on the mechanism that matches its durability need, on a justified cadence;95registration confirmed; the mechanism's constraints stated to the user.9697## The validated job schema (the gate)9899Both the brief (Phase 1) and the creation (Phase 2) are enforced deterministically by100`scripts/validate-job.mjs` — the guardrails above are mechanically checkable, so they do NOT ship as101prose only. A recurring job is a JSON object that MUST declare all nine fields; missing/empty any → the102gate exits nonzero and names it:103104| Field | What it pins | Shape |105|---|---|---|106| `key` | a STABLE idempotency + registration key (dedup and the automation id are built from it) | `[A-Za-z0-9][A-Za-z0-9_-]*` |107| `repo` | where it operates — no reliance on session context | string |108| `cadence` | a TIMEZONE-anchored recurrence (a cron with no tz drifts) | `{ timezone, cron\|expression }` |109| `prompt` | the self-contained brief a memory-less run executes | string (≥ 20 chars) |110| `dedup` | the idempotency rule — marker/state/since-query so repeats don't re-file/re-spam | string or object |111| `perRunCap` | a POSITIVE per-run bound so one run can't grind or fan out | `{ maxItems\|maxTokens\|maxMinutes\|maxActions }` or a positive number |112| `reporting` | the channel each run reports through (incl. "nothing to do") | string or object |113| `escalation` | the stop-and-ask rule for irreversible/ambiguous/high-volume work | string or object |114| `teardown` | the off-switch (job done, N empty runs, a date, user cancel) | string or object |115116```117node schedule-recurring-agent/scripts/validate-job.mjs validate <job.json>118```119120## The capability ladder (honest creation — never a false registration)121122Creation runs through the same gate in `create` mode, and the ORDER is load-bearing:123124```125node schedule-recurring-agent/scripts/validate-job.mjs create <job.json> \126 [--capability RemoteTrigger|CronCreate] [--authorize] [--existing <registry.json>]127```1281291. **Validate the schema.** Invalid → `created:false`, exit 2, nothing registered.1302. **List + DEDUP FIRST.** Before any capability check, the job's `key` is looked up in the registry. A131 match is a correct idempotent NO-OP (`created:false`, `reason:duplicate`, the existing id echoed, exit132 0) — a re-run never stacks a second routine. This is why the key must be stable.1333. **Capability + authorization.** A verifiable automation id (`<capability>:<key>`) is minted ONLY when134 a SUPPORTED capability AND explicit `--authorize` are both present:135 - `RemoteTrigger` — durable claude.ai Routine (the `/schedule` skill). **Claude Code only.**136 - `CronCreate` — in-session cron. **Claude Code only.**137 - **anything else (Codex, plain CLIs) → NO capability.** The gate returns `created:false`,138 `registered:false`, exit 3, and a **ready brief** for manual/other-platform registration. It NEVER139 claims a RemoteTrigger/CronCreate registration on a platform that has neither — it degrades and says140 so. Missing `--authorize` on a supported platform degrades the same way.1414. **On success** the id is appended to the `--existing` registry, so a later list/create sees it — the142 id is VERIFIABLE, not invented.143144## Phase 3: Manage the lifecycle145146- List/inspect existing routines before creating a near-duplicate — extend one rather than stacking147 overlapping schedules (`/schedule` list for claude.ai Routines; `CronList` for in-session crons).148- When the teardown condition is met (or the user asks), tear it down (disable/remove via `/schedule`149 for a Routine; `CronDelete` for an in-session cron). Don't leave dead routines running.150151**Success criteria:** no duplicate/overlapping routines; completed/cancelled routines are torn down.152153## Common Rationalizations154155| Rationalization | Reality |156|---|---|157| "The routine will remember what it did last time." | It won't — each run is memory-less. Put an idempotency rule in the brief or it re-does/re-spams every run. |158| "Run it every 15 minutes so nothing's missed." | Over-frequent routines burn tokens and create noise. Match the cadence to how often the work actually arrives. |159| "Let it auto-merge / auto-deploy when it looks good." | Irreversible actions need explicit per-action authorization. A routine prepares and reports; it doesn't pull irreversible triggers unbidden. |160| "No need for a per-run cap, it's a small job." | Small-usually is not small-always. A busy day turns an unbounded run into a grind. Bound every run. |161| "I'll leave it running; it's harmless." | A routine with no teardown runs forever, spending tokens for nothing. Give it an off-switch. |162| "Report only when there's something interesting." | Silent runs hide failures. Report what it did, including 'nothing to do' — just don't flood. |163164## Red Flags165166- A brief that assumes memory of prior runs (no idempotency/dedup rule).167- A cadence far more frequent than the work arrives.168- A routine authorized to take irreversible actions without explicit per-action sign-off.169- No per-run bound (one run can grind or fan out unboundedly).170- No teardown condition; overlapping duplicate routines accumulating.171- A run that fabricates activity or floods the report channel.172173## Guardrails174175- Never write a brief that relies on prior-run memory; make it self-contained + idempotent.176- Never over-schedule; match the cron cadence to the work's arrival rate.177- Never let a routine take irreversible/external actions without explicit authorization.178- Never create an unbounded per-run job; declare a per-run cap.179- Never leave a completed/duplicate routine running; tear it down.180181## Native goal/loop routing182183On Claude Code, a routine's BRIEF should itself be goal-shaped: state the per-run done-condition the184way `/goal` would ("triage every issue opened since the last run; done when each has a label and a185priority"), so each scheduled invocation runs as a bounded goal loop rather than an open-ended prompt.186Routines are the scheduled form; `/loop` is the in-session form; this skill is about choosing and187briefing the former correctly.188189## When To Load References190191- `scripts/validate-job.mjs` — the deterministic job-schema + honest-creation gate (run it in `validate`192 mode on the brief, in `create` mode to stand the routine up dedup-first with a verifiable id).193- `budget-guard` (skill) — the per-run bound + escalation contract each invocation enforces.194- `watch-and-act` (skill) — for a one-off in-session wait instead of a standing routine.195- The phase/loop skills (`auto-*`, `converge-loop`, …) — the actual work a routine's brief invokes.196197## Output Contract198199Report:2002011. the routine created — its job, cadence (with the rationale), the MECHANISM (claude.ai Routine vs202 in-session cron) and why, and the idempotency rule2032. the per-run bound and the report/escalation channel2043. the teardown condition + how to list/cancel it (via `/schedule` for a Routine, or the cron tools for205 an in-session cron)2064. confirmation it registered (or the duplicate it extended instead)