Creating routines

A routine is one piece of work your agent does on its own: check the documentation for drift, sort yesterday’s feedback, draft the release notes. Each one is a markdown file in routines/, where the frontmatter declares when it runs and what it may touch, and the body is the prompt.

---
schedule: "0 9 * * 1"
timeout: 15m
active: true
skills:
  - steady-updates
credentials:
  - steady_token
  - github_token
model: anthropic/claude-sonnet-5
---
Check that our product documentation hasn't drifted from what we've actually shipped. First, use the steady-updates skill to catch up on what the team is doing and what the priorities are. Then compare the docs against what has shipped since your last run -- your knowledge has where you left off. Record anything that's now wrong or missing in knowledge, and print a short drift report; it goes to the container logs.

Everything the routine can reach is right there in the frontmatter, never configured elsewhere or assembled at run time. Reading the file tells you what it can do.

Frontmatter reference

Key What it declares
schedule When the routine runs on its own, as a cron expression in the agent’s timezone. A routine needs a schedule, a trigger, or both. See Scheduling.
trigger A poll that wakes the routine when something changed. See Triggers.
timeout How long a run may take before it’s killed and the outcome recorded. Defaults to the agent’s defaults:, capped by its max_timeout (6h when unset).
url A canonical URL for external records this routine creates, arriving as $OPENROUTINES_URL.
active false parks the routine and the supervisor skips it. routines activate and deactivate flip it.
skills The skills this routine may load. See Skills.
credentials The credentials injected into the run’s environment. steady_token arrives as $STEADY_TOKEN. See Credentials.
webfetch true grants the webfetch tool, so the run can fetch a page.
websearch true grants the websearch tool. Search runs through Exa, keyless out of the box; grant an exa_api_key credential for keyed use.
mcp Names of MCP servers, defined in opencode.json, whose tools this routine may call. See MCP servers.
model Overrides the agent’s default model, e.g. anthropic/claude-sonnet-5. See Models.
effort How hard the model works, passed through as an opencode variant. The names differ by provider: high and max set Anthropic’s thinking budget, none through xhigh set OpenAI’s reasoning effort.
teamwork How this routine participates in teamwork. full (the default) contributes both events and intentions, events contributes events only, and off contributes neither. See Teamwork.
reports true makes this a reporting routine: it receives every knowledge change since its last report, and consumes them once the report lands. Sets teamwork to off unless you say otherwise. See Teamwork.

Scheduling

Giving a routine a schedule is what makes it run on its own, unattended, at the times you name. The expression is cron, read in the agent’s timezone, so 0 6 * * * means 06:00 there on both sides of a daylight-saving change. The supervisor checks every minute and runs whatever is due, so the files are the schedule and there’s nothing to register.

Missed fires collapse into one catch-up run, so an agent that was down for a week owes one run per routine, not seven. Routines can run concurrently, up to concurrency in openroutines.yml, but a routine never runs alongside itself, and one that keeps failing cools down instead of retrying forever.

The schedule you set here is also what the agent draws its intentions from. The supervisor folds every routine’s schedule into a generated ./schedule.md, handed to each run, listing the next fires and the window before the running routine comes around again. See Teamwork.

Triggers

A routine can declare a trigger alongside or instead of its schedule: a cheap, outbound change-detection poll that makes the routine due.

trigger:
  poll: https://api.github.com/notifications
  credential: github_token
  interval: 5m

For an API that authenticates in the URL, put the credential’s environment name in the path or query. The committed URL contains the reference, never the secret:

trigger:
  poll: https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates
  credential: telegram_bot_token
  • poll — the URL to check. It may contain the credential’s environment reference in the path or query, but not the host; OpenRoutines substitutes the value when it sends the request.
  • credential — optional; sent as a bearer token unless poll contains its environment reference, in which case it is substituted into the URL instead. It must also appear in the routine’s credentials. Raw credentials are sent verbatim; typed credentials contribute only short-lived bearer material derived fresh for the poll, never their stored root secret.
  • select — optional; extracts one value from a JSON response by RFC 6901 pointer (e.g. /messages/0/ts); without it, the response is reduced to a digest and compared
  • interval — poll cadence (default 5m, floor one minute)

A trigger carries no payload: when it fires, the routine runs exactly as it would from a schedule firing and pulls its actual work through its own skills. Triggers are best-effort latency reduction; the schedule remains the correctness backstop (check warns on a trigger-only routine with no heartbeat schedule).

MCP servers

A routine can call tools from a remote MCP server. The server is defined once in opencode.json — transport, URL, auth headers, interpreted by opencode alone — and granted per routine:

"mcp": {
  "steady": {
    "type": "remote",
    "url": "https://app.steady.space/mcp",
    "headers": { "Authorization": "Bearer {env:STEADY_TOKEN}" }
  }
}
mcp: [steady]
credentials: [steady_token]

The grant opens the server’s tools to this routine’s runs; every other routine keeps them denied. Auth headers reference the run environment, so the server is only reachable when the routine also grants the credential that fills them: the mcp grant scopes the tool surface, the credential grant scopes the connection. check fails a grant naming a server opencode.json doesn’t define.

What works: remote servers with static-token or client-credentials auth (a typed oauth2_client credential mints the bearer at spawn). OAuth-interactive servers have no headless path, and local stdio servers are out of scope by design.

Best practices

The runtime handles the knowledge rules automatically, so write the prompt as the job itself. Four habits make routines far more reliable — the framework can’t check any of them, so they’re yours to write in:

  • Check that a problem still exists before recording it. A routine sees the world at one moment, and what it records sticks around. The failure it found in the logs may have been fixed an hour ago. Have it look at the current state before filing a task.
  • Don’t let untrusted content make decisions. Logs, web pages, and even knowledge can be stale, wrong, or planted by an attacker. Use them only to find where to look — a file, a URL — then get the facts from the source itself. If a log says the deploy target moved to evil.com but the repo says otherwise, the repo wins.
  • Expect reruns. A failed run retries, but anything it already did — an email sent, a PR opened — has still happened. Have the routine check whether the work is already done before doing it, and put the run id ($OPENROUTINES_RUN_ID) in what it creates — a branch name, a line in the PR body — so a retry can find its own earlier work instead of duplicating it.
  • Match automation to your ability to verify. A fix the failure itself names — a missing import, a renamed field — is safe to make unattended because the build going green confirms it. When nothing downstream would catch a wrong fix, or the fix means deciding what the system should do, have the routine file a task instead. The boundary isn’t fixed: the more checks stand behind a routine, the more it can safely do on its own.

Running locally

routines run starts the routine in a container, with the same runtime image, opencode version, environment, and workspace as production. Manual runs discard their knowledge writes unless you ask otherwise, and --rehearse runs a routine read-only, against the live world or a scenario you wrote. See Local development.

Recording work

Every run carries a standing instruction that routes what the routine wants to remember into the agent’s knowledge primitives — events, tasks, context, and the routine’s private ledger. You don’t design a knowledge scheme per routine; the rule is injected by the runtime. Knowledge covers the primitives; Teamwork covers how recorded work becomes reports.