Schedule, run, and verify OpenCode tasks in the background — driven by natural language through MCP, managed from a web UI, and kept alive by your OS as a native service.
Website: 4nkitd.github.io/open-routines
Open Routines is a single Go binary that exposes a Model Context Protocol (MCP) server. It lets an OpenCode agent create routines — one-time or recurring tasks — that are executed later by a fresh OpenCode instance, then independently verified by a second one.
Tell your agent:
"Every day at 8 AM, check my emails and send me a summary to ntfy."
…and it will gather the details, register a routine, and the scheduler takes care of the rest — even after reboots.
- Features
- How it works
- Requirements
- Installation
- Connect it to OpenCode
- Usage
- Scheduling reference
- Configuration
- Webhook notifications
- Logs
- MCP tools
- REST API
- Security
- Troubleshooting
- Contributing
- License
- MCP-native — 7 tools (
routines_create/list/get/update/delete/trigger/runs) over a Streamable-HTTP endpoint, ready to drop into any MCP client. - One-time & recurring — standard 5-field cron,
@hourly/@daily/@every 30mdescriptors, or a single RFC3339 timestamp. - Execute → Verify — each run executes your prompt with OpenCode, then a separate OpenCode instance judges whether it actually succeeded and records a verdict.
- Runs as a native service — installs into systemd (Linux), launchd (macOS), or the Windows Service Manager, so routines keep firing in the background and across reboots.
- Web UI — a clean dashboard on a non-standard port (
47600) to create, edit, trigger, enable/disable routines and browse run history, with a live "next fire times" preview while you type a schedule. - Webhook notifications — get a JSON POST when a run fails (or on every run), globally or per routine.
- File logging with monthly rotation — daemon logs go to
logs/routines-YYYY-MM.log; the last 6 months are kept. - Cross-platform, pure Go — no CGO, builds and runs identically on macOS, Linux, and Windows.
- Embedded storage — routines and run history live in a single bbolt file. No external database.
flowchart LR
A[OpenCode agent] -- routines_create --> B(MCP server)
U[You / Web UI] -- REST --> B
B --> S[(bbolt store)]
B --> SCH{Internal scheduler}
SCH -- at scheduled time --> E[Executor:\nopencode run]
E --> V[Verifier:\nsecond opencode\nJSON verdict]
V --> S
OS[systemd / launchd / SCM] -. keeps daemon alive .-> B
- Plan — the agent calls
routines_createwith a self-contained prompt, a working directory, and a schedule. - Store — the routine is saved to bbolt and its next run time is computed.
- Execute — at the scheduled moment the daemon runs
opencode run "<prompt>" --dir <dir>in the target directory. - Verify — a second OpenCode instance inspects the output and returns
{"success": …, "reason": …}. The result is stored, and recurring routines are rescheduled (one-time routines disable themselves).
Design note: the daemon uses the OS service manager to guarantee it survives reboots, and runs a precise internal cron scheduler inside that always-on process. This is more reliable and portable than writing individual crontab / Task Scheduler entries per routine.
- OpenCode installed and authenticated (
opencode auth login). The routine runner shells out to theopencodeCLI, so whichever models/providers you've configured are what routines will use. - Go 1.25+ — only required to install/build from source.
Download the archive for your OS/arch from the latest release, extract it, and put routines somewhere on your PATH:
# example: macOS Apple Silicon
tar -xzf routines_*_darwin_arm64.tar.gz
sudo mv routines /usr/local/bin/go install github.com/4nkitd/open-routines/cmd/routines@latestThis puts a routines binary in $(go env GOPATH)/bin (make sure that's on your PATH).
git clone https://github.com/4nkitd/open-routines.git
cd open-routines
go build -o routines ./cmd/routinesroutines installThis registers and starts routines as a per-user service (launchd LaunchAgent / systemd user service / Windows service) and prints the Web UI + MCP URLs and a ready-to-paste OpenCode config snippet.
Other lifecycle commands:
routines status # running / stopped
routines stop
routines start
routines restart
routines uninstall # stop + remove the service
routines info # show config paths and the MCP URLAdd the server to your OpenCode config (~/.config/opencode/opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"routines": {
"type": "remote",
"url": "http://127.0.0.1:47600/mcp",
"enabled": true
}
}
}Restart OpenCode and the routines_* tools become available to the agent.
Just ask in natural language. The agent maps your request onto the tools:
"Create a routine that runs every weekday at 9am in
~/work/reports, generates the daily standup notes from git log, and posts them to my ntfy topicstandup."
"List my routines and show the last run of the email one."
"Disable the blinkit routine."
Open http://127.0.0.1:47600:
- Active routines — create, edit, Run now, enable/disable, delete.
- Run history — filter by routine, see every run, click a row to expand execution + verification output.
The CLI manages the service itself; routines are managed via the agent, the web UI, or the REST API.
| Type | once |
schedule example |
Meaning |
|---|---|---|---|
| Cron | false |
0 8 * * * |
every day at 08:00 |
| Cron | false |
*/5 * * * * |
every 5 minutes |
| Descriptor | false |
@hourly, @daily |
shorthand intervals |
| Descriptor | false |
@every 30m, @every 1h30m |
fixed interval |
| One-time | true |
2026-01-02T08:00:00+05:30 |
run once at an absolute time (RFC3339) |
| One-time | true |
2026-01-02 08:00 |
run once (local time, friendly format) |
Cron fields are the standard five: minute hour day-of-month month day-of-week.
Config lives at <config-dir>/routines/config.json:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/routines/ |
| Linux | ~/.config/routines/ |
| Windows | %AppData%\routines\ |
{
"host": "127.0.0.1",
"port": 47600,
"opencode_path": "/path/to/opencode",
"default_model": "",
"db_path": ".../routines/routines.db",
"timeout_seconds": 900,
"webhook_url": "",
"webhook_events": "failure"
}port— non-standard by default to avoid collisions; change it and re-runroutines restart(update your OpenCode config URL too).opencode_path— auto-resolved at install time; set explicitly ifopencodeisn't on the service'sPATH.default_model— used when a routine doesn't specify its ownmodel. Leave empty to use OpenCode's own default.timeout_seconds— default per-run timeout; overridable per routine.webhook_url— global webhook that receives a JSON POST after runs finish; overridable per routine.webhook_events—failure(default, notify only on failed / verify-failed runs) orall(also on success).
Set webhook_url globally in config.json, or per routine (web UI field, webhook_url in the MCP/REST create/update payloads). The per-routine URL wins when both are set.
After a run finishes, the daemon POSTs:
{
"event": "run.failed",
"routine_id": "…",
"routine_name": "Daily email check",
"run_id": "…",
"status": "verify_failed",
"error": "…",
"started_at": "2026-07-02T08:00:01+05:30",
"ended_at": "2026-07-02T08:03:12+05:30",
"duration_seconds": 191.2
}eventisrun.failedorrun.succeeded(run.succeededis only sent whenwebhook_eventsis"all").- Delivery is retried up to 3 times with backoff; a 2xx response counts as delivered.
- Works out of the box with ntfy, Slack incoming webhooks (via a bridge), Discord (via
/slackproxies), or your own endpoint.
The daemon writes logs to <config-dir>/routines/logs/routines-YYYY-MM.log, rotating to a new file each month and keeping the last 6 months. routines info prints the log directory.
| Tool | Description |
|---|---|
routines_create |
Create a routine (name, prompt, schedule, once, directory, model, verify_prompt, timeout_seconds, webhook_url). |
routines_list |
List all routines with status and next run time. |
routines_get |
Fetch one routine by id. |
routines_update |
Patch any field of a routine (only provided fields change). |
routines_delete |
Delete a routine and its run history. |
routines_trigger |
Run a routine immediately, regardless of schedule. |
routines_runs |
List run history, optionally filtered by routine_id. |
The web UI is built on a small JSON API (same port):
| Method & path | Purpose |
|---|---|
GET /api/routines |
list routines |
POST /api/routines |
create routine |
GET /api/routines/{id} |
get routine |
PUT /api/routines/{id} |
update routine |
DELETE /api/routines/{id} |
delete routine |
POST /api/routines/{id}/trigger |
run now |
POST /api/routines/{id}/toggle |
enable/disable |
GET /api/runs?routine={id}&limit={n} |
run history (limit=0 = all) |
GET /api/schedule/preview?schedule={expr}&once={bool} |
next 5 fire times for a schedule expression |
GET /api/info |
port, opencode path, MCP URL |
Warning
Routines execute opencode run with --dangerously-skip-permissions so they can run unattended. A routine prompt can do anything your user account can do (run commands, edit files, make network calls). Only create routines you trust, and keep the server bound to 127.0.0.1 (the default). Do not expose port 47600 to untrusted networks.
| Symptom | Likely cause / fix |
|---|---|
| Runs fail instantly with a model error | The routine/default_model/OpenCode default points at an unavailable model. Set a valid model on the routine. |
opencode: executable file not found |
Set opencode_path in config.json, then routines restart. |
| Web UI/MCP unreachable | routines status; check the port isn't taken; confirm the URL in your OpenCode config. |
| Routine never fires | Make sure it's enabled and the cron/once time is in the future; check routines status. |
| Browser/login tasks fail | Tasks needing a logged-in browser session require that session to already exist; verification will report the failure reason. |
Inspect run output and the verifier's verdict in the Run history panel or via GET /api/runs.
Issues and PRs welcome. Please keep changes cross-platform (no CGO) and run go build ./..., go vet ./... and go test ./... before submitting — CI runs the same checks. Releases are cut automatically by GoReleaser when a v* tag is pushed. See the Wiki for architecture details.
Released under the GNU General Public License v3.0. See LICENSE.