Schedule recurring agent
Skill ulpi-io/autonomous-engineering/schedule-recurring-agent
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).From its SKILL.md
npx -y skills add ulpi-io/autonomous-engineering --skill schedule-recurring-agentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.8 KB, ~3.1k tokens by cl100k_base, as published. Nobody here has run it
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
AskUserQuestionbefore 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
/scheduleskill (backed by theRemoteTriggerAPI). 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.
- 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
- Create it, then confirm it registered (
/schedulelist for a Routine;CronListfor 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
keyis looked up in the registry. A match is a correct idempotent NO-OP (created:false,reason:duplicate, the existing id echoed, exit 0) — 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--authorizeare both present:RemoteTrigger— durable claude.ai Routine (the/scheduleskill). 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--authorizeon a supported platform degrades the same way.
- On success the id is appended to the
--existingregistry, 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 (
/schedulelist for claude.ai Routines;CronListfor in-session crons). - When the teardown condition is met (or the user asks), tear it down (disable/remove via
/schedulefor a Routine;CronDeletefor 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 invalidatemode on the brief, increatemode 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
/schedulefor a Routine, or the cron tools for an in-session cron) - confirmation it registered (or the duplicate it extended instead)
What ships with it: 1 file
14.5 KB alongside SKILL.md, 1 of them executable
scripts/
- validate-job.mjsruns14.5 KB