Headless runs in CI
omnicoreagent run runs one instruction and exits. Nobody is there to approve
a call or top up a budget, so you say up front how those are answered; the
exit code names how the run ended, and the evidence is written to files. By
the end of this page you will have an agent that opens a pull request from a
CI job, the run’s record read back from its files, and an outcome recorded
once the tests have passed.
It needs only the core install: no server.
Every output on this page is what the command printed when it was run. The
model’s wording, the ids and the token counts will differ on your run.
An agent file
The command loads a Python file that defines anagent variable or a
create_agent() function, the same file omniserve run takes. This agent
lists changes and may open a pull request, and its policy says a person
approves pull requests:
pip install "omnicoreagent[postgres]", which brings SQLAlchemy). With the
default in-memory store the record is gone when the command ends.
Run it
--approval-mode stop) the command leaves the run waiting and
exits 3. out/result.json says what it is waiting for:
resolve_approval, then resume: Approvals).
In CI, decide instead. An approvals file approves pull requests and denies
anything else the policy asks about:
usage is the whole run’s, both segments counted (three model calls: two
before the pause, one after), the same totals trajectory.json holds.
How it works
1
Load the agent, run the instruction
The run is tagged
headless, approval-mode:<mode> and
budget-mode:<mode>, plus your --tags, and recorded as entered from the
headless surface. MCP servers the agent names are connected first.2
Answer each pause by the mode you chose
When the run pauses for approvals, each one is decided by
--approval-mode through the same resolve_approval a person would use,
with the approver omnicoreagent-cli and a note naming the mode or rule,
and the run is resumed. This repeats up to --max-approval-rounds (20)
times. When a budget runs out, --budget-mode deny denies the top-up
through deny_budget; the CLI never grants money.3
One deadline for all of it
--timeout covers the whole run, pauses and resumes included. When it
passes, the run is stopped and recorded as timeout, not cancelled.4
Write the evidence, exit with the state
result.json and trajectory.json go to --output-dir; the answer (or
with --json, the whole result.json) to stdout; one status line to
stderr. Reading the evidence never changes the outcome: if the trajectory
cannot be read, evidence_error says why.Approval modes
A rule matches on
tool_name, capability, or both, and has a decision
(approve or deny) and an optional note. The file’s default is
approve, deny (when omitted) or stop.
Exit codes
What it writes
The traces themselves are also in the agent’s telemetry store
(
./workspace/telemetry/ by default), and the run’s record in its memory
store.
Read the run afterwards
trajectory.json needs no agent to read:
status is used: its decision was applied when the run
resumed. decision says which decision it was, approve or deny; a denied
approval is used too once the run resumes, and its call was not made.
--provenance
keys the trace knows (environment, trial_id, case_id, evaluation_id,
deployment_id, application_version, adapter, …) are set directly; any
other key, like build, goes under external_ids. What every field means is
in Read a run.
Record the outcome after the tests
The run said it opened a pull request; whether the change was any good is known when the tests run. A later step records it on the run, with no model key needed:In a CI job
A GitHub Actions job: run the agent, keep its evidence whatever happened, run the tests, record the outcome. The steps share one machine, so the SQLite file and the workspace carry from step to step; across jobs or machines, pointDATABASE_URL at a shared database and keep the workspace with it.
--instruction-file reads the instruction from a file (or - for stdin),
which keeps a long one out of the workflow. With --budget-mode deny, a run
that runs out of budget ends (exit 1) instead of waiting (exit 4) for a
top-up nobody will give.
Options
The command line reference is generated from the code.
When things go wrong
Exit 5: run exceeded its deadline
Exit 5: run exceeded its deadline
--timeout 2 against a model call that takes several seconds. The run
is recorded as timeout in its trace and its record, and result.json
has "error": "run exceeded its deadline". Give it the time a real run
needs. The deadline starts once the agent is set up (its tools, MCP
servers and model client loaded), so a cold start does not spend it.Exit 1: the budget was denied
Exit 1: the budget was denied
With
--budget-mode deny and a request budget of one model call:termination_reason is budget_exhausted, and cli_decisions holds the
denial. Raise the budget (Budgets).Exit 1: LLM_API_KEY not found
Exit 1: LLM_API_KEY not found
Exit 2: the command did not start
Exit 2: the command did not start
Nothing ran; the message says why:An agent file that raises on import, or defines neither
agent nor
create_agent(), is exit 2 as well.LookupError: No run … when recording the outcome
LookupError: No run … when recording the outcome
The later step cannot find the run: the agent keeps its records in
memory (the default), or the step points at a different store. Give the
agent a durable store, as
agent.py does, and run both steps against it.Next
Read a run
Every field of the trajectory the command writes.
Outcomes and training records
What the outcome is for, and how to export runs.
Observability
Traces, stores and exporters.
Harbor and Terminal-Bench
The same headless run, on benchmark tasks.