Documentation menu

Runtime Configuration

When Jetty runs a runbook, it executes a real coding agent inside a managed sandbox. You choose which agent — the runtime — and which provider backs it. This is the other half of agent integrations: instead of running your agent elsewhere and grading on Jetty, you run the agent on Jetty.

The runbook is plain markdown; the runtime that reads it is swappable. If a provider changes pricing or a model regresses, you change one value, not your workflow. This page covers how to make that change without touching the file, then goes runtime by runtime.

The runtimes

Eight runtimes are registered today. The Providers column lists the values you can put in model_provider for that runtime; MCP says whether the runtime honours mcp_servers on a task; Usage says whether token counts land on the run.

RuntimeProvidersMCPUsageUse it when
claude-code (default)anthropic, bedrock, openrouter, vllmThe starting point. Best general agentic coding behaviour; the loop the skills are tuned against.
codexopenai, openrouter, vllmYou want OpenAI models driving the agent.
gemini-cligoogleExisting Gemini runbooks. For new ones, use antigravity.
antigravity (Google)googleYou want Gemini models driving the agent. Google's current terminal agent, agy.
opencodeanthropic, openai, bedrock, openrouter, vllmbest-effortYou want an open-source agent with MCP, browser automation and scraping ready on every run.
hermes (Nous)anthropic, openai, bedrock, openrouter, google, vllmYou want one runtime across every provider, including a self-hosted vLLM endpoint.
pianthropic, openai, bedrock, openrouter, google, vllmYou want a lean agent that leans on CLI tools and skills rather than MCP.
goose (Block)anthropic, openai, bedrock, openrouterbest-effortYou want to compare against Block's agent.

Best-effort usage means the runtime doesn't emit token counts reliably, so a null or zero usage on the run is not a failure signal. New to this? Stay on claude-code until you have a reason to switch.

Frontmatter sets the default

A runbook names its runtime in frontmatter, so the choice travels with the file:

---
agent: claude-code                 # claude-code | codex | gemini-cli | antigravity | opencode | hermes | pi | goose
model: anthropic/claude-sonnet-5   # slug in the provider's own format
model_provider: openrouter         # anthropic | openai | bedrock | openrouter | google | vllm
snapshot: python312-uv             # python312-uv | prism-playwright
---

Leave all of them out and you get claude-code on anthropic/claude-sonnet-5 via openrouter in the python312-uv sandbox. Set model without model_provider and Jetty picks the provider: OpenRouter if the collection has an OPENROUTER_API_KEY and the runtime supports it (codex is excluded; see below), then Bedrock if there is an AWS_BEARER_TOKEN_BEDROCK, then the runtime's native provider — Anthropic for claude-code, OpenAI for codex, Google for gemini-cli and antigravity. opencode, hermes, pi and goose have no native provider, so name model_provider for them. Naming it is the safe habit everywhere; the inference is a convenience that will eventually go away.

Override it per run

The frontmatter is a default, not a lock. When you deploy a runbook, Jetty stores agent, model, model_provider and snapshot as init_params on the task, and the run step reads them from there. Whatever you send when you start a run merges over those defaults, caller wins. So a deployed runbook can run on another agent, model or provider without a redeploy and without a second copy of the file. That is what turns a model sweep into a loop instead of a project.

Two ways to send them. From the run endpoint, pass init_params:

curl -X POST https://flows-api.jetty.io/api/v1/run/my-collection/cbc-homepage-summary \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "init_params": {
      "agent": "antigravity",
      "model": "gemini-3.7-flash",
      "model_provider": "google",
      "agent_env": { "ANTIGRAVITY_EFFORT": "medium" },
      "vars": { "url": "https://www.cbc.ca/" }
    }
  }'

From chat completions, the model goes at the top level and the rest in the jetty block. The runbook travels inline as the system message, frontmatter included:

curl https://flows-api.jetty.io/v1/chat/completions \
  -H "Authorization: Bearer $JETTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.7-flash",
    "messages": [
      { "role": "system", "content": "<contents of RUNBOOK.md>" },
      { "role": "user", "content": "Execute the runbook." }
    ],
    "jetty": {
      "runbook": true,
      "collection": "my-collection",
      "task": "cbc-homepage-summary",
      "agent": "antigravity",
      "model_provider": "google",
      "snapshot": "prism-playwright",
      "agent_env": { "ANTIGRAVITY_EFFORT": "medium" },
      "template_variables": { "url": "https://www.cbc.ca/" }
    }
  }'

The run form in the web app and the run-workflow tool in the MCP server send the same init_params, so anything below applies there too.

A few rules decide what actually wins:

  • Inside a step, a parameter resolves in this order: literal in the step config → *_path in the step config → literal in init_params *_path in init_params → the declared default. Deployed runbooks use the *_path form for agent, model, provider and snapshot, which is why init_params can override them.
  • Tasks created through chat completions store snapshot, agent_env and mcp_servers as literals in the step config. On those tasks the three can't be overridden from init_params; send them in the jetty block, or change them once with PUT /api/v1/tasks/{collection}/{task}.
  • agent_env is how runtime knobs reach the agent (ANTIGRAVITY_EFFORT, OPENAI_REASONING_EFFORT, HERMES_MAX_TURNS). Frontmatter doesn't carry it. The run does.
  • Omit jetty.agent on chat completions and Jetty guesses from the model name: gemini*gemini-cli, claude* claude-code, anything else → codex. Name the agent.
  • When you switch providers, switch the slug format with it. The same model is anthropic/claude-sonnet-5 on OpenRouter, claude-sonnet-5 on Anthropic and anthropic.claude-sonnet-5 on Bedrock. A mismatched slug is the most common cause of a run that exits in seconds having produced nothing.
  • Every runtime receives the substituted runbook as one command-line argument, capped at 128 KiB. Reference a large input once in the body and ship the rest as files.

Runtime by runtime

Each section lists what the runtime can reach, the model id format it expects, what it does with mcp_servers, the agent_env knobs it reads, and a configuration that runs today. Every runtime executes with its own permission prompts switched off; the sandbox is the boundary.

claude-code

Anthropic's agent, and the default. Runs claude -p with a fixed tool allowlist (Bash, Edit, Write, Read, Glob, Grep, WebFetch, notebooks, todos, sub-agents) plus mcp__<server>__* for each MCP server you declare. Versions are pinned and bumped deliberately.

Providersanthropic, bedrock, openrouter, vllm
Model idsBare on Anthropic and Bedrock (claude-sonnet-5, anthropic.claude-sonnet-5); vendor-namespaced on OpenRouter (anthropic/claude-sonnet-5); verbatim on a custom endpoint.
MCPYes. Written to /app/.mcp.json; URL servers get a bearer header from mcp_auth_tokens.
UsageYes, including cache read and cache creation tokens.
agent_envANTHROPIC_AUTH_TOKEN for custom endpoints that only accept a bearer token; ANTHROPIC_CUSTOM_HEADERS to add request headers.

vllm here means any endpoint that speaks the Anthropic Messages API (a LiteLLM proxy, Bedrock Mantle's /anthropic path). The SDK appends /v1/messages itself, so VLLM_BASE_URL is the segment before that. On OpenRouter every request carries the run id as x-session-id, so traces group per run in OpenRouter's dashboard.

agent: claude-code
model: anthropic/claude-sonnet-5
model_provider: openrouter

codex

OpenAI's agent, pinned to a known-good release (@latest broke three times in one week). Runs codex exec --json with approvals and its own sandbox turned off, since the Jetty sandbox already isolates the run.

Providersopenai, openrouter, vllm
Model idsBare on OpenAI (gpt-5.5); vendor-namespaced on OpenRouter (openai/gpt-5.5); verbatim on a custom endpoint.
MCPNo. Declared servers are logged as a warning and ignored.
UsageYes.
agent_envOPENAI_REASONING_EFFORT (minimal to xhigh, model-dependent). OPENAI_WIRE_API only matters on older pins.

Two things trip people up. The Rust CLI ignores OPENAI_BASE_URL, so routing is written to config.toml and codex is left out of the OpenRouter auto-default: to run it on OpenRouter, say so. And codex 0.146 and later speak only the Responses API, so a chat-completions-only endpoint (plain vLLM) can't be driven by codex; use pi, opencode or hermes for those.

agent: codex
model: gpt-5.5
model_provider: openai

The same runbook on OpenRouter, with more thinking, from the run endpoint:

{
  "init_params": {
    "model": "openai/gpt-5.5",
    "model_provider": "openrouter",
    "agent_env": { "OPENAI_REASONING_EFFORT": "high" }
  }
}

gemini-cli

Google's earlier terminal agent, run as gemini --yolo. Google has wound it down in favour of Antigravity CLI. It stays registered so existing runbooks keep running; start new Gemini work on antigravity.

Providersgoogle
Model idsGemini API ids, bare (gemini-3.1-pro-preview, gemini-2.5-flash). Default gemini-2.5-pro.
MCPYes, through ~/.gemini/settings.json.
UsageYes, read from the session file after the run.
agent_env
agent: gemini-cli
model: gemini-3.1-pro-preview
model_provider: google

antigravity

Google's current terminal agent, agy. Its default sign-in is a browser flow, so Jetty runs it in Gemini-API-key mode instead: the sandbox gets a settings file with modelProvider: gemini and the collection's GEMINI_API_KEY. Print mode, permissions auto-approved, a 120-minute print timeout in place of the CLI's five-minute default.

Providersgoogle
Model idsagy's own catalog, not Gemini API ids: gemini-3.7-flash, gemini-3.1-pro, each with an effort tier. A tiered slug (gemini-3.7-flash-medium) carries the tier itself. Default gemini-3.7-flash. gemini-2.5-pro is rejected.
MCPYes, through ~/.gemini/config/mcp_config.json; bearer tokens from mcp_auth_tokens are honoured.
UsageYes, including thinking tokens.
agent_envANTIGRAVITY_EFFORT (low / medium / high, default high; none omits the flag). ANTIGRAVITY_PRINT_TIMEOUT (a duration like 90m, default 120m).

The tier rules are agy's: a bare slug needs an effort, a tiered slug must not be combined with one, and gemini-3.1-pro only has low and high. Jetty normalises either form, so write whichever reads better and use ANTIGRAVITY_EFFORT to change the tier per run. high is the default because it is the one tier every catalog model accepts.

agent: antigravity
model: gemini-3.7-flash
model_provider: google
snapshot: prism-playwright

Cheaper tier for a routine run, no file change:

{ "init_params": { "agent_env": { "ANTIGRAVITY_EFFORT": "medium" } } }

opencode

The open-source SST agent, run as opencode run. Headless opencode auto-rejects any permission left on “ask”, and models treat that rejection as a stop signal, so Jetty writes an opencode.json that allows everything up front. That file also registers Playwright and Firecrawl MCP servers on every run, Firecrawl with a Jetty-provided key, so browser automation and scraping work without any setup on your side.

Providersanthropic, openai, bedrock, openrouter, vllm
Model idsopencode wants provider/id; Jetty adds the prefix. Keep the vendor namespace on OpenRouter (anthropic/claude-sonnet-5), bare ids elsewhere, Bedrock's dotted form (anthropic.claude-sonnet-5).
MCPYes. Your mcp_servers are merged on top of the Playwright and Firecrawl presets.
UsageBest-effort. opencode prints text rather than a stable event stream.
agent_env

vllm goes through the AI SDK's OpenAI-compatible loader on the chat completions wire, which makes opencode a good fit for a plain vLLM or SGLang server.

agent: opencode
model: anthropic/claude-sonnet-5
model_provider: openrouter

hermes

Nous Research's agent, run as hermes chat -Q. The one runtime that reaches all six providers from a single config, including a self-hosted vLLM endpoint, and the route to Nous Portal subscription credits. Jetty pre-seeds Hermes's provider file with its own entries so requests go where you pointed them rather than through Hermes's built-in aliases.

Providersanthropic, openai, bedrock, openrouter, google, vllm
Model idsVendor-namespaced on OpenRouter, bare on native providers, and passed through untouched on vllm so HF-style ids like meta-llama/Llama-3.1-8B survive.
MCPmcp_servers is not wired in. Hermes brings its own toolsets instead — terminal, file, web, Firecrawl and Playwright by default.
UsageYes, read from the session JSON after the run.
agent_envHERMES_TOOLSETS (default terminal,file,web,firecrawl,playwright), HERMES_MAX_TURNS (default 300), JETTY_HERMES_PROVIDER to force a provider entry.

Hermes has no native provider, so model_provider is required. The turn budget is a hard stop: when it fires the agent ends mid-flight, outputs written or not, so raise it for deep-research runbooks rather than hoping. Bedrock goes through Hermes's own plugin with the bearer token, which gets you prompt caching and thinking budgets on that path.

agent: hermes
model: anthropic/claude-sonnet-5
model_provider: openrouter

Pointed at your own server, with a longer budget:

{
  "init_params": {
    "model": "meta-llama/Llama-3.1-70B-Instruct",
    "model_provider": "vllm",
    "agent_env": { "HERMES_MAX_TURNS": "600" }
  }
}

pi

A small, provider-agnostic agent that ships without an MCP client on purpose. Give it CLI tools and skills. Jetty feeds it the runbook on stdin and pins its config to the run's log volume, so nothing it writes leaks into the workspace it may be committing from.

Providersanthropic, openai, bedrock, openrouter, google, vllm
Model idsPer provider: anthropic/claude-sonnet-5 on OpenRouter, anthropic.claude-sonnet-5 on Bedrock, gemini-3.1-pro-preview on Google, bare elsewhere.
MCPNo, by design. Declared servers are noted in the agent log and skipped.
UsageYes, including cache tokens.
agent_envOPENAI_WIRE_API for vllm endpoints: chat (default), responses or messages.

vllm is registered as a custom provider in pi's models.json, so any served id works verbatim. No native provider: name model_provider.

agent: pi
model: anthropic/claude-sonnet-5
model_provider: openrouter

goose

Block's agent, run as goose run --quiet from a file (its argument parser chokes on a runbook that starts with ---). Included so you can compare. The output parser is best-effort.

Providersanthropic, openai, bedrock, openrouter
Model idsBare. The runner keeps only the last path segment, so anthropic/claude-sonnet-5 reaches goose as claude-sonnet-5.
MCPmcp_servers is not wired in.
UsageBest-effort.
agent_env

Because the vendor prefix is dropped, OpenRouter ids lose the namespace OpenRouter needs. Run goose on a direct provider. No native provider: name model_provider.

agent: goose
model: claude-sonnet-5
model_provider: anthropic

Sandboxes

python312-uv is the default: Python 3.12 with uv, Node, Chromium and the Playwright libraries, with claude-code, codex, opencode and hermes pre-installed. prism-playwright is the same idea with Playwright and Chromium front and centre. The other runtimes install on first use, which costs a few seconds per run. A custom image or Dockerfile works too; the machine instructions cover image, pip_packages and apt_packages.

Provider keys

Each provider needs a key on the collection. Configure them in Settings → AI providers, or check what's present from an agent with the check-secrets tool.

model_providerRequiredOptional
anthropicANTHROPIC_API_KEY
openaiOPENAI_API_KEY
bedrockAWS_BEARER_TOKEN_BEDROCKAWS_REGION
openrouterOPENROUTER_API_KEYOPENROUTER_BASE_URL, OPENROUTER_HTTP_REFERER, OPENROUTER_X_TITLE
googleGEMINI_API_KEYGOOGLE_API_KEY
vllmVLLM_BASE_URLVLLM_API_KEY

vllm is the catch-all for a self-hosted or custom endpoint — vLLM, SGLang, TGI, LiteLLM, or any OpenAI- or Anthropic-compatible server. Which wire it must speak depends on the runtime, as noted above. See Integrations for how keys reach the sandbox.

Trade-offs to know

  • Prompt style matters. Some runtimes need the runbook to imperatively demand tool use; a prose-style system prompt can cause an agent to reply conversationally and write zero files. Keep instructions action-oriented.
  • Read the agent log first. A run that finishes in seconds with no results almost always failed inside the agent CLI, and the reason is in its log on the run: a slug the provider doesn't know, a missing key, a flag the CLI rejected.
  • When in doubt, A/B it. The bring-your-own-framework pattern works for comparing two runtimes too — same grader, different agent in init_params.

Next: write a runbook → or read the run it produces →