Activity and job YAML
Activity envelope
Section titled “Activity envelope”The examples illustrate schema shapes. example_action is a placeholder for
a registered action; assess_a and assess_b stand for catalog activities
you define. Replace those names before executing a job.
schemaVersion: 2kind: Activitymetadata: name: example_activityspec: type: deterministic description: Run a registered deterministic action. action: example_action input_schema_json: type: object properties: {} output_schema_json: type: object properties: status: type: stringActivity types
Section titled “Activity types”| Type | Required fields | Notes |
|---|---|---|
agent_loop |
instruction; optional tools or tool_disallow_list, provider, model, wall_clock_timeout_seconds |
Runs the provider’s CLI agent. Tool lists and retired fields are covered below. |
deterministic |
action; optional config |
Runs a registered deterministic action. |
Agent tool lists
Section titled “Agent tool lists”tools is the activity’s baseline allowlist. Declaring tool_disallow_list,
even as [], selects deny mode instead: every registered agent-facing tool
except the listed ones. tool_disallow_list cannot be combined with a
non-empty tools.
For task-backed dispatch, Orbit adds the task’s exact required_tools to the
baseline, removes duplicates, and rejects invalid requirements before the
provider launches. The resulting effective list is serialized as tools in
the CLI execution envelope and exported as ORBIT_ACTIVITY_TOOLS. The task’s
requested list is serialized separately as required_tools. It cannot change
after the task is created, and audit evidence records both lists.
In deny mode the effective list is every callable tool, and the run also
exports ORBIT_ACTIVITY_TOOL_POLICY=, ORBIT_ACTIVITY_TOOLS_DENY, and
ORBIT_ACTIVITY_NAME. Disallow entries follow the same name and wildcard
rules as tools. A task’s required_tools cannot re-grant a disallowed tool.
On agent_implement, and on any activity outside the shipped non-implementer
set, a covered requirement refuses the run before launch. final_recovery,
step_failure_recovery, pr_conflict_recovery, agent_review_repair,
review_reconciliation_review, task_pilot and agent_invoke drop that
requirement from the requested list and record a note; it stays out of the
callable set. The shipped agent_explore and verify_finding activities use
the same non-implementer rule.
An operator’s task grant is a narrow exception for agent_implement: it can
lift an exact deny entry for a read/repeat-safe tool, but a mutating grant
remains denied and goes through a reviewed proposal instead. Grants do not
override wildcard denials or the tool’s other authorization checks. See
Task tool grants for the change
window, placement and proposal workflow.
An agent_loop activity that declares neither list still loads, with a
deprecation warning. Declare tools or tool_disallow_list.
Being on the allowlist never replaces runtime role, capability, policy, filesystem, subprocess, or authentication checks.
Optional steering_instruction and explore_instruction fields add guidance
only when mid-run steering or read-only exploration is enabled and available.
See Agent execution
for the corresponding workspace settings.
Retired agent fields
Section titled “Retired agent fields”Agent execution uses the CLI path only. backend: still parses and is
ignored; backend: and backend: fail catalog load, and
orbit removes them. See
Retired backend selection.
max_iterations is accepted and has no effect: only
wall_clock_timeout_seconds bounds an invocation.
Job envelope
Section titled “Job envelope”schemaVersion: 2kind: Jobmetadata: name: example_jobspec: state: enabled max_active_runs: 1 kind: workflow steps: - id: run_action target: activity:deterministic_referenceStep bodies
Section titled “Step bodies”Every step has an id and one body.
Reference an activity:
- id: assess target: activity:agent_assess_diffInline a type-specific activity spec:
- id: run_action spec: type: deterministic action: example_action config: {}Run branches in parallel:
- id: parallel_assessment parallel: join: { mode: all } branches: - id: branch_a target: activity:assess_a - id: branch_b target: activity:assess_bparallel. and fan_in. use the same success requirements:
allsucceeds when every branch or worker succeeds, including an empty block.anyrequires at least one successful branch or worker, so an empty block fails.quorumrequires at leastnsuccesses.nmust be positive and cannot exceed a parallel block’s declared branch count. Fan-out item counts are resolved at runtime, so a positive quorum fails when too few workers succeed, including when there are no items.
An empty fan-out records [] under its step id and optional fan_in.
alias, and emits dispatch and join audit events with zero workers. A failed
join stops subsequent steps.
Fan-in keeps one output slot per item in dispatch order. Failed workers have
null output slots; successful siblings remain available when any or
quorum admits the join. Set fan_in. to publish a
separate array at steps. in the same order: successful
workers have null errors and failed workers have their dispatch error or
unsuccessful outcome’s message. A worker panic records its panic diagnostic.
An empty block publishes []. The errors array is retained in successful
compound checkpoints and restored on resume. Choose an errors key distinct
from the step’s id and collect alias.
The shipped finding verification job passes these errors into apply as
partition_errors. A failed partition stays unverified with verifier_failed
and its crew and diagnostic; a successful verifier that omits a task’s verdict
still reports missing_verdict. Native pinned-source inspection supports
Codex and Claude, so verifiers are drawn only from crews of those providers;
a tier without one passes to the next tier and then the default crew. A
verifier that still fails, for any reason, keeps its refusal alongside
successful partitions’ verdicts.
Fan-out and fan-in
Section titled “Fan-out and fan-in”Use fan-out when one step’s output is a list and the same work must run
once per item, with bounded concurrency. A fan-out step always pairs
fan_out with fan_in on the same step:
- id: verifiers when: "{{ steps.prepare.output.partition_count }} != 0" fan_out: items: "{{ steps.prepare.output.partitions }}" max_workers: 5 worker: id: verifier target: activity:verify_finding default_input: task_ids: "{{ item.task_ids }}" partition_index: "{{ item.partition_index }}" workspace_path: "{{ steps.prepare.output.workspace_path }}" fan_in: join: { mode: any } collect: verdict_results errors: verdict_errorsfan_out.is a template that must render to a JSON array; text that is not JSON is read as a comma- or whitespace-separated list of strings. At most 256 items may be dispatched.items fan_out.bounds how many workers run at once (default 4). It gates execution, not dispatch: every item gets a worker.max_workers fan_out.is a full step with its ownworker idand body (targetor inlinespec), and may carry its owndefault_input,whenandretry. Inside the worker,{{is the current item anditem }} {{its 0-based index. Workers see the pipeline as it was at dispatch; a worker’s own step outputs do not leak into siblings or back into the parent.iteration }} fan_in.decides the step’s success from the workers’ results (see the join modes above).join fan_in.additionally publishes the collected outputs under that pipeline key, readable downstream ascollect {{; the outputs are always recorded under the fan-out step’s ownsteps. verdict_results. output }} idtoo.fan_in.publishes the position-aligned error array described above.errors
Downstream steps then consume the collection like any other step output:
- id: apply target: activity:apply_finding_verification default_input: results: "{{ steps.verdict_results.output }}" partition_errors: "{{ steps.verdict_errors.output }}"When to choose fan-out over parallel: parallel names a fixed set of
different branches in the YAML; fan-out derives its branches at runtime
from data, so use it whenever the item list is not known until the job
runs.
Use a loop when a body must repeat — either once per item, like a serial
fan-out, or until a condition holds, like polling. max_iterations is
always required and bounds the body either way.
Once per item:
- id: mark_review loop: items: "{{ input.task_ids }}" max_iterations: 100 steps: - id: mark_review_one target: activity:update_task default_input: task_id: "{{ item }}" status: "{{ input.terminal_status }}"The rendered items list may not exceed max_iterations, so raise
max_iterations to the largest list you accept. Body steps run in order
and share the job’s pipeline state, so a later body step — and the
break_when check — can read earlier steps’ outputs. Inside the body,
{{ is the current item and {{ its 0-based index.
A body step that fails ends the loop and fails the step.
Until a condition holds:
- id: drain loop: max_iterations: 6000 break_when: "{{ steps.refill.output.done }} == true" steps: - id: window target: activity:drain_window - id: refill target: activity:pull_refillbreak_when uses the when grammar above and is checked after each full
pass through the body. Two optional bounds keep a polling loop finite in
wall-clock terms as well:
timeout_secondscaps the loop’s elapsed time. It is a template string that must render to a finite, non-negative number of seconds.0runs no iterations; an iteration already running is not interrupted, and the loop stops before the next one.interval_secondswaits between unsuccessful iterations (passes that did not break), capped by whatever timeout budget remains.
A loop records { under its
step id, where last_output is the final body step’s output from the
last pass. Reaching max_iterations without break_when ever holding is
not itself a failure — the step succeeds and reports broke: — so
check that field downstream when convergence matters. Retired session:
bindings on loop or target steps are refused at load; conversation state
does not carry across iterations.
Input and output wiring
Section titled “Input and output wiring”A step passes data to its activity with default_input. Every string in
that mapping is rendered as a template before dispatch; numbers, booleans,
arrays and nested objects pass through, with templates rendered anywhere
inside them.
- id: apply target: activity:apply_results default_input: prepared: "{{ steps.prepare.output }}" results: "{{ steps.collect_results.output }}" workspace_path: "{{ steps.prepare.output.workspace_path }}"The template namespaces are:
| Token | Resolves to |
|---|---|
{{ |
The run’s input (after the job’s default_input merge below). |
{{ |
A completed step’s recorded output. {{ alone forwards the whole output object. |
{{ |
A completed step’s recorded state, when the step recorded any. |
{{ / {{ |
The current fan-out or loop item. Only available inside a fan-out worker or a loop body. |
{{ |
The current 0-based fan-out worker or loop iteration index. |
{{ |
One variable from the run’s environment. A missing variable fails the step. |
{{ |
The run’s workspace path, when the run has one. |
A whole-string {{ reference forwards the value
with its JSON type intact, so a string output such as "618" stays a
string. A template embedded in longer text renders to text, and text that
parses as JSON (a number, true, an array) is converted back to that JSON
value. A token that names an unknown namespace, a missing key or a step
that has not recorded data fails the step instead of rendering empty.
When a step declares no default_input, the activity receives the run’s
input unchanged. The special whole-input form forwards the caller’s input
verbatim, without rendering anything inside it:
default_input: "{{ input }}"At the job level, spec. supplies defaults under the run’s
input: explicit run input wins key by key, and a run submitted with no
input uses the defaults as-is.
spec: default_input: base_branch: agent-main mode: standardModifiers
Section titled “Modifiers”Any step may add when and retry.
when: conditional steps
Section titled “when: conditional steps”when is a boolean expression evaluated once, before any retry. A step
whose when is false is skipped: it records success with a null output
and never retries.
- id: push when: "{{ input.auto_push }} == true" target: activity:git_pushThe grammar is deliberately small:
trueandfalseliterals.<value>and= = <value> <value>. Each side is rendered on its own first, then compared as trimmed text, so a rendered value can never inject another operator.!= <value> &&(binds tighter) and||combine comparisons.- A lone template is allowed when it renders to
trueorfalse.
Anything else — an unknown operator, an unrenderable token, a lone value
that is not true or false — fails the step at evaluation time. The
same grammar drives a loop’s break_when below.
retry: max_attempts: 3 initial_backoff_ms: 500 backoff_cap_ms: 5000 backoff_strategy: exponentialRetries re-run the step body with full-jitter backoff between attempts
(initial_backoff_ms defaults to 1000, backoff_cap_ms to 60000, and
backoff_strategy to exponential; linear is the other strategy).
Non-retryable failures — a denied tool, an unknown deterministic action,
a validation error — bypass retry entirely.
Timeouts
Section titled “Timeouts”Three different bounds apply at three different levels; set the one that matches what you are bounding:
| Field | Where | Bounds |
|---|---|---|
wall_clock_timeout_seconds |
On an agent_loop activity spec |
One CLI agent invocation (max_iterations is inert; see Retired agent fields). For reviewer runs, the host’s review. deadline replaces this bound; a trusted-host or explorer run input timeout_seconds can also shorten it. |
timeout_seconds / interval_seconds |
On a loop: block |
The loop’s total elapsed time and the pause between passes (see Loops). |
timeout_ms |
In a deterministic local_shell activity’s config |
One child process; see Deterministic local shell below. |
Agent steps are bounded by wall_clock_timeout_seconds on the referenced
activity spec, not by step-level properties. The executor does not read
timeout_seconds on a target or inline spec step as a dispatch deadline:
- id: implement_one target: activity:agent_implement default_input: task_id: "{{ item }}"Recovery hooks
Section titled “Recovery hooks”Retries handle transient failure of the same step. Recovery hooks bring in a different activity when a step has definitively failed. They are declared by activity name and resolve against the catalog at load:
| Field | Where | Behavior |
|---|---|---|
recovery_activity |
On a step, or on the job’s spec |
Runs once after the step’s retries are exhausted (a step-level declaration overrides the job-level one for that step). It gets one attempt to repair the situation; the failed step’s outcome stands if recovery does not succeed. |
final_recovery_activity |
On the job’s spec |
The last automated look at a failed run: invoked at most once per run, after step recovery is exhausted and before failure_activity. It returns one typed decision: resume (reruns the run from an earlier step), a settled decision (complete_no_diff, reject, archive, or requeue, which does not run failure_activity), or escalate (which, like a missing or malformed result, still runs failure_activity). |
failure_activity |
On the job’s spec |
A best-effort terminal hook invoked once after a step failure. Unlike recovery_activity, it does not retry or replace the failed step — use it for notification and cleanup, not repair. |
spec: failure_activity: notify_failure steps: - id: validate recovery_activity: step_failure_recovery target: activity:candidate_validateRecovery is not a way to make a failing step pass silently: the original failure, every retry and each recovery attempt are recorded in the run’s audit trail.
Agent process policy
Section titled “Agent process policy”An agent_loop activity whose tools include proc. must declare
which programs the spawned processes may run. The policy is enforced
before any process starts:
spec: type: agent_loop instruction: "Run the checks and report." tools: - proc.spawn proc_allowed_programs: - cargo - gitproc_allowed_programsis an allowlist. An empty list denies every program — declaring[]is fail-closed, not permissive.proc_disallowed_programsinstead selects deny mode: every program except the listed ones may run.- Catalog load rejects an activity that grants
proc.without either policy, and a dispatch that somehow reaches execution without one is treated as deny-all.spawn
Being allowed to spawn a program never replaces the tool allowlist, filesystem profile or sandbox checks that also apply to the run.
Completion contracts
Section titled “Completion contracts”Two activity-level flags say what a finished CLI invocation must prove:
require_completion_envelope(defaulttrue) requires the invocation to terminate with a well-formed Orbit response envelope. An agent that exits 0 without one stopped mid-turn, so the step fails rather than checkpointing work that never happened. The check reads only the envelope frame — an agent that reportsstatus:inside a well-formed envelope still satisfies it. Set it to"failed" falseonly for a genuinely decorative activity, and record why in the asset.require_response_envelope(defaultfalse) additionally requires a fully valid success envelope before the step may report success. Use it for activities whose result fields feed downstream templates; it implies the completion check above.
on_denial controls what happens when the agent requests a tool its
policy denies: terminate (the default) ends the invocation, while
continue returns the denial to the agent as a structured tool error
and lets it proceed.
Deterministic local shell
Section titled “Deterministic local shell”The local_shell deterministic action runs one local command as a job
step, through the same process supervision as an agent CLI run. It is
not an agent: it receives no prompt, model or tool allowlist. Use it for
short, deterministic commands (git , a build check) whose output
a later step consumes.
schemaVersion: 2kind: Activitymetadata: name: git_statusspec: type: deterministic description: Report the checkout's porcelain status. action: local_shell config: command: git args: ["status", "--porcelain"] cwd: "." env: GIT_OPTIONAL_LOCKS: "0" timeout_ms: 60000 allow_nonzero_exit: falseThe safety contract is the point of the action:
-
The program comes from static declarations, never step input. The activity’s
configmay declarecommand+argsorshell+script, or neither: when the activity names no program, the selected executor definition’scommandis the fallback program and itsargsare the argv. For an explicitcommand, the definition’s staticargsprefix the activity’sargs;shell+scriptruns exactly<shell>. A step’s templated input can choose the checkout (-c <script> workspace_pathin the step input, defaulting to the registered repository root) but can never reach argv, so agent- produced text cannot become a command. -
Argv and shell are different declarations.
command+argsis a direct exec with no shell anywhere — nothing is word-split, globbed or expanded. To run a script, name the interpreter and the script explicitly; Orbit invokes<shell>and never joins-c <script> argsinto a command line. Setting bothcommandandshell, orargsalongsideshell, is rejected.config:shell: /bin/shscript: |set -eumake ci-fast -
cwdresolves relative to the workspace root and must stay inside it; acwdthat escapes the checkout fails the step. -
enventries layer over the executor definition’senv, which layers over the same credential-free environment validation commands run with — nothing ambient is inherited. -
timeout_msdefaults to 600000 (10 minutes) and must be between 1 and 3600000 (1 hour); the executor definition’stimeout_secondsis the fallback when the activity sets neither. A step that legitimately needs longer belongs in its own job. -
With
allow_nonzero_exit:(the default), a nonzero exit or a timeout fails the step with the program, working directory and status in the message. Set it tofalse truewhen a later step should branch on the exit code instead.
The step’s output is { . The shipped local-shell
executor definition declares no OS sandbox, so steps run under the
process supervisor confined by the cwd check above, and each step
reports the backend it actually ran under in sandbox ("none" for the
shipped definition). Add a sandbox to the executor definition and an
fsProfile to the activity to confine shell steps at the kernel level.