Adding a runtime
A runtime is a coding-agent CLI that Open Run can spawn. It touches four files at most.
First: is it a good fit?
Open Run spawns a child process, feeds it a prompt, reads its output, and waits for it to exit. A CLI works here only if it can do that without a human.
Good fit
- Non-interactive: takes a prompt, does the work, exits.
- Prompt via stdin or a file path.
- Machine-readable output (JSONL/NDJSON, or ACP).
- A flag to resume a previous session.
Poor fit — please don't open a PR for these
- TUI-only agents that need a terminal.
- IDE extensions (Cursor, Windsurf, Continue). The mismatch is architectural.
- Anything needing a model API key. Open Run holds no keys.
Sanity-check the CLI before writing code:
echo "list the files in this directory" | your-cli --headless-flagIf that prints something and exits, you are in business.
The four levels
Each level works on its own. Stop at whichever one the CLI supports.
| Level | You get | Files |
|---|---|---|
| 1. Preset | It runs. Raw stdout in the log. | lib/runtimePresets.ts |
| 2. Events | A real chat transcript instead of a log dump | lib/agentEvents/ |
| 3. Resume | Follow-up turns and repair turns | server/resume.ts |
| 4. Models | Model and effort pickers | lib/models.ts |
Users can already add any CLI by hand on the Runtimes page — binary, args template, stdin toggle. A preset makes it one click; levels 2–4 make it first-class.
Level 1 — a preset
Add an entry to RUNTIME_PRESETS in
src/lib/runtimePresets.ts:
{
id: 'amp',
label: 'Amp CLI',
bin: 'amp',
argsTemplate: ['--headless'],
promptViaStdin: true,
description: 'Amp CLI in headless mode. Uses your local `amp` login.',
}- Prefer
promptViaStdin: true. Otherwise use{promptFile}. Only fall back to{prompt}in argv if the CLI supports nothing else — long prompts hitARG_MAXand appear in the process list. - Do not put output-format, session, model or permission flags in the template. Open Run injects those and removes template flags it owns. The Command preview on the Runtimes page shows the resulting argv and warns when a template flag was overridden.
canOpenPrs: trueonly if the agent should be told it may branch, commit, push and open a PR.transport: 'acp'if you are wiring it over the Agent Client Protocol — that skips levels 2–4.
Check your work in the UI: Runtimes → Presets, add it, read the command preview, then run an automation against it.
The shortcut: ACP
If the CLI speaks the Agent Client Protocol,
set transport: 'acp' and you are done. The args template only has to launch
the agent; output format, resume, models and permissions become the protocol's
job (server/acpTurn.ts, lib/agentEvents/acp.ts). Check for an ACP mode
before writing an adapter.
Level 2 — a chat transcript
Without this, the run detail page shows raw stdout. With it, you get assistant text, tool calls with status and file locations, and errors.
Add src/lib/agentEvents/yourcli.ts next to
claude.ts,
codex.ts and
grok.ts.
An adapter is a pure function: one line of CLI output in, zero or more canonical
events out. The events are ACP's vocabulary, not ours — the shapes are in
lib/acp.ts. Do not invent a payload field that ACP already
names; lib/acpConformance.ts type-checks the subset against the real SDK.
Register it in lib/agentEvents/index.ts.
Write the test as you go. Paste real captured output from the CLI into
yourcli.test.ts and assert the events.
Adapters must survive:
- A partial line (the process wrote half a JSON object; the rest arrives next chunk).
- A line the adapter does not recognise — ignore it, never throw.
Level 3 — resume
Unlocks follow-up turns and repair turns (handing failed checks back to the same session).
- Add your kind to
runtimeKind()— it matches on the binary's basename. - Teach
buildTurnCommand()how to (a) capture the session id from the first turn's output and (b) pass it back on later turns. supportsResume()returns true for any non-generic kind that has a resume flag. Gemini over the CLI transport is the counter-example: it has a model catalog but no headless resume, so it stays single-shot. Gemini ACP resumes via the protocol.
Existing shapes: claude --resume <id>, codex exec resume <id>.
Level 4 — models and effort
Add a catalog entry in src/lib/models.ts to get model
and effort pickers. That list is the fallback seed; at runtime
server/modelCatalog.ts prefers models the installed CLI actually reports.
The executor injects the flags.
Before you open the PR
pnpm typecheck
pnpm test
pnpm build- Adapter has a colocated
*.test.tsbuilt from real captured output - The command preview shows the argv you expect, with no warnings
- A real run produces a readable transcript, not a log dump
- Follow-up turn works (if you did level 3)
-
changelog.d/entry, in the negative-relief voice: "You no longer …" - README mentions the CLI if it is first-class
Say in the PR which CLI version you tested against.
Getting help
Open a runtime request issue with the CLI's headless invocation and output format.