xcb --json route lets another program, usually a coding agent, hand xcb one
task. xcb picks an account and model that can take it, runs one provider turn
in the project folder you name, and prints one JSON result. The caller can
narrow the choice; checking the provider build, holding the account, keeping
the provider inside the folder, and confirming the provider stopped all stay
with xcb.
Three commands run one task at a time:
xcb runis for people: flags on the command line, a saved direct session, and your configured continuation and failover.xcb --json routeis for programs: one JSON request on stdin, account and model chosen per request, exactly one provider turn, no continuation.xcb --json generateis for applications: one model response with no tools, folder, or session. See the application API.
From Claude Code or Codex, use the shell tool to call the native xcb CLI.
First install and set up xcb, sign in an account, and
check xcb --json doctor. Run a dry run before handing over file changes:
xcb --json route <<'JSON'
{
"version": 1,
"workspace": "/absolute/path/to/project",
"task": "Fix the failing parser test and show the diff",
"dryRun": true
}
JSONReplace the workspace with the existing project folder. A successful preview
prints status: "selected" and the chosen provider, account, and model; it
starts no provider and changes no project files. If it fails with
code: "unavailable", check account sign-in and the supported provider build in
troubleshooting before retrying.
After reviewing the choice and your agent's shell permission prompt, repeat
the request with dryRun: false to run one turn. The provider then changes
files only inside the named workspace, through xcb's file tools. Read the
response and review the diff; status: "completed" confirms the turn
finished, not that tests passed or the patch is correct. A preview does not
reserve the account, so a later run can select a different eligible route
unless you pin it with the request fields below.
Write one UTF-8 JSON document to stdin, close stdin, and read the result from
stdout. The request is limited to 1 MiB. Unknown fields are rejected; every
field except version, workspace, and task is optional.
{
"version": 1,
"workspace": "/absolute/path/to/project",
"task": "Fix the failing parser test and show the diff",
"provider": "claude",
"account": "a_…",
"model": "claude/sonnet/low",
"timeoutMs": 1800000,
"dryRun": false
}versionis1. Pin it: a changed request format ships under a new version.workspacemust be an existing folder. The provider's file tools stay inside it.taskis 1 byte to 256 KiB of text without NUL.providerisclaudeorcodex, and requires that provider.accountnames one account by ID or exact name. Aproviderthat doesn't match the account's provider is aninvalid_request.modelis a full key as printed byxcb models, and limits the route to that model.timeoutMsis 1,000 to 3,600,000. When it expires, xcb cancels the turn and answers only after the provider has stopped; the code isdeadline.dryRun: truereports the route without creating a session, holding an account, or starting a provider.requirements: {"signed_in_browser": true}requires Codex for an existing signed-in browser;requirements: {"desktop": true}requires Codex for native desktop application control. Both can be set together. Requirements persist with the saved session, and a conflicting provider, account, or model pin is rejected. See browser and shared tools for setup and handoff behavior.requirements: {"native_execution": true}requires native workspace commands through the selected provider's tool protocol. It does not pin Codex and may be combined with browser or desktop requirements. On macOS, runxcb native qualify, verify each selected provider withxcb native verify --provider <provider>, and grant the workspace withxcb --cwd /absolute/path/to/project native grant --provider codexfor a Codex-only grant; repeat--providerto select both supported providers. Use--githubon both verification and granting if commands need host GitHub credentials. Add--read-only-rootfor toolchains outside system directories and--git-metadatafor a worktree's external Git directories, or--host-readto let commands read and run anything on the host, like a provider's own workspace-write mode. A host-read grant still hides xcb's private state, provider and GitHub credentials,~/.config, shell history and the browser, mail, message and application-support stores under~/Library; writes stay confined to the workspace and its Git metadata. Its commands also search~/.cargo/bin,~/.bun/bin, the newest nvm Node, Homebrew and/usr/local/bin, and rustup resolves the host's installed toolchains. A matching workspace grant also requests native execution for new direct routes and newly submitted tasks. Missing or stale tests and grants returnunavailable, including for a dry run; native tasks never substitute the offline VM. Linux and Windows remain unavailable. This uses xcb's native command tool, not provider-built-in host shell or web tools. See the native execution direction.
Pins limit the choice; xcb never falls back outside them. With no pins, xcb
considers accounts with a supported provider build that are signed in,
enabled, idle, and not at a known usage limit, with a model recently seen in
the provider's catalog. It orders those models by your
preference stack, then by task type,
relative quality, cost, and latency, remaining usage, and your configured
favorites. An optional judge can require browser or desktop capabilities and
rank eligible routes; it preserves your pins and the provider checks above.
A pinned model that the stack's never list excludes fails with
unavailable. See quota routing for the rules.
Top-level fields are camelCase; the fields inside outcome are snake_case.
A chosen route reports provider, account, the full model key, a display
label, and a short reason for a person to read: how xcb classified the
task, the capability tier (standard or frontier), the model's relative
quality, cost, and speed, and the preference-stack tier and pattern position
that decided (tier default · stack #1). The route object has no other
fields; the stack tier is reported only inside reason. It explains the
choice and is not a price or quality guarantee. A public pricing promotion is named in the reason but never changes
which route wins. A dry run returns:
{
"version": 1,
"status": "selected",
"requestId": "route_…",
"route": {
"provider": "claude",
"account": "a_…",
"model": "claude/sonnet/low",
"label": "Sonnet · low",
"reason": "deterministic fallback · classifier not available · standard tier · balanced task · Pareto P1 · quality 92 · relative cost 55 · relative latency 50"
}
}A run returns status: "completed" only when the turn completed, the provider
process has exited, xcb has recorded its effects, nothing is waiting for an
answer, and the turn produced answer text or file changes:
{
"version": 1,
"status": "completed",
"requestId": "route_…",
"session": "s_…",
"route": { "provider": "claude", "account": "a_…", "model": "claude/sonnet/low", "label": "Sonnet · low", "reason": "…" },
"state": "idle",
"outcome": {
"terminal": "completed",
"joined": true,
"effects": "settled",
"pending_attention": false,
"failure": null
},
"text": "…"
}In outcome, joined: true means the provider's processes have exited, and
effects is none, settled (changes recorded), or uncertain. session
identifies the durable record for later inspection through the JSON task and
conversation projections; the removed interactive resume command is not part of
the agent contract. text holds up to 256 KiB, and textTruncated: true marks
a longer answer.
A failure exits 1 and prints one object:
{
"version": 1,
"status": "failed",
"requestId": "route_…",
"code": "unavailable",
"joined": true,
"effects": "none"
}| Code | Meaning |
|---|---|
invalid_request |
The request is malformed, the folder doesn't exist, or stdin is a terminal. |
unavailable |
No account can take the task: none qualify, the account or model is unknown, the provider build isn't supported, or credentials are missing. |
busy |
The account is running another task. |
deadline |
The caller's timeoutMs expired and the turn was cancelled. |
cancelled |
SIGINT or SIGTERM cancelled the turn. |
provider_error |
The turn failed, hit a provider limit, or ended without a reply or file changes; outcome.terminal and outcome.failure carry the detail, such as account_quota, model_quota, or no_reply, and a person can reopen session. |
custody_unproven |
xcb couldn't confirm that the provider stopped or what it changed, so it keeps the account held. Don't retry blindly; see xcb recover. |
needs_input |
The provider stopped with a question; text carries it, and a person can reopen session. |
joined: true and effects: "none" appear only when the request provably
started no provider process. Once a session exists, those facts come from the
recorded outcome instead. SIGINT and SIGTERM cancel the turn the same way
timeoutMs does; killing xcb doesn't prove the provider stopped.
- One call is one turn. Multi-step plans, retries, and route exclusion are the caller's loop; an account that failed on a usage limit is skipped on the next call because xcb recorded the limit.
- A request never carries tools, hooks, system prompts, credentials, or provider flags. The provider gets the same file tools as any direct session.
- Read accounts, models, usage limits, and provider status with
xcb --json accounts,xcb --json models, andxcb --json doctor.xcb --json models route --task …previews the route the thread would pick.