Agent Runtime
Ad-hoc LLM agent runs via POST /sandbox/agent/v1/runs.
Start a run
export SANDBOX_GATEWAY_URL="${SANDBOX_GATEWAY_URL:-http://localhost:8780}"
curl -sS -H "X-Api-Key: dev-local-key" \
-H "x-correlation-id: agent-run-42" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "opencode",
"instruction": "Propose a plan to fix the failing test",
"model": "anthropic/claude-sonnet-4-5",
"workspace": {"files": {"notes.txt": "context"}},
"agent_options": {
"opencode_agent": "plan",
"agent_env": {"POSTMAN": "1"}
}
}' \
"$SANDBOX_GATEWAY_URL/sandbox/agent/v1/runs"Returns 202 with job_id when queued via Pub/Sub worker. The correlation id propagates through the worker for logs and Langfuse — see Observability.
A durable status record is written before the 202 returns, so you can poll GET .../jobs/{job_id} immediately without a "not found" window.
Choosing an agent
GET /sandbox/agent/v1/agents lists what you may pass as agent_id, and two
fields on each entry are preconditions your request has to satisfy:
| field | if true |
|---|---|
requires_model | you must send model, as provider/name, or the request is refused with 400 |
requires_reference_solution | the agent applies the task's own solution/solve.sh rather than solving your instruction, so it needs a task that ships one |
model must name its provider: anthropic/claude-sonnet-4-5, not
claude-sonnet-4-5. Most of the agent CLIs reject a bare name, and the provider is
also what decides which of your keys gets leased for the run. A model without one is
refused at enqueue rather than failing about eighty seconds later, once a pod has
been scheduled and the CLI installed.
The second one rules out an entire class of request. oracle scores 1.0 on any
correct task, which is exactly what makes it the instrument for validating a
task — and exactly why it cannot answer a free-text instruction. This surface
synthesizes a task from your instruction, and no reference solution exists for a
sentence, so such requests are refused at enqueue rather than failing several
minutes later inside the run. Pass workspace.archive_url pointing at a task tree
that contains solution/solve.sh if validating a task is what you want, or pick
an agent that solves the instruction itself.
What a finished run tells you
A terminal agent run carries a completion block:
{
"status": "completed",
"trials": {"ran": 1, "errored": 0, "completed": 1},
"exceptions": [],
"reasons": []
}completion.status is completed, errored, or no_trials, and a job whose work
never finished reports status: "failed" — the Harbor CLI exits 0 even when a trial
raises, so a run that failed was previously reported as succeeded.
There is deliberately no scoring verdict here. Nothing grades an ad-hoc run:
there is only the instruction you sent, and no hidden test suite to be measured
against. If you need a graded result, use a
Harbor datapoint, whose response carries outcome
with valid_for_scoring and the verifier evidence behind it.
You may see a reward of 0.0 in the raw logs — ignore it
If you dig into a run's artifacts you will find a reward.txt containing 0.0.
That is not a failed grade. Agentic runs currently execute through Harbor,
which refuses to start a task that has no verifier, so the platform writes a
placeholder one. It scores nothing, and 0.0 was chosen over 1.0 precisely so
an ungraded run cannot be mistaken for a pass. Judge the run by its diff and
artifacts.
Poll status and cancel
Agent runs and Harbor datapoint jobs share one execution engine and one job-status contract, so the Harbor job routes serve both. Poll and cancel an agent run by its job_id:
# Poll status (returns status + timings + created_at/updated_at)
curl -sS -H "X-Api-Key: dev-local-key" \
"$SANDBOX_GATEWAY_URL/sandbox/harbor/v1/jobs/$JOB_ID"
# Cancel a running job (stops the agent and its model spend)
curl -sS -X POST -H "X-Api-Key: dev-local-key" \
"$SANDBOX_GATEWAY_URL/sandbox/harbor/v1/jobs/$JOB_ID/cancel"Cancel is honored mid-run: the worker watches for the cancel flag and SIGTERMs the running trial so it tears down its sandbox, rather than letting it run to completion.
Timings (AHT)
Every terminal job (succeeded, failed, or cancelled) carries a timings block in epoch seconds plus a millisecond breakdown:
| field | meaning |
|---|---|
enqueued_at / started_at / finished_at | absolute Unix epoch stamps (float seconds) |
queue_wait_ms | enqueue → worker pickup |
run_ms | worker pickup → terminal |
total_ms | enqueue → terminal (queue_wait_ms + run_ms) |
On version numbers. Each surface is versioned independently.
/sandbox/agent/v1/runsis this API's first version and is current — not a legacy path. The/v2you see on benchmark routes belongs to those routes alone; there is no/v2for agent runs.
Upstream services derive average handling time by averaging total_ms (or its parts) across jobs. Because the block is present on all terminal states, AHT reflects a realistic outcome mix, not just successes. Synchronous POST /run/datapoint also returns a Server-Timing: total;dur=… header for the same measurement inline.
Use "opencode_agent": "build" for full tools. This sets OpenCode default_agent in opencode.json — not a Harbor --mode flag. Applies only when agent_id is opencode.
To define custom OpenCode agents (e.g. a read-only review subagent), add agent_options.opencode_config. See Custom agents (opencode_config).
Caching and continuing work
OpenAI/Codex and Claude Code manage eligible prompt-prefix caching in their own provider/CLI loops. Sandbox forwards documented agent kwargs but does not keep an LLM KV cache. See Caching and reuse before passing provider-specific fields.
For an interactive workflow that needs the same filesystem over many commands, use a long-lived sandbox. Benchmark pass@k attempts must remain independent and must not share that session.
Renamed from harbor_extensions
This field was called harbor_extensions. Which executor runs a given agent is a
catalog decision (integration in infra/catalog/agents/), so the agentic API no
longer names Harbor in its own contract. harbor_extensions is still accepted as a
deprecated alias — existing clients need no change — but agent_options is the
name to use. The Harbor datapoint surface keeps harbor_extensions, where the name
is accurate.
Workspace
Optional workspace object supports inline files, archive_url, and Harbor-native mcp_servers for OpenCode/MCP tooling.
Strict catalog
Only Tier 1 catalog agent_id values are accepted. Use GET /sandbox/v1/catalog?kind=agent or GET /sandbox/agent/v1/agents to discover IDs.
API reference: Agent Runtime.