Adapters
Adapters are the bridge between the orchestrator and the agent that does the work. Every adapter implements the same contract: receive a dispatch bundle, produce a staged turn result. The orchestrator does not care whether the work is done by a human, a local subprocess, an MCP stdio server, a remote API, or an external agent service — it only cares that the result conforms to the protocol.
Before you wire a runtime, read the adjacent operator surfaces:
- Quickstart for the governed bootstrap path
- CLI Reference for the operator command surface
- Protocol Reference for the turn-result contract and validator rules
- Build Your Own Connector for the implementation checklist that turns this page into a custom runtime
For the canonical reference of all five runtimes, three authority levels, and which combinations are valid, see the Runtime Matrix.
Package export
If you are building a non-CLI runner and want to reuse the shipped adapters, import them through the public package boundary:
import {
ADAPTER_INTERFACE_VERSION,
dispatchLocalCli,
dispatchApiProxy,
dispatchMcp,
dispatchRemoteAgent,
printManualDispatchInstructions,
waitForStagedResult,
readStagedResult,
} from 'agentxchain/adapter-interface';
Do not import deep source paths like cli/src/lib/adapters/local-cli-adapter.js. That is implementation detail, not contract.
For a runnable example that uses this boundary with dispatchLocalCli, see examples/external-runner-starter/run-adapter-turn.mjs.
Shared adapter contract
Every adapter follows a three-phase lifecycle:
1. Dispatch
The orchestrator writes a dispatch bundle to .agentxchain/dispatch/turns/<turn_id>/. The bundle contains everything the agent needs to do its work.
2. Wait
The orchestrator waits for the adapter to signal completion. How it waits depends on the adapter type (polling, subprocess exit, HTTP response).
3. Collect
The orchestrator reads the staged result from .agentxchain/staging/<turn_id>/turn-result.json, validates it against the protocol schema, and either accepts it into the pipeline or marks it as failed.
┌─────────────┐ dispatch bundle ┌──────────────┐
│ Orchestrator │ ──────────────────────► │ Adapter │
│ │ │ │
│ │ ◄────────────────────── │ │
│ │ staged result │ │
└─────────────┘ └──────────────┘
Filesystem contract
Dispatch bundle
The orchestrator writes these files to .agentxchain/dispatch/turns/<turn_id>/:
| File | Purpose |
|---|---|
ASSIGNMENT.json | Structured assignment metadata: run ID, turn ID, role, phase, runtime ID, write authority, staging path, budget reservation |
MANIFEST.json | Integrity manifest listing all dispatch files with SHA-256 checksums, written after after_dispatch hooks complete |
PROMPT.md | The role prompt from .agentxchain/prompts/<role>.md with variable interpolation, protocol rules, and role-scoped workflow-kit responsibilities for the current phase when applicable |
CONTEXT.md | Assembled context from previous turns: accepted decisions, open objections, files changed, verification evidence, and the phase-wide Workflow Artifacts table when workflow-kit is present |
{
"run_id": "run_a1b2c3",
"turn_id": "turn_x7y8z9",
"role": "dev",
"phase": "implementation",
"runtime_id": "local-dev",
"write_authority": "authoritative",
"staging_result_path": ".agentxchain/staging/turn_x7y8z9/turn-result.json",
"reserved_paths": [],
"allowed_next_roles": ["dev", "qa", "eng_director", "human"],
"attempt": 1,
"deadline_at": null,
"assigned_sequence": 3,
"budget_reservation_usd": 2
}
Staged result schema
The adapter must write a valid turn result to .agentxchain/staging/<turn_id>/turn-result.json. The schema is consistent across all adapters:
{
"schema_version": "1.0",
"run_id": "run_...",
"turn_id": "turn_...",
"role": "dev",
"runtime_id": "local_cli-claude",
"status": "completed",
"summary": "Implemented the authentication module...",
"decisions": [
{
"id": "DEC-005",
"category": "implementation",
"statement": "Use bcrypt for password hashing",
"rationale": "Industry standard, constant-time comparison"
}
],
"objections": [
{
"id": "OBJ-003",
"severity": "low",
"against_turn_id": "turn_prev",
"statement": "PM spec did not address rate limiting",
"status": "raised"
}
],
"files_changed": [
{ "path": "src/auth.ts", "action": "created" },
{ "path": "src/auth.test.ts", "action": "created" }
],
"verification": {
"status": "passed",
"commands": ["npm test", "npm run lint"],
"evidence_summary": "All 14 tests pass. No lint errors.",
"machine_evidence": [
{ "command": "npm test", "exit_code": 0, "stdout_tail": "14 passing (1.2s)" }
]
},
"artifact": {
"type": "code",
"ref": "src/auth.ts"
},
"proposed_next_role": "qa",
"phase_transition_request": null,
"run_completion_request": null
}
Required fields: All fields shown above are required. The objections array is required and must be a valid array. For review_only roles, the protocol enforces a challenge requirement: at least one objection must be raised. Other roles may submit an empty objections array.
Custom phases are operator-defined, not scaffold-generated. If you extend routing with phases like design or security_review, you must also define their gate files in gates, and phase_transition_request may only target the immediate next declared phase. If you want those phases to have scaffolded docs or structural validation, declare workflow_kit explicitly in agentxchain.json; otherwise custom phases rely on requires_files alone and do not inherit the built-in planning / implementation / qa workflow artifacts.
When workflow_kit is present, the dispatch bundle splits visibility from accountability on purpose:
CONTEXT.mdrenders the fullWorkflow Artifactstable for the current phase so the agent can see the whole contract.PROMPT.mdrenders only the workflow-kit responsibilities assigned to the current role.- Ownership resolves from
owned_byfirst. Ifowned_byis absent, accountability falls back to the current phaseentry_role. review_onlyownership means attestation, not file authorship. Those roles see "reviewing and attesting" guidance inPROMPT.md, must confirm the artifact exists and is acceptable, and must escalate if a required artifact is missing because they cannot write repo files directly.
manual adapter
The manual adapter is designed for human-in-the-loop workflows where a person reads the dispatch, does the work, and stages the result themselves.
How it works
- The orchestrator writes the dispatch bundle and prints instructions to the terminal.
- The adapter polls
.agentxchain/staging/<turn_id>/turn-result.jsonevery 2 seconds. - When the file appears and is valid JSON, the adapter collects it.
- Default timeout: 20 minutes (1,200,000 ms). Configurable via
--timeout.
Configuration
{
"roles": {
"pm": {
"title": "Product Manager",
"mandate": "Protect user value, scope clarity, and acceptance criteria.",
"write_authority": "review_only",
"runtime": "manual-pm"
}
},
"runtimes": {
"manual-pm": {
"type": "manual"
}
}
}
The manual adapter's poll interval (2 seconds) and timeout (20 minutes) are built-in defaults. They are not set in agentxchain.json — configure timeout via --timeout on agentxchain step.
When to use
- Planning turns where a human PM writes the spec
- Code review turns where a senior engineer reviews the work
- Any turn where you want full human control over the output
local_cli adapter
The local_cli adapter spawns a local subprocess and feeds it the dispatch bundle. The default scaffold targets Claude Code with claude --print --dangerously-skip-permissions --bare and stdin prompt transport. Those extra flags are not cosmetic: unattended governed implementation turns need file-write access, and plain claude --print can stop at a permission prompt or macOS keychain auth read without ever staging a result. Any local CLI tool that can accept a prompt and produce a valid turn result works — configure it at scaffold time with --dev-command and --dev-prompt-transport instead of hand-editing JSON afterward.
How it works
- The orchestrator writes the dispatch bundle.
- The adapter spawns the configured command as a child process.
- The prompt is delivered via one of three prompt transport modes.
- The adapter waits for the subprocess to exit.
- On exit, if a valid staged result file exists, the adapter collects it (regardless of exit code).
- On timeout, the adapter sends
SIGTERM, waits 10 seconds, thenSIGKILL.
Prompt transport modes
| Mode | How the prompt reaches the agent | Best for |
|---|---|---|
argv | {prompt} placeholders in command/args are replaced with the full prompt text | Simple tools that accept prompt as a CLI argument |
stdin | Prompt is piped to the process's standard input | Claude Code (--print mode), streaming tools |
dispatch_bundle_only | No prompt delivery; the subprocess reads PROMPT.md and CONTEXT.md from disk | Agents that manage their own file I/O |
If prompt_transport is not set explicitly, the adapter infers the mode: if {prompt} appears in the command or args, it uses argv; otherwise it defaults to dispatch_bundle_only.
Configuration
{
"roles": {
"dev": {
"title": "Developer",
"mandate": "Implement approved work safely and verify behavior.",
"write_authority": "authoritative",
"runtime": "local-dev"
}
},
"runtimes": {
"local-dev": {
"type": "local_cli",
"command": ["claude", "--print", "--dangerously-skip-permissions", "--bare"],
"cwd": ".",
"prompt_transport": "stdin"
}
}
}
Timeout and signal handling
| Event | Action |
|---|---|
| Subprocess exits with staged result present | Collect staged result (success) |
| Subprocess exits without staged result | Mark turn as failed with exit code |
| Timeout reached | Send SIGTERM to process |
10 seconds after SIGTERM | Send SIGKILL to process |
SIGKILL sent without staged result | Mark turn as failed with reason timeout |
Default timeout when no turn deadline is set: 20 minutes (1,200,000 ms).
Non-default local CLI examples
The scaffold ships with Claude Code as the default, but --dev-command and --dev-prompt-transport let you target any local tool at scaffold time.
These examples omit --goal and doctor for brevity. For the full governed bootstrap path, see Getting Started.
Tool that accepts prompt as a CLI argument:
agentxchain init --governed --dev-command my-agent run {prompt} -y
This scaffolds prompt_transport: "argv" and replaces {prompt} with the full prompt text at dispatch time. The generated runtime:
{
"type": "local_cli",
"command": ["my-agent run {prompt}"],
"cwd": ".",
"prompt_transport": "argv"
}
Tool that reads dispatch files directly (no prompt piping):
agentxchain init --governed --dev-command ./scripts/dev-agent.sh --dev-prompt-transport dispatch_bundle_only -y
The agent script receives no prompt on stdin or argv — it reads PROMPT.md and CONTEXT.md from the dispatch directory itself. The generated runtime:
{
"type": "local_cli",
"command": ["./scripts/dev-agent.sh"],
"cwd": ".",
"prompt_transport": "dispatch_bundle_only"
}
Tool that reads prompt from stdin (like the Claude default):
agentxchain init --governed --dev-command "claude --print --dangerously-skip-permissions --bare" --dev-prompt-transport stdin -y
{
"type": "local_cli",
"command": ["claude --print --dangerously-skip-permissions --bare"],
"cwd": ".",
"prompt_transport": "stdin"
}
If you supply --dev-command without {prompt} and without --dev-prompt-transport, init rejects the scaffold. This is intentional — ambiguous prompt delivery is worse than a clear error.
When to use
- Dev turns with Claude Code (verified default) or any local coding CLI configured via
--dev-command - QA turns with automated test runners
- Any turn where a local tool can accept a prompt and produce a valid turn result
mcp adapter
The mcp adapter connects to a Model Context Protocol server and calls one explicit governed-turn tool. In the current shipped slice, it supports:
- local
stdio - remote
streamable_http
:::info v1 scope
This slice is tool-contract based. AgentXchain does not claim that any arbitrary MCP server can execute a governed turn. The runtime is valid only when the target server exposes the configured tool (default: agentxchain_turn) and that tool returns a valid AgentXchain turn result. Supported MCP transports in this slice are stdio and streamable_http. Deprecated SSE transport is not part of this contract.
:::
How it works
- The orchestrator writes the dispatch bundle.
- The adapter either spawns the configured
stdioserver or connects to the configuredstreamable_httpendpoint. - The adapter initializes an MCP client connection and runs
tools/list. - It verifies that the configured tool exists.
- It calls that tool with governed turn metadata, dispatch file paths, and the rendered
PROMPT.mdandCONTEXT.mdcontent. - The tool returns the turn result either as
structuredContentor JSON text in a text content block. - The adapter stages that result at
.agentxchain/staging/<turn_id>/turn-result.json.
This is synchronous in v1, like api_proxy. There is no separate polling phase.
:::note Streamable HTTP Interop
MCP streamable_http POST requests must negotiate both JSON and event-stream responses. If you build or probe a remote MCP server outside the SDK path, send Accept: application/json, text/event-stream or expect transport-level rejection from SDK-based servers.
:::
Required tool contract
The MCP server must expose one tool named by tool_name. The default is agentxchain_turn.
The adapter sends an argument object containing:
| Argument | Description |
|---|---|
run_id | Current run identifier |
turn_id | Current turn identifier |
role | Assigned role for this turn |
phase | Current governed phase (default: planning, implementation, qa; custom phases are supported via routing config and advance only in declared order) |
runtime_id | Runtime identifier from config |
project_root | Absolute path to the project root |
dispatch_dir | Absolute path to the dispatch turn directory containing ASSIGNMENT.json, PROMPT.md, CONTEXT.md |
assignment_path | Absolute path to ASSIGNMENT.json |
prompt_path | Absolute path to PROMPT.md |
context_path | Absolute path to CONTEXT.md |
staging_path | Absolute path where the adapter will write the staged result (the tool does not write this file) |
prompt | Rendered PROMPT.md content as a string |
context | Rendered CONTEXT.md content as a string (empty string if no context file) |
The tool must return a valid turn-result object. The adapter accepts the result in three forms:
structuredContent(preferred) — the turn result as a direct object in the MCP response- JSON text — a text content block containing the turn-result JSON string
- Nested SDK wrapper — some MCP SDK versions wrap the response in a
toolResultenvelope; the adapter unwraps this automatically
The adapter stages the result, then the normal orchestrator validation and acceptance flow runs unchanged.
:::tip Validation leniency
The adapter accepts any object that has at least one identity field (run_id or turn_id) and one lifecycle field (status, role, or runtime_id). Missing fields are handled downstream by the turn-result validator. Your tool does not need to produce every field to be staged successfully.
:::
Timeout
The default timeout for MCP dispatch is 20 minutes (1,200,000 ms). If the turn has a deadline_at, the remaining time until deadline is used instead. The timeout applies to both the tools/list and tools/call requests, and resets on progress notifications from the server.
Configuration
Local stdio runtime
{
"roles": {
"dev": {
"title": "Developer",
"mandate": "Implement approved work safely.",
"write_authority": "authoritative",
"runtime": "mcp-dev"
}
},
"runtimes": {
"mcp-dev": {
"type": "mcp",
"command": "node",
"args": ["./examples/mcp-echo-agent/server.js"],
"tool_name": "agentxchain_turn",
"cwd": "."
}
}
}
| Field | Required | Description |
|---|---|---|
type | yes | Must be "mcp" |
transport | no | MCP transport. Defaults to stdio. streamable_http is supported for remote endpoints |
command | yes for stdio | Executable to spawn (string or string array) |
args | no | Arguments when command is a string (not valid with array command) |
tool_name | no | Tool to call on the server (default: agentxchain_turn) |
cwd | no for stdio | Working directory for the server process (relative to project root) |
url | yes for streamable_http | Absolute HTTP or HTTPS MCP endpoint |
headers | no for streamable_http | Static string request headers attached to MCP HTTP requests |
Remote streamable HTTP runtime
{
"roles": {
"dev": {
"title": "Developer",
"mandate": "Implement approved work safely.",
"write_authority": "authoritative",
"runtime": "mcp-remote-dev"
}
},
"runtimes": {
"mcp-remote-dev": {
"type": "mcp",
"transport": "streamable_http",
"url": "http://127.0.0.1:8787/mcp",
"tool_name": "agentxchain_turn",
"headers": {
"x-agentxchain-project": "demo"
}
}
}
}
For streamable_http, the runtime stays synchronous from AgentXchain's point of view: tools/list, tools/call, stage result, validate, accept. If the server returns 405 on GET, the MCP SDK still proceeds over POST request flow. AgentXchain does not require SSE support in this slice.
Example servers
Two reference MCP servers are shipped:
| Example | Transport | Use case |
|---|---|---|
examples/mcp-echo-agent/ | stdio | Local agents on the same machine |
examples/mcp-http-echo-agent/ | streamable_http | Remote agents, hosted services, containers |
Both implement the full agentxchain_turn tool contract and return a validator-clean no-op governed turn result. The only difference is transport: stdio is spawned per-turn by the adapter; HTTP is a long-running server you start independently.
For a real governed-project wiring path showing both transports, see the governed-todo-app example. Use either echo server as a starting point for building your own governed MCP agent.
When to use
- Local agent runtimes that already expose an MCP stdio server
- Remote MCP servers that expose a governed-turn tool over
streamable_http - Connector work where you want a governed turn contract over MCP instead of raw subprocess prompts
- Tool-driven local integrations that can return structured turn output directly
api_proxy adapter
The api_proxy adapter calls an LLM API directly, handles retries, tracks token usage, and writes the staged result on behalf of the model.
:::info Write authority support
The api_proxy adapter supports review_only and proposed write authority roles. Roles with authoritative write authority cannot bind to api_proxy runtimes — the adapter does not support tool use or direct repo writes.
- review_only: The model returns a structured review verdict. The orchestrator materializes it to
.agentxchain/reviews/. - proposed: The model returns a
proposed_changesarray with structured file proposals. The orchestrator materializes them to.agentxchain/proposed/<turn_id>/for review — they are not applied to the working tree. - Accepted proposal artifacts are rendered into subsequent
review_onlydispatch context so peer reviewers can inspect the staged proposal, not just a filename list. - Operators manage proposals with
agentxchain proposal list|diff|apply|reject.proposal apply <turn_id>compares the current workspace against the proposal's captured source snapshot before writing. If the file diverged meanwhile, apply fails closed instead of silently overwriting it.proposal apply <turn_id> --forceis the explicit operator override for that conflict.proposal reject <turn_id> --reason "..."marks the proposal as rejected. Selective apply is supported with--file <path>. :::
These restrictions are operational, not academic. An api_proxy QA turn can return a structured verdict and request completion, but it cannot directly author .planning/acceptance-matrix.md or .planning/ship-verdict.md. A proposed api_proxy turn can propose code changes, but those proposals are staged for human or peer review, not applied automatically. Operators apply or reject proposals explicitly using agentxchain proposal apply or agentxchain proposal reject. If your completion gate depends on repo-local files, those files must contain real content from a writable or manual path.
How it works
- The orchestrator writes the dispatch bundle.
- The adapter assembles a prompt from
PROMPT.mdandCONTEXT.md. - If preflight tokenization is enabled, the adapter evaluates the token budget and trims context to fit the model's context window.
- The adapter calls the configured LLM API with retry logic.
- On success, the adapter parses the model's response into a turn result and stages it.
- Token usage and cost are recorded in the turn metadata.
Supported providers
| Provider | Status |
|---|---|
| Anthropic | Supported (Claude Opus, Sonnet, Haiku) |
| OpenAI | Supported for chat-completions-compatible review models (for example gpt-4o, gpt-4o-mini) |
Supported for Gemini models via the generateContent API (for example gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash) | |
| Ollama | Supported for local models (Llama, Mistral, Gemma, Phi, etc.) via OpenAI-compatible API — no API key required |
Anthropic, OpenAI, Google, and Ollama are supported. OpenAI support uses the Chat Completions endpoint with JSON output. Google support uses the Gemini generateContent endpoint with responseMimeType: "application/json". The API key is passed as a query parameter per Google's API convention. When Gemini blocks a prompt or halts with a non-STOP finish reason, AgentXchain preserves that Google-specific reason in the extraction-failure message instead of collapsing it into a generic parse error. Ollama support uses the OpenAI-compatible Chat Completions endpoint at http://localhost:11434/v1/chat/completions by default, with optional auth. Tool use, background execution, and streaming are out of scope for api_proxy.
Custom endpoint override
For supported providers, you may override the default API endpoint with an optional base_url field on the runtime:
{
"type": "api_proxy",
"provider": "openai",
"model": "gpt-4o-mini",
"auth_env": "OPENAI_API_KEY",
"base_url": "http://127.0.0.1:4010/v1/chat/completions"
}
Use this for local mocks, enterprise gateways, Azure-style front doors, or self-hosted deployments that speak the same provider-family request/response contract. This is an endpoint override, not a new provider type: provider still determines request format, auth headers, and error classification, and base_url stays within existing provider families only. base_url, when present, must be an absolute http or https URL.
Error classification
The adapter classifies every error into a named error class with a retryable flag:
| Error class | Trigger | Retryable |
|---|---|---|
auth_failure | HTTP 401, 403 | No |
model_not_found | HTTP 404 | No |
invalid_request | HTTP 400 (non-context) | No |
context_overflow | HTTP 400 with context/token keywords | No |
rate_limited | HTTP 429 (transient) | Yes |
rate_limited | HTTP 429 with spend/budget limit | No |
provider_overloaded | HTTP 529 | Yes |
network_failure | Connection error | Yes |
timeout | Request timeout | Yes |
response_parse_failure | Malformed JSON response | Yes |
turn_result_extraction_failure | Valid JSON but no extractable turn result | Yes |
unknown_api_error | HTTP 500 or unclassified | Yes |
Each classified error includes error_class, message, recovery instructions, retryable flag, http_status, and raw_detail for auditability.
Retry policy
Retry behavior is configured via a retry_policy object in the runtime config. The retry policy applies uniformly to all retryable error classes — there is no per-HTTP-status retry schedule.
Default policy (when retry_policy.enabled: true with no overrides):
| Field | Default |
|---|---|
max_attempts | 3 |
base_delay_ms | 1000 |
max_delay_ms | 8000 |
backoff_multiplier | 2 |
jitter | "full" (randomized delay up to computed backoff) |
retry_on | All retryable error classes |
Retry traces (attempts, delays, outcomes) are persisted as audit artifacts in the staging directory.
Model tier and retry budget
Not all models produce schema-conformant turn results on every attempt. Cheaper or smaller models may require governed retries for valid output. In live testing, claude-haiku-4-5-20251001 required 2–3 attempts to produce a valid governed turn result due to schema violations such as invalid proposed_next_role values and malformed artifacts_created arrays. Larger models (claude-sonnet-4-6, claude-opus-4-6) produce conformant results more reliably on the first attempt.
Budget implication: When using the api_proxy adapter with cheaper models, plan for retry overhead. A max_attempts: 3 policy means worst-case API spend is 3× the single-call cost. For claude-haiku-4-5-20251001 at ~$0.004 per governed turn, worst-case is ~$0.012. For claude-opus-4-6 at ~$0.90 per turn, worst-case is ~$2.70. Choose your model tier based on both quality requirements and retry budget tolerance.
Supported models with built-in cost tracking: claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5-20251001, gpt-4o, gpt-4o-mini, gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, o3, o3-mini, o4-mini, gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash. Models not in this list still work but report $0 cost in turn telemetry. Override or add per-model cost rates via config --set:
agentxchain config --set budget.cost_rates.deepseek-v3.input_per_1m 0.27
agentxchain config --set budget.cost_rates.deepseek-v3.output_per_1m 1.10
For bulk rate tables covering many models at once, edit budget.cost_rates in agentxchain.json directly.
The governed retry mechanism (rejectTurn → re-dispatch) is part of the protocol, not a workaround. It is designed to handle exactly this kind of model-level schema non-conformance. The retry policy, error classification, and audit trail work together to ensure that retry overhead is visible and auditable.
Preflight tokenization
When enabled, the adapter evaluates the token budget before making the API call. If the assembled prompt exceeds the model's context window minus a safety margin, the adapter trims context to fit.
provider_local preflight tokenization is currently Anthropic-only. OpenAI runtimes may use api_proxy, but if they enable preflight_tokenization, config validation fails closed because the repo does not yet ship an OpenAI local tokenizer.
{
"preflight_tokenization": {
"enabled": true,
"tokenizer": "provider_local",
"safety_margin_tokens": 2048
}
}
Preflight artifacts (effective context, token budget report) are persisted in the dispatch directory.
Token usage and cost tracking
The adapter records token usage from provider telemetry:
{
"adapter_meta": {
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"input_tokens": 12450,
"output_tokens": 3820,
"cost_usd": 0.073,
"retries": 0
}
}
input_tokens and output_tokens are always populated when the provider returns usage telemetry. usd is populated when the model exists in AgentXchain's pinned rate table; otherwise it remains 0 instead of inventing pricing.
Configuration
{
"roles": {
"qa": {
"title": "QA",
"mandate": "Challenge correctness and ship readiness.",
"write_authority": "review_only",
"runtime": "api-qa-openai"
}
},
"runtimes": {
"api-qa-openai": {
"type": "api_proxy",
"provider": "openai",
"model": "gpt-4o-mini",
"auth_env": "OPENAI_API_KEY",
"max_output_tokens": 4096,
"timeout_seconds": 120,
"retry_policy": {
"enabled": true,
"max_attempts": 3,
"base_delay_ms": 1000,
"max_delay_ms": 8000,
"backoff_multiplier": 2,
"jitter": "full"
}
},
"api-qa-anthropic": {
"type": "api_proxy",
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"auth_env": "ANTHROPIC_API_KEY",
"max_output_tokens": 4096,
"timeout_seconds": 120,
"preflight_tokenization": {
"enabled": true,
"tokenizer": "provider_local",
"safety_margin_tokens": 2048
}
},
"api-qa-google": {
"type": "api_proxy",
"provider": "google",
"model": "gemini-2.5-flash",
"auth_env": "GOOGLE_API_KEY",
"max_output_tokens": 4096,
"timeout_seconds": 120
},
"local-dev": {
"type": "api_proxy",
"provider": "ollama",
"model": "llama3.2",
"max_output_tokens": 4096,
"timeout_seconds": 180
}
}
}
Use an Anthropic runtime when you want local preflight tokenization. Use an OpenAI runtime when you want synchronous review-only dispatch to a chat-completions-compatible OpenAI model. Use a Google runtime for Gemini models — the adapter handles the generateContent endpoint format, API-key-as-query-parameter auth, and Gemini-specific usage telemetry automatically. Use an Ollama runtime for local models — no API key needed, no cloud dependency, and the same governed turn contract as cloud providers.
If you need to hit a mock or proxy endpoint, add base_url to the runtime block. Keep the provider field aligned with the endpoint contract you expect the adapter to speak.
remote_agent adapter
The remote_agent adapter dispatches governed turns over HTTP to an external agent service. This is the connector that proves protocol replaceability across non-local execution boundaries — the remote service receives a governed turn envelope and returns a valid turn-result JSON in a single synchronous response.
Unlike api_proxy (which speaks a specific provider's API), remote_agent posts the full AgentXchain turn envelope and expects the remote service to return a complete turn result. This makes it suitable for any external agent platform that implements the agentxchain_turn contract over HTTP.
In v1, remote_agent is an honest non-local JSON bridge, not a remote filesystem writer. It supports review_only and proposed roles. It does not support authoritative roles because the adapter has no proven local workspace mutation path.
Request envelope
The adapter POSTs a JSON body containing:
run_id,turn_id,role,phase,runtime_id— governed identitydispatch_dir— path to the dispatch bundle on the local machine (informational)prompt— renderedPROMPT.mdcontentcontext— renderedCONTEXT.mdcontent
Response contract
The remote service must return HTTP 200 with Content-Type: application/json and a body that is a valid AgentXchain turn result (same schema as any other adapter's staged result).
Error handling
- Non-2xx → dispatch failure with HTTP status in the error
- Non-JSON response → dispatch failure
- JSON but missing turn-result fields → dispatch failure
- Timeout → dispatch failure with
timedOut: true - Network error → dispatch failure
All failures mark the run as blocked with the turn retained for retry via agentxchain step --resume.
Write-authority boundary
review_onlyis supported. Accepted reviews materialize a derived artifact under.agentxchain/reviews/<turn_id>-<role>-review.md.proposedis supported. Accepted proposals materialize under.agentxchain/proposed/<turn_id>/and can be applied withagentxchain proposal apply.authoritativeis not supported in v1. If you need direct repo writes, uselocal_clior add a future connector with an explicit workspace-bridge contract.
Security
Authorization headers are sent to the remote service but are never echoed into dispatch logs or governance artifacts. Headers matching authorization, x-api-key, cookie, and proxy-authorization are redacted to [REDACTED] in all log output.
Config
{
"runtimes": {
"remote-review": {
"type": "remote_agent",
"url": "https://agent.example.com/agentxchain/turn",
"headers": {
"authorization": "Bearer replace-with-real-token"
},
"timeout_ms": 120000
}
}
}
| Field | Required | Default | Description |
|---|---|---|---|
type | Yes | — | Must be "remote_agent" |
url | Yes | — | Absolute HTTP or HTTPS URL of the remote agent service |
headers | No | {} | String-to-string request headers |
timeout_ms | No | 120000 | Request timeout in milliseconds (positive integer) |
Header values are sent exactly as configured. The runtime does not interpolate ${VAR} placeholders inside headers; pre-expand secrets before writing agentxchain.json.
Common validation failures
decisions[].idmust matchDEC-NNN. Dynamic IDs likeDEC-BRIDGE-20260409fail validation even if the rest of the payload is correct.review_onlyroles must raise at least one objection. A QA review with an emptyobjectionsarray is rejected by the protocol challenge requirement.proposedremote turns must includeproposed_changes[]. A summary-only dev response is not enough for acceptance orproposal apply.
Comparison with other adapters
remote_agent is not the same as:
api_proxy— speaks Anthropic/OpenAI/Google/Ollama provider APIs;remote_agentspeaks the AgentXchain turn-result contract directlymcp— uses the MCP tool protocol over stdio or streamable_http;remote_agentuses plain HTTP POST/responselocal_cli— spawns a local subprocess;remote_agentcalls a remote service
Use remote_agent when you have an external agent service that can accept governed turn envelopes and return structured turn results over HTTP.
Implementing a new adapter
AgentXchain adapters stay deliberately thin. If you are adding a runtime, implement the transport boundary and stop there.
Minimal checklist:
- Implement
dispatch: consume the governed turn envelope and deliver it to the runtime. - Implement
wait: block until the runtime finishes, times out, or fails definitively. - Implement
collect: return a valid staged turn result or a transport-level failure. - Read the dispatch bundle from
.agentxchain/dispatch/turns/<turn_id>/. - Write the staged result to
.agentxchain/staging/<turn_id>/turn-result.jsonor return the equivalent JSON to the orchestrator surface that will materialize it there. - Do not write orchestrator state, mutate routing, or bypass validation. Adapters transport work; the orchestrator owns governance.
If you need the full connector contract, examples, or packaging guidance, continue with Build Your Own Connector.
Comparison table
| Feature | manual | local_cli | api_proxy | mcp | remote_agent |
|---|---|---|---|---|---|
| Automation | None — human does all work | Full — subprocess runs autonomously | Full — API call runs autonomously | Full — MCP tool call runs autonomously | Full — HTTP call runs autonomously |
| Latency | Minutes (human speed) | Seconds to minutes | Seconds | Seconds to minutes | Seconds to minutes |
| Cost | Human time | Tool license + compute | API tokens | Tool/runtime dependent | Remote service dependent |
| Prompt transport | N/A (human reads files) | argv, stdin, or dispatch_bundle_only | HTTP request body | MCP stdio tools/list + tools/call | HTTP POST with turn envelope |
| Retry logic | N/A | Exit code based | Configurable exponential backoff with jitter | None in v1 | None in v1 — retry via step --resume |
| Timeout handling | Configurable poll timeout | SIGTERM → 10s grace → SIGKILL | HTTP timeout + retry policy | Request timeout via MCP client call | Configurable timeout_ms (default 120s) |
| Cost tracking | N/A | N/A | Automatic token tracking; USD when the model has a pinned rate table | N/A | N/A — remote service reports cost in turn result |
| Write authority | Any | Any | review_only or proposed only | Any | review_only or proposed only |
| Best for | PM review, human QA, approvals | Claude Code (default), or any local CLI via --dev-command | Direct Anthropic, OpenAI, Google Gemini, or Ollama (local) review calls | Local MCP-based agent runtimes with an explicit governed-turn tool | External agent services, hosted agent platforms, cross-network governed execution |