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.
| Runtime | Providers | MCP | Usage | Use it when |
|---|---|---|---|---|
claude-code (default) | anthropic, bedrock, openrouter, vllm | ✓ | ✓ | The starting point. Best general agentic coding behaviour; the loop the skills are tuned against. |
codex | openai, openrouter, vllm | — | ✓ | You want OpenAI models driving the agent. |
gemini-cli | google | ✓ | ✓ | Existing Gemini runbooks. For new ones, use antigravity. |
antigravity (Google) | google | ✓ | ✓ | You want Gemini models driving the agent. Google's current terminal agent, agy. |
opencode | anthropic, openai, bedrock, openrouter, vllm | ✓ | best-effort | You want an open-source agent with MCP, browser automation and scraping ready on every run. |
hermes (Nous) | anthropic, openai, bedrock, openrouter, google, vllm | — | ✓ | You want one runtime across every provider, including a self-hosted vLLM endpoint. |
pi | anthropic, openai, bedrock, openrouter, google, vllm | — | ✓ | You want a lean agent that leans on CLI tools and skills rather than MCP. |
goose (Block) | anthropic, openai, bedrock, openrouter | — | best-effort | You 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 →
*_pathin the step config → literal ininit_params→*_pathininit_params→ the declared default. Deployed runbooks use the*_pathform for agent, model, provider and snapshot, which is whyinit_paramscan override them. - Tasks created through chat completions store
snapshot,agent_envandmcp_serversas literals in the step config. On those tasks the three can't be overridden frominit_params; send them in thejettyblock, or change them once withPUT /api/v1/tasks/{collection}/{task}. agent_envis how runtime knobs reach the agent (ANTIGRAVITY_EFFORT,OPENAI_REASONING_EFFORT,HERMES_MAX_TURNS). Frontmatter doesn't carry it. The run does.- Omit
jetty.agenton 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-5on OpenRouter,claude-sonnet-5on Anthropic andanthropic.claude-sonnet-5on 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.
| Providers | anthropic, bedrock, openrouter, vllm |
|---|---|
| Model ids | Bare on Anthropic and Bedrock (claude-sonnet-5, anthropic.claude-sonnet-5); vendor-namespaced on OpenRouter (anthropic/claude-sonnet-5); verbatim on a custom endpoint. |
| MCP | Yes. Written to /app/.mcp.json; URL servers get a bearer header from mcp_auth_tokens. |
| Usage | Yes, including cache read and cache creation tokens. |
| agent_env | ANTHROPIC_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: openroutercodex
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.
| Providers | openai, openrouter, vllm |
|---|---|
| Model ids | Bare on OpenAI (gpt-5.5); vendor-namespaced on OpenRouter (openai/gpt-5.5); verbatim on a custom endpoint. |
| MCP | No. Declared servers are logged as a warning and ignored. |
| Usage | Yes. |
| agent_env | OPENAI_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: openaiThe 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.
| Providers | google |
|---|---|
| Model ids | Gemini API ids, bare (gemini-3.1-pro-preview, gemini-2.5-flash). Default gemini-2.5-pro. |
| MCP | Yes, through ~/.gemini/settings.json. |
| Usage | Yes, read from the session file after the run. |
| agent_env | — |
agent: gemini-cli
model: gemini-3.1-pro-preview
model_provider: googleantigravity
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.
| Providers | google |
|---|---|
| Model ids | agy'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. |
| MCP | Yes, through ~/.gemini/config/mcp_config.json; bearer tokens from mcp_auth_tokens are honoured. |
| Usage | Yes, including thinking tokens. |
| agent_env | ANTIGRAVITY_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-playwrightCheaper 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.
| Providers | anthropic, openai, bedrock, openrouter, vllm |
|---|---|
| Model ids | opencode 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). |
| MCP | Yes. Your mcp_servers are merged on top of the Playwright and Firecrawl presets. |
| Usage | Best-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: openrouterhermes
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.
| Providers | anthropic, openai, bedrock, openrouter, google, vllm |
|---|---|
| Model ids | Vendor-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. |
| MCP | mcp_servers is not wired in. Hermes brings its own toolsets instead — terminal, file, web, Firecrawl and Playwright by default. |
| Usage | Yes, read from the session JSON after the run. |
| agent_env | HERMES_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: openrouterPointed 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.
| Providers | anthropic, openai, bedrock, openrouter, google, vllm |
|---|---|
| Model ids | Per provider: anthropic/claude-sonnet-5 on OpenRouter, anthropic.claude-sonnet-5 on Bedrock, gemini-3.1-pro-preview on Google, bare elsewhere. |
| MCP | No, by design. Declared servers are noted in the agent log and skipped. |
| Usage | Yes, including cache tokens. |
| agent_env | OPENAI_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: openroutergoose
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.
| Providers | anthropic, openai, bedrock, openrouter |
|---|---|
| Model ids | Bare. The runner keeps only the last path segment, so anthropic/claude-sonnet-5 reaches goose as claude-sonnet-5. |
| MCP | mcp_servers is not wired in. |
| Usage | Best-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: anthropicSandboxes
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_provider | Required | Optional |
|---|---|---|
anthropic | ANTHROPIC_API_KEY | — |
openai | OPENAI_API_KEY | — |
bedrock | AWS_BEARER_TOKEN_BEDROCK | AWS_REGION |
openrouter | OPENROUTER_API_KEY | OPENROUTER_BASE_URL, OPENROUTER_HTTP_REFERER, OPENROUTER_X_TITLE |
google | GEMINI_API_KEY | GOOGLE_API_KEY |
vllm | VLLM_BASE_URL | VLLM_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
agentininit_params.