Skip to main content
The normal way to make a routine is the Add routine form: see Set up a routine. This page documents the underlying format the form writes to, for anyone editing an agent file by hand, scripting routine creation, or auditing what a routine actually says. A routine is a YAML object inside the routines: array on an agent’s frontmatter. For the concept, see Routines.

Where routines live

Every routine is declared on the agent it runs for. Inside the agent file’s frontmatter:
Routines are a Rundock concept. The routines: array is read by Rundock’s scheduler and ignored by Claude Code. An agent file with routines works in plain Claude Code; the routines simply do not run there.

Frontmatter fields

Each entry in the routines: array is a YAML object. These five fields are the ones you write by hand: A routine made in the form, or approved in Rundock, also carries these. Rundock writes them; do not hand-edit the plan keys. The whole routines block is Rundock-only. Other tools that read agent frontmatter ignore it.

Schedule format

The schedule field accepts only two patterns. Both are exact regex matches.

Examples that work

Examples that look correct but never fire

The scheduler does not raise an error on a bad schedule string. The routine registers fine and silently never runs on its own; what is no longer silent is the reporting. The routine’s row in the Routines list names the two forms that work and shows no next run time, so a routine that will never fire is told apart from one that is simply not due yet.

Timezone

The schedule is interpreted in the local timezone of the machine running Rundock. A timezone key is recorded on a routine made in the form, but it does not change when the routine fires. If you run Rundock on a VPS in a different timezone from where you live, set the schedule for the VPS’s local time, not yours.

Scheduler behaviour

The scheduler ticks every 60 seconds. On each tick:
  1. Rundock re-discovers all agents (this picks up routine changes without a restart).
  2. For every routine on every agent, the scheduler computes the next run time.
  3. If the next run time has come due, and nothing else about the routine refuses it (not enabled, paused, an unreadable schedule, no prompt), Rundock fires it.
Each routine has a lastRun guard. Daily routines do not re-fire the same calendar day. Weekly routines do not re-fire on the same weekday they last ran on. The guard is held in memory, filled from .rundock/routine-state.json when Rundock starts or you switch workspace, and written back after every run, so a restart does not fire a routine twice. When a routine fires, it runs on its agent’s runtime. For a Claude Code agent, Rundock spawns a headless Claude Code subprocess with the routine’s prompt as the input message. The subprocess runs with --dangerously-skip-permissions because there is no user available to approve tool calls in real time. A routine on a Codex agent runs as a turn on the Codex CLI. Where a routine’s permission card appears. Claude Code’s permission hooks still run for a routine, so a routine’s agent can still raise a card. A routine has no conversation, so the card is matched to its run and shown in the approvals dock, over whatever screen is open, under the routine’s name and the agent’s, such as “Morning Briefing, run by Ada”. It never appears inside a conversation. A request Rundock cannot match to a run is shown in the same dock as unattributed. An unanswered card is denied at the usual timeout, and once a request has timed out, been answered in any window, or lost the process that asked, its card says so and can no longer be answered.

Routines fire only while Rundock is running

This is the central limit and it shapes everything else.
A missed slot is caught up the same day, not lost. If Rundock is closed at 05:00 and you open it at 09:00, the 05:00 routine fires shortly after launch: a next-run time that has already passed stays on today rather than rolling forward to tomorrow. What catch-up cannot do is run on time, or run at all on a day you never open Rundock.
Two practical paths around this:
  • Run Rundock on a small VPS so the scheduler ticks 24/7. Reach it from any device through a browser.
  • Use Anthropic’s Claude Code Routines, Anthropic’s managed scheduling layer, for repo-bound work that can run on their cloud.
The split that tends to work in practice: keep workspace-bound routines (anything that depends on local files or a Rundock agent’s system prompt) on Rundock and host Rundock on a VPS if you need 24/7 firing. Move repo-bound work (PR triage, scheduled code review, release notes) to Anthropic’s Routines.

What Rundock records about a run

When a routine fires, the spawned Claude Code subprocess produces output on stdout (stream-json) and stderr.
Rundock discards this output. The child’s stdout and stderr are attached to the null device: the model’s response, its running commentary, and anything it printed do not flow back into a Rundock conversation or notification.
What Rundock records instead:
  • The routine’s lastRun timestamp.
  • The routine’s status: running, completed, failed, cancelled, or interrupted. completed and failed follow the subprocess exit code, except that a run whose agent declares a failure is recorded as failed (see Declaring that a run failed). cancelled is written when the run was stopped from outside it (see Routines), which is recorded as its own outcome rather than as a failure. interrupted is written when a run left marked running is loaded back and nothing answers for it, meaning Rundock closed mid-run: this is distinguishable from a run that actually failed.
  • The routine’s duration in seconds.
  • A separate record under .rundock/runs/ of the files the run changed: the path, the tool that touched it, whether it was created or edited, and when.
These fields update in the Routines list in real time over the WebSocket. The practical implication: any routine that needs to leave a trace must write that trace itself, through the agent’s tools. A morning briefing creates a file in the daily note. A research digest writes a markdown report. An end-of-day summary adds tasks through a tasks connector over MCP. A routine that asks the model to think out loud will produce output nobody ever reads. There is no built-in notification when a routine completes. You notice a routine ran by either seeing the row update in the Routines list, or seeing the file the agent wrote, or seeing the tasks the agent created.

Declaring that a run failed

A run’s status follows how the process ended, so an agent that hits a problem, says so, and exits cleanly is recorded as completed. When your routine’s skill can fail in a way the process cannot see, such as finishing without doing its job, have the agent declare it. Make the last line of the agent’s final message:
For example:
Rundock records that run as failed, with the reason as its error, and the routine’s own status reads failed too. The Routines list shows it as Failed, and the run’s record shows Reported failed, headed “The agent reported that this run failed.” with “The reason it gave:” and your reason. A run whose process itself failed reads Stopped early instead, so the two are told apart. The reason is optional: RUN STATUS: failed on its own records the failure and says no reason was given. The rules, exactly:
  • Only the last non-empty line of the final message counts. A declaration followed by more text, or a run that ends on a tool call rather than a message, declares nothing.
  • RUN STATUS is matched without regard to case, and markdown emphasis or a code span around the line is ignored. Any other wording is not a declaration.
  • RUN STATUS: succeeded is accepted and changes nothing.
  • A run that declares nothing is recorded exactly as before, so no existing routine changes state.
  • A run that was stopped from outside stays cancelled, whatever it declared.
  • It is read from the run’s session transcript, so it applies to Claude runs. A Codex run leaves no transcript and cannot declare a failure this way yet.
Packaged skills that run on a schedule should use this, so a person who installed them hears about a failure from Rundock rather than by noticing that nothing happened.

The Routines view

Routines is its own entry in the rail. Its sidebar lists scopes: All routines, then each agent that owns routines, with a count beside each. The main pane is the Routines list for the chosen scope, ordered by next run. Each row shows:
  • The routine as a sentence, such as Every day at 5:00am, run: Morning briefing, with the owning agent’s name beneath it.
  • Once it has run at least once, what happened last time, in one of five tones (Ran, Caught up, Missed, Failed, Stopped) and a link to the record.
  • Its controls: Run now, the pause switch, Edit schedule and Delete.
While a routine is running, the row shows Still going and a link to watch it, in place of the last-run tone, or Running now (started manually) for a run you started.
A routine that is not enabled carries a Turn on control instead, and the offer states what pressing it does, including whether it runs shortly after being turned on because its slot has already gone today. The offer is withheld anywhere turning it on would not actually start it (also paused, or its schedule is unreadable), so the control never promises something that would not happen.
The agent profile page has a Routines card. Each entry shows the routine’s name and its schedule in words, such as “Every day at 5:00am”, or the raw schedule string if it is not one the form offers. Selecting it opens the Routines view for that agent.

Running a routine now

Run now runs the routine once, straight away, through the same path a scheduled run takes, and leaves the same kind of record. It is on every row, including a routine that is paused, not enabled, or waiting for approval, because a run you pressed is attended. It is refused only when nothing could run: the routine has no prompt, it names a runOn this release cannot run, or a run of it is already going. The refusal is shown on the row. A manual run moves no schedule. The lastRun guard is not written, and the next run stays exactly where it was, so a test run in the afternoon does not satisfy that evening’s slot. The row’s Ran, Caught up and Missed verdicts come from scheduled runs only. The run record carries trigger: manual for a pressed run and trigger: scheduled for the scheduler’s.

Approval and pausing

A routine made in the form is approved from the moment it exists. Editing its prompt, its skill or its runOn lapses that approval, because an agent can edit a routine and nobody would otherwise know. Editing when it runs, its timezone, pausing it, turning it on or off, or editing the skill’s own file leaves the approval standing. A routine whose approval has lapsed is paused, and its row takes the attention colour, says that what it runs has changed since you last approved it, and offers Review and resume, which approves the plan as it stands on disk. A routine nobody has approved yet, such as one a package brought in, says it is waiting for your approval before its first run, with the same Review and resume. A pause you set yourself is quieter: the row dims, reads Paused, and Resume brings it back. Pausing is the switch on the row, and it is the only control that sets or clears paused.

Upgrading an existing workspace to a version with routines

If a workspace already carried routines written by hand before Rundock could run them, enabled is what decides whether the upgrade starts them.
  • A block with no enabled key reads as not enabled. It is listed, keeps every word of its file, and does not run until turned on. This is the common case for a routine that sat next to a cron job doing the same work by hand: the upgrade does not start a second copy of it without being asked.
  • A block that already says enabled: true (or yes, or on) is live, and the upgrade leaves it alone.
  • A block that says enabled: false (or no, or off) stays off, and the upgrade leaves that alone too.
Migration never touches schedule. A cron expression already there survives an upgrade exactly as written, and still does not fire: cron was never one of the two accepted forms.

Complete worked example

Here is a complete agent file with a single routine. Every field is present.
Notes on this routine:
  • The schedule uses the daily form, two-digit zero-padded.
  • enabled: true is what makes it live; without it, the routine would sit in the list waiting to be turned on.
  • The prompt is short. It assumes the agent’s body knows what “the morning briefing” means.
  • The description is for whoever reads the file. Rundock does not show it, and it does not affect the scheduler.

Common patterns

Five recipes that work in practice.

Morning briefing on the orchestrator

End-of-day summary

Weekly research digest

Weekly digest on a different day to avoid collisions

Multiple routines on one agent, staggered

Stagger the times. Two routines firing in the same minute spawn concurrent Claude Code subprocesses that do not share context, conversation history, or file locks.

Common pitfalls

Cron expressions never fire. The scheduler does not understand cron. A routine with schedule: 0 5 * * * parses fine, registers fine, and never runs. The routine’s row names the unreadable schedule rather than staying silent about it. If a routine appears to do nothing, the schedule string is the first thing to check. Hours without a leading zero never fire. The pattern is exact. every day at 9:00 does not match. Always zero-pad. Abbreviated and multi-day weekdays never fire. Capitalisation is safe: the schedule string is lowercased before matching, so every Friday at 04:00 works. But the parser only matches one full lowercased weekday word. every Fri at 04:00, every Mon-Fri at 09:00, and every weekday at 18:00 do not match. A routine with no enabled: true sits waiting to be turned on. This is deliberate, not a bug: an absent key means not enabled, so a routine written by hand needs the field spelled out to actually run. The routines list offers a Turn on control on the row. Rundock is closed for a whole day. The scheduler is in-process, and catch-up is same-day only: a daily routine missed for an entire calendar day is not replayed the next day, and a weekly routine missed on its weekday waits a week. Opening Rundock later on the same day is fine, the routine fires then. For routines that must run at a particular hour, or on days you might not open Rundock at all, host it somewhere always-on. Routines that need their output read. Rundock discards stdout. If the agent does not write its output somewhere durable through tools, the run produces nothing the user can find later. Always design routines so the agent writes a trace. Two routines on the same agent fire in the same minute. Both spawn Claude Code subprocesses concurrently. They do not share context or file locks. Stagger the schedules unless the routines are genuinely independent and idempotent. Routine name changed after first run. The routine’s lastRun guard is keyed on agentId:name. Renaming a routine creates a new key, which means the new name has no run history and may fire immediately on the next tick if its schedule has already passed. To rename a routine without an immediate re-fire, do it just after a known successful run rather than just before the next due time.

Where to next

Routines concept

What routines are, what they are good for, and the limits worth knowing.

Set up a routine

A walkthrough of adding a routine through the form and confirming it fires.
The full source of truth is docs/ROUTINES.md in the Rundock GitHub repo. It tracks any new fields and scheduler behaviour as they ship.