Skip to content

Agent runtimes ​

A runtime is the agent program fullsend runs inside the sandbox — the thing that talks to the model and executes tool calls. fullsend run delegates to it and owns everything around it: the sandbox, the credentials, and the verdict.

RuntimeUse it forStatus
claudeProduction agent runs (Claude Code)Default
piSecond runtime, opt-in per repo — Claude, Grok and Gemini on Vertex; GPT via OpenAI WIF (wired, not yet exercised live)Supported for all roles
codexThird runtime, opt-in per repo or agent — OpenAI models via the same secretless credential path (wired, not yet exercised live), or Responses-capable models through an inference gateway (gateway/<model>)Opt-in
opencodeFourth runtime, opt-in per repo or agent — reads AGENTS.md nativelyExperimental; read-only agents (triage, prioritize) until the security-hook adapter lands (#7824)
dummyBehaviour tests — scripted ops, no inferenceInternal
dummy-playbackBehaviour tests — replays canned agent results from a playlist, no inferenceInternal

Pick one with runtime: in .fullsend/config.yaml, or per run with --runtime.

bash
fullsend run triage --runtime pi --model xai-vertex/xai/grok-4.6

How a run uses the runtime ​

The runner owns the sandbox, credentials and verdict; the runtime owns what happens between "start" and "event stream".

Loading diagram...

Choosing a runtime ​

Claude Codepicodex
ModelsAnthropic on Vertex; any Messages-capable model through an inference gateway (gateway/<model>)Claude, Grok and Gemini on Vertex; GPT via OpenAI WIF (opt-in, not yet exercised live)GPT, via OpenAI WIF (not yet exercised live); any Responses-capable model through an inference gateway (gateway/<model>, experimental)
Sub-agentsNative (Agent tool)Agent/Task via a fullsend extensionNot available
Default alias modelsopus/sonnet/haiku → claude-*-5-5, passed as the id; fable by Claude Code's own table; sub-agent frontmatter resolved by Claude Codeopus/sonnet/haiku → claude-*-5-5, fable → claude-fable-5-1, children includedNot applicable — OpenAI models only
Fallback model chainAlias requests retried by the runner when Vertex does not serve the model (Claude Code's own "model not available" result naming the model, or Vertex's two 404/403 messages): FULLSEND_FALLBACK_MODELS, else the alias's built-in chain (opus → claude-opus-4-6, sonnet → claude-sonnet-4-6, haiku → claude-haiku-4-5); only an explicit FULLSEND_FALLBACK_MODELS is also passed as --fallback-model, never the built-in chain; pinned ids, retargets and sub-agents fail loudlyThe same chain, same provider only; sub-agent children asking for an alias use its built-in chain; pinned ids and retargets fail loudlyIgnored with a warning
RolesAllAll; review/retro at --thinking medium by defaultSame recommendation as before — no sub-agent roster on codex
Effort--effort low..max--thinking, same levels (high when unset)model_reasoning_effort, same levels
ToolsNative Claude permission syntax--tools (strict) + a first-token Bash allowlistShell + apply_patch only; tools: is recorded, not enforced (the allowlist hook is opt-in)
Security controlsFull matrixFull matrix; stricter on failed-call sanitizingFull matrix; post-tool hooks detect and block but cannot rewrite output
Inference gateway route (gateway/<model>, ADR 0137)Runner-owned ANTHROPIC_BASE_URL, cleared and re-exported after .env; credential via an apiKeyHelper on a re-seeded token file (oidc) or an ANTHROPIC_AUTH_TOKEN placeholder (api-key), sent as Authorization: Bearer in both modes (oidc also copies it into x-api-key); an api-key block with messages_auth_header: x-api-key uses an ANTHROPIC_API_KEY placeholder sent as x-api-key only; no model list; needs a block (details)gateway/<model> through the runner-loaded inference-gateway extension; Responses, Messages or Chat Completionsgateway/<model> through a runner-owned fullsend-gateway provider in the digest-guarded config.toml; Responses only. On every runtime the sandbox holds only the run-scoped provider's placeholder
Cost in metrics.jsonReportedReportedNot reported — codex sends none
Content capture (Level 3)Text, reasoning, tool calls with their arguments, and tool results (correlating ids); the retry prompt under feedback_mode: appendText, reasoning, tool calls (no correlating ids) — pi's parser emits no ids, arguments or tool results yet (#7414); the retry prompt, as on ClaudeText, reasoning, tool calls (no correlating ids) — codex's parser emits no ids, arguments or tool results (#7414); the retry prompt, as on Claude
Tool spans (execute_tool)One per id-bearing tool call (server-side tools get none), up to 1,024 per iteration, a child of the iteration's agent span, timed at receiptNone — the parser emits no call ids (#7414)None — the parser emits no call ids (#7414)

All four run unattended in the same sandbox, behind the same egress allowlist. Choose pi when you want a non-Anthropic model, several vendors from one runtime, or its Agent/Task sub-agent roster. Choose codex when you want OpenAI models specifically and codex's shell-centric way of working. Choose opencode for an experimental open-source runtime backed by configurable providers; it is currently limited to read-only agents (triage, prioritize) until the security-hook adapter lands (#7824).

Selecting a runtime and model ​

First non-empty wins — the usual flag > env var > config > default. fullsend run resolves this once, validates it, prints the source, and records it in metrics.json; runtimes never read the override variables themselves.

Loading diagram...
SettingFlagEnvConfig (per-agent)Config (repo-wide)
Runtime--runtimeFULLSEND_RUNTIMEruntime: on the agent's agents: entryruntime: in .fullsend/config.yaml (repo default)
Model--modelFULLSEND_MODEL (FULLSEND_PI_MODEL on pi and FULLSEND_CODEX_MODEL on codex are lower-precedence aliases)model: on the agent's agents: entryharness model:, then agent frontmatter model:; models.aliases in .fullsend/config.yaml remaps the alias any of these resolve to
Effort--effortFULLSEND_EFFORTeffort: on the agent's agents: entryharness effort:

In CI these are repository variables of the same name, plain or role-prefixed (TRIAGE_FULLSEND_MODEL), so a repo can switch one role's model without a pull request. For durable per-agent configuration that lives in the repository and is reviewable, use the agent's agents: entry in .fullsend/config.yaml instead. Harness env.runner does not reach the fullsend process.

Per-agent runtime, model and effort ​

The agents: list is the per-agent place in config.yaml: an entry names an agent and can set its runtime, model, effort and subagents. A built-in agent (triage, code, review, fix, retro, prioritize) is tuned with a name-only entry; a custom agent carries the settings on its source: entry.

yaml
runtime: pi                    # repo default for agents that set none
agents:
  - name: triage
    model: xai-vertex/xai/grok-4.6
  - name: code
    runtime: claude
    model: sonnet
    effort: high
  - name: review
    subagents:
      default: haiku           # personas that name no model, and children that name no persona
      correctness: opus
  - source: https://raw.githubusercontent.com/acme/agents/<sha>/harness/lint.yaml#sha256=…
    model: haiku

Or from the CLI, which validates the entry before writing it:

  1. fullsend agent set code --runtime claude --model sonnet --effort high
  2. fullsend agent list shows the settings next to each agent — code (built-in) [runtime=claude model=sonnet effort=high], or the source: path for a custom agent.
  3. The next fullsend run code names the entry as the source — Runtime: claude (from <config path> agents.code) — and a --runtime/--model flag on that run still wins.

An invalid value is refused before the write — invalid effort "turbo": must be one of low, medium, high, xhigh, max — and the same check runs on every fullsend run, so a hand-edited entry fails the run before a sandbox starts rather than being skipped.

A source: entry needs no name: — the agent's name is derived from the source file (harness/lint.yaml → lint, ADR 0058), and that is the name the settings, fullsend run lint and fullsend agent set lint all use; add name: only to override it.

Names are agent names as passed to fullsend run <agent> — not harness role: values (code and fix both carry role: coder) — matched case-insensitively. A name-only entry for anything that is not a built-in agent fails validation (coder gets a "did you mean code" hint); a custom agent gets its settings on its own entry.

Precedence: flag > env var > the agent's agents: entry > repo-wide runtime: / harness model: effort: > default. Entries merge per field across the layered config (config.yaml over config.base.yaml), so a preset base can tune agents too. fullsend run validates the whole agents: list in every layer (names, runtime, model syntax, effort) and fails the run with an error naming the file and entry rather than silently skipping a mistyped entry or handing a bad value to the runtime.

A value that came from here shows up as <config path> agents.<name> wherever the selection is surfaced (plan block, stderr, metrics.json — see below); the path is the effective config file.

provider/id is pi's model form. The syntax is accepted for every runtime (model ids are not a closed set), but an entry that pairs runtime: claude with a provider/id model gets a warning in the plan block — Claude Code expects an alias (opus, sonnet, …) or an Anthropic model id.

Migrating from repository variables. A repo that carries <ROLE>_FULLSEND_MODEL / <ROLE>_FULLSEND_RUNTIME variables can move them onto agents: entries one-to-one: the variable prefix is the agent name (CODE_FULLSEND_RUNTIME=claude → - name: code / runtime: claude). Delete the variable afterwards — while it exists it still wins, so the config entry would be silently shadowed. Bump the workflow's fullsend pin to a version that carries per-agent settings before adding them: an older pinned CLI rejects an enabled agents: entry without a source, whereas a current CLI validates the settings on every run.

Set the runtime per repo with fullsend github setup <owner/repo> --runtime pi (GitHub). For GitLab, set runtime: in a repos.yaml config section (defaults.config, gitlab.config, or the entry's config) and run fullsend repos install; do not edit the managed .fullsend/config.yaml directly — see Choosing a runtime. Repos on pi need a sandbox image that carries PI_VERSION; repos on codex need one that carries CODEX_VERSION.

Models ​

On Claude Code, pass an alias (opus, sonnet, haiku, fable) or a model id.

On pi, a model is provider/id — aliases and bare ids still work, and the provider comes from FULLSEND_PI_PROVIDER (default anthropic-vertex). pi reaches Claude, Gemini and Grok, each through its own provider; see Pi › Models and providers.

On codex, a model is an OpenAI id — openai/<id> or the bare id — or gateway/<model> when an inference.gateway block applies (Codex › Models through an inference gateway). The Claude aliases above do not apply: codex serves the OpenAI Responses API only, so opus and friends are refused rather than remapped to a GPT model. Because the fleet harnesses say model: opus, a repo moving to codex names its model either once on the runner with FULLSEND_CODEX_MODEL=openai/gpt-5.6-luna or per agent with model: openai/<id> on the agents: entry — no harness needs editing either way. See Codex › Models.

Harness model: and agents: entry model: values accept provider-qualified provider/id syntax (e.g. google-vertex/gemini-3.8-flash). On pi, a harness can also select a provider with a bare model: plus FULLSEND_PI_PROVIDER.

Per-repo alias overrides ​

Point an alias at a different model for one repo with models.aliases in .fullsend/config.yaml — sonnet: claude-sonnet-5 changes sonnet and leaves the other aliases alone. Works on both runtimes; the override applies to the parent run and to sub-agent dispatch on pi (children resolve aliases through the same merged table). See Pi › Per-repo alias overrides for the syntax and what the plan block shows, and Claude Code › Models for the one limit there (sub-agent model: frontmatter is not remapped).

Where the selection appears ​

SurfaceWhat it shows
Run plan blockRuntime: <name> (from <source>) next to Model and Effort; <source> is the flag, the variable, or <config path> (suffixed agents.<name> when the agent's entry decided)
stderrruntime: selected "<name>" from <source>
Status comment / ::notice::Runtime · Model: <requested → reported> · Effort · Cost
OTel spanfullsend.runtime (harness), gen_ai.system / gen_ai.provider.name (serving endpoint of the model used), next to gen_ai.request.model
metrics.jsonruntime, requested_runtime, runtime_source, requested_model, override_source

requested_model is the model after the per-run overrides (an alias stays the alias name) and override_source says where it came from, with , remapped by <config path> models.aliases appended when a per-repo alias override applied — so a silent override is visible after the fact. The reported model is the provider-stripped id (claude-opus-4-6); for a provider whose ids are publisher-qualified it keeps that segment (xai/grok-4.6), since that is the wire id.

Harness config keys per runtime ​

Harness keys are runtime-neutral in YAML; each runtime owns the translation. Test-only runtimes (dummy, dummy-playback) ignore all harness config keys and are omitted from this table.

Harness keyClaude CodepicodexOpenCode
model--modelalias table (merged with models.aliases), then provider/id; see Models--model <id>; OpenAI ids and gateway/<model> onlyalias table, then provider/model (default provider google-vertex-anthropic); passthrough for provider/model
effort--effort--thinking (superset of the harness levels; high when unset)model_reasoning_effort (same levels)--variant (model reasoning variant)
tools:Native Claude permission syntax--tools (strict) + a first-token Bash allowlistNo native allowlist. Bash(...) lists are recorded but not enforced, entries with no codex tool are dropped with a warning, and the tool-allowlist hook is opt-in (FULLSEND_TOOL_ALLOWLIST)agent frontmatter permission: record ({toolID: "allow"|"deny"}); Claude names mapped to OpenCode IDs; per-argument Bash restrictions collapse to plain bash with a warning; restrict commands with a policy pattern map
skillsCLAUDE_CONFIG_DIR/skills/PI_CODING_AGENT_DIR/skills/, discovered nativelyCODEX_HOME/skills/, discovered nativelyOPENCODE_CONFIG_DIR/skills/, discovered natively
pluginsLoads the plugin.json directories (marketplace layout)Loads the extension directories: uploaded to PI_CODING_AGENT_DIR/extensions/, tree-hash preflight, -e (Plugins, ADR 0094)Unsupported — warned and skippedUnsupported — warned and skipped
security.sandbox_hookshooks.json via --settingsHook scripts + manifest + adapter extensionhooks.json + adapter script under CODEX_HOMENot wired yet (#7824)
validation_loop.feedback_modeReplaces the prompt on retrySameSameSame (RunParams.Prompt honored)

On OpenCode the OPENCODE_CONFIG_CONTENT policy must allow every tool the agent needs: without a TTY, OpenCode rejects any tool call the policy does not allow (OpenCode).

Full per-key detail, including the exact --tools mapping and allowlist parsing rules, is in Implementing an agent runtime.