Assignments and execution environments declare typed variables that are resolved at run creation, frozen into the manifest, and interpolated into agent instructions, assignment instructions, task titles, task bodies, caller instructions, environment fields, mounts, and workspace lifecycle steps.
Variables are declared on the assignment under vars:
---
schemaVersion: 1
name: code-review
vars:
range:
type: string
sources: [cli]
default: full
description: Git range to review (e.g. main..HEAD)
implementation_run_id:
type: string
sources: [cli]
required: true
log_level:
type: enum
values: [debug, info, warn]
sources: [parent, env]
envName: LOG_LEVEL
default: info
tasks: [...]
---Selected execution environments can also declare vars using the same
schema. Environment vars are merged with assignment vars for that run, so
environment-required inputs are accepted through the normal --var, web,
env, default, and parent channels without every assignment having to
redeclare them.
schemaVersion: 1
kind: container
mode: managed
cwd: /workspace
vars:
repo_source:
type: string
sources: [cli, web]
required: true
base_ref:
type: string
sources: [cli, web]
default: main
image: node:22If an assignment and the selected environment declare the same variable, the definitions must be identical. Different definitions are rejected instead of applying precedence.
implementation_run_id is specific to the implementation-path
code-review assignment, where the reviewer checks a completed
implementation run for plan coverage. Direct reviews use
code-review-direct and only need range.
{
type?: "string" | "number" | "boolean" | "enum" // default: "string"
required?: boolean // default: false
requiredAt?: "initial" | "prepare" // default: "initial"
sources?: ("cli" | "web" | "env" | "parent")[] // default: ["cli", "web"]
envName?: string // default: same as key
default?: unknown // must match type
description?: string
values?: string[] // required for enum
}For each variable declared by the assignment or the selected environment,
agent-runner walks the authored sources array from left to right:
clireads--var key=value.webreads API/UI-authored vars.envreadsenvName(or the var key whenenvNameis omitted).parentwalks the declaredparentRunIdchain and picks the nearest ancestor run that already froze that variable.
If every authored source fails, agent-runner then applies default, then
required, and otherwise omits the variable.
parent is a hot-cut source, not a fallback heuristic. Nested
agent-runner invocations launched from a worker automatically receive
AGENT_RUNNER_PARENT_RUN_ID, so descendant runs can inherit parent vars
without repeating --var flags.
Parent lineage is separate from run grouping. parentRunId is the chain
used by sources: [parent]; runGroupId controls group-scoped
attachments, group filters, and group dependencies. Nested invocations
also receive AGENT_RUNNER_RUN_GROUP_ID so children join the same run
group by default, but variable inheritance still follows parent lineage.
Prepare hooks run after the initial resolution pass and can add or mutate
runtime vars such as worktree_path and validated base refs such as
worktree_base_ref. Fresh-run interpolation is then recomputed against
the final runtime namespace so descendant assignments can author patterns
like:
cwd: "{{worktree_path}}"
vars:
worktree_path:
type: string
required: true
sources: [parent]
worktree_base_ref:
type: string
required: true
sources: [parent]string— pass-through.number— parsed viaNumber(value); rejectsNaN.boolean— accepts"true"/"1"as true,"false"/"0"as false; anything else errors.enum— must be one of the declaredvalues.
CLI values come in as strings and are coerced; env values are coerced the
same way. default must already match the declared type.
Before agent-runner validates agent or assignment frontmatter, it resolves shell-style env expressions in parsed scalar values:
${VAR}— useVAR; fail if it is unset or empty${VAR:-fallback}— usefallbackwhenVARis unset or empty${VAR-fallback}— usefallbackonly whenVARis unset
This happens during definition loading, before run creation and before schema validation. Resolved values are then validated and coerced by the existing schemas.
Field surfaces are split explicitly:
- Exact-match typed fields accept only a single env expression for the
whole value. This includes scalar config such as
schemaVersion,name,backend,model,effort,timeoutSec,unrestricted,cwd,maxRetries, task ids, lock entries, var metadata, individualbackendArgs.<backend>.extraArgs[]tokens, and backendConfig string leaves such as Codex transporturland absolute UDSpath. - String prose fields accept env interpolation only when the entire field
value is exactly one
${...}expression. This includes assignmentmessage,callerInstructions, task titles and bodies, var descriptions, and the markdown body text for agent and assignment instructions. Partial${...}inside a larger prose string is left literal. - Object and array containers do not accept blob replacement.
${...}cannot inject YAML or JSON intobackendConfig,backendArgs,vars,tasks, orlockedFields.
Failures are load-time config errors, not runtime interpolation misses.
The error includes the definition path, config path, env var name, and the
reason (missing, empty, invalid syntax, or field-surface mismatch).
agent-runner always provides these variables in addition to the declared ones:
| Key | Value |
|---|---|
run_id |
the run's short id |
run_group_id |
the selected run group id for the run |
cwd |
the resolved working directory |
config_dir |
the resolved agent-runner config root |
state_dir |
the resolved agent-runner state root |
assignment_name |
the frozen assignment name, when the run has an assignment |
agent_runner_cmd |
resolved CLI command for subcommand examples |
These cannot be overridden by --var. When a run has no assignment,
assignment_name is omitted, so {{assignment_name}} remains
uninterpolated under the normal undefined-value rule.
run_group_id is runner-injected; it is not stored in
manifest.runtimeVars unless an assignment separately declares a
different variable.
References use {{key}} with optional whitespace. The matching pattern is
/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g. Undefined or null values leave the
token unchanged.
This runtime {{key}} interpolation is separate from the config-time
${...} env interpolation above.
Runtime interpolation is applied to:
- Agent role instructions
- Assignment instructions
- Fresh
cwdvalues from--cwdor assignment frontmatter - Task titles and bodies
- Caller instructions
- Assignment hook
with,when, andpathvalues - Fresh resolved launcher
commandandargs[]values - Fresh resolved execution environment cwd, env, image, container names, mounts, workspace paths, and lifecycle step values
Execution environment lifecycle steps may retain only
{{container_name}}, {{container_id}}, and {{container_pid}} after
fresh resolution. agent-runner resolves those late-bound values after a
managed container has been started or reused and inspected.
Values are stringified with String(value) before substitution.
Resolved variables are frozen into manifest.runtimeVars, and their
provenance is frozen into manifest.runtimeVarSources.
The manifest persists the concrete resolved value, including inherited values, so descendants can keep resolving from frozen lineage state. CLI, daemon, and web read surfaces redact env-backed values at projection time:
"runtimeVars": {
"range": "main..HEAD",
"log_level": "info"
},
"runtimeVarSources": {
"range": { "source": "cli" },
"log_level": {
"source": "parent",
"envName": "LOG_LEVEL",
"redacted": true,
"inheritedFromRunId": "abc123"
}
}Projected RunDetail.runtimeVars still redacts env-derived or
inherited-env-derived values for humans.
Variables are resolved once at run creation and frozen. Resume rejects
--var flags — use dependencies, new tasks, or follow-up messages to pass
new information. See resume.md.
Initialized runs can still be edited with run reconfigure; that path
uses the frozen assignment and environment variable schemas and rerenders
the initialized run before it has executed.
agent-runner run \
--agent implementer \
--assignment code-review \
--var range=main..HEAD \
--var implementation_run_id=abc123For a direct review that is not tied to an implementation run:
agent-runner run \
--agent code-reviewer \
--assignment code-review-direct \
--var range=unstaged--var is repeatable. Values are split on the first =, so
--var message=key=value yields message=key=value.
Nested descendant runs usually should not repeat parent-owned vars
manually. Prefer assignment schemas that declare sources: [parent] or
sources: [parent, env] and let lineage resolution reuse the frozen
parent values.
agent-runner run status <run-id> --output-format jsonincludesruntimeVars(with env values redacted).show assignment <name>renders the declared var schema and defaults.