Local development
You write routines on your machine and run them there before anything is deployed. A local run uses the same container, the same runtime, and the same credentials the routine will have in production, so what you see locally is what the agent will do.
What you need
A Docker runtime on your local machine, running. Docker Desktop is the recommended option; routines run shells out to the docker command, so any runtime that provides it works.
The first run builds the agent’s runtime image; every run after that reuses it. If the daemon isn’t up, the build fails with an error asking “is Docker running?”
Write, rehearse, run
A routine is a markdown file in routines/: schedule and grants in the frontmatter, the prompt in the body. Creating routines covers every key you can declare.
Most of this is editing that file in your editor. The CLI wraps the same edits with validation:
openroutines routines new doc-drift # or write routines/doc-drift.md yourself
openroutines check # validate before running anything
openroutines routines run doc-drift --rehearse # see what it would do
openroutines routines run doc-drift # do it for real
openroutines routines activate doc-drift # or set active: true in the frontmatter
Creating, editing, and activating a routine are all just the file: routines new scaffolds it, routines edit opens it in $EDITOR and validates on close, and activate flips one frontmatter key. Reach for whichever is less friction.
check and run are the two you need the CLI for. check validates config, frontmatter, and schedules, catching a bad cron expression or a credential nothing declares, and it’s the same command you run in CI. run builds the workspace, constructs the environment from the routine’s grants, and starts the container.
Rehearse while the prompt is still changing, which is safe to repeat precisely because nothing lands. A real run does land: it sends the email, opens the pull request, writes to the API, and throwing away its knowledge afterward undoes none of that. Save it for when you want those things to happen, and let it record what it did. Then activate and commit.
Rehearsals
A rehearsal runs the real routine without consequence: for seeing what it would do, auditioning models, and testing a prompt change without waiting for a live fire.
openroutines routines run steady-check-in --rehearse # the live world, read-only
openroutines routines run announcements --rehearse cold-start # a world you wrote
Out of the box a rehearsal needs no files. The routine keeps its credentials and tools so its reads work, and an injected preamble tells the model to keep every external action read-only and to print what it would have delivered instead of delivering it. That part is instruction, not enforcement. What is enforced is that nothing settles — no knowledge writes, no feed consumption, no run record — so a rehearsal is always safe to repeat.
A fixture turns the rehearsal into a simulated world: deterministic inputs, a frozen moment to work as if, and no grants at all, since the runner strips credentials, MCP, skills, and web access and hands the routine the fixture as a read-only ./rehearsal.md. Reach for one when you want a repeatable scenario, like a quiet day or a cold start, rather than whatever the world happens to hold right now.
Fixtures bind to routines by name. A routine with one scenario gets a flat file; a routine with several graduates to a directory:
rehearsals/
├── steady-inbox.md # run steady-inbox --rehearse
└── steady-check-in/ # several scenarios for one routine
├── default.md # run steady-check-in --rehearse
└── quiet.md # run steady-check-in --rehearse quiet
check warns when a fixture’s name matches no routine, which is how you find out a rename stranded one.
There’s no fixture syntax to learn, because the file is prompt text. What works is a scenario that fixes the moment, lays out the inputs in the shapes the routine expects, and says what to print:
Work as if it is Fri 2026-07-31 08:00 (America/Los_Angeles). The fixtures
below stand in for the live systems, and their formats are authoritative:
work from them, not from the knowledge files on disk.
## Fixtures
`./schedule.md` at the simulated time:
now: Fri 2026-07-31 08:00 (America/Los_Angeles)
window: now -> Mon 2026-08-03 07:00
in-window
(none)
Your open action items, the week's new events, the check-in ledger, and
anything else the routine reads, each in the shape it arrives in.
## Output
Print, and nothing else: the exact check-in you would have submitted,
every field verbatim, or the statement that you would not file, and why.
Naming the moment matters most, since everything the routine reasons about hangs off it. Being explicit that the fixtures outrank what’s on disk matters next: the routine can still see real knowledge files, and you want it working from the scenario.
Running for real
openroutines routines run doc-drift # knowledge writes are discarded
openroutines routines run doc-drift --write-knowledge # knowledge writes are settled
Both are real runs. They get the routine’s credentials and tools and can act on the outside world, and the flag changes only what happens afterward. By default a manual run throws away its knowledge writes and run record, because the terminal is where you iterate and iterating shouldn’t teach the agent or consume the change feed a reporting routine is waiting on.
--write-knowledge makes the run count: its writes settle, its cursor advances, and it leaves a run record, exactly as if the schedule had fired it. Which one you want depends on what the routine did. A routine that only reads loses nothing when its knowledge is discarded. A routine that acted and recorded nothing leaves the agent believing it never acted, which is how the same pull request gets opened twice.
Shipping a change
Because a deployed agent runs from its image, committing and pushing changes nothing in production by itself. Your edit reaches the agent when that image is rebuilt and redeployed, and not before. That goes for turning a routine off as much as adding one: deactivating a routine that’s misbehaving stops it at the next deploy, not the next tick.
You can rebuild and deploy by hand, or set up continuous deployment so a merge does it for you: run openroutines check on every push, then rebuild and redeploy on merge to main. Pushes to the knowledge branch never trigger a deploy, so an agent recording its own work never redeploys itself. See Deploying your agent.