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-flag

If 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 hit ARG_MAX and 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: true only 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).

In src/server/resume.ts:

  1. Add your kind to runtimeKind() — it matches on the binary's basename.
  2. Teach buildTurnCommand() how to (a) capture the session id from the first turn's output and (b) pass it back on later turns.
  3. 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.ts built 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.