The Process adapter is the generic version of the Claude Code and Codex adapters. It launches a local binary, forwards stdio, parses tool-call events, and relays them through the MCP server, without being tied to a specific runtime. Use it for:
  • Gemini CLI (Google’s agent CLI)
  • Cursor Agent (Cursor’s cursor-agent command)
  • OpenClaw (our in-house OSS runtime)
  • OpenCode (the open-source OpenCode runtime)
  • Any other CLI agent that can emit newline-delimited JSON events to stdout and read responses from stdin
If you are writing your own agent CLI, the Process adapter is the easiest target. You do not write an adapter at all; you write to the adapter’s stdio protocol and the adapter handles the orchestrator side.

Configuring the Process adapter

The adapter is parameterized by the binary and the argv template. Configure it once per runtime, then hire agents that use that runtime. In config.json:
{
  "adapters": {
    "gemini-cli": {
      "type": "process",
      "binary": "gemini",
      "args": ["--non-interactive", "--workdir", "{{workspacePath}}"],
      "env": {
        "GOOGLE_GENERATIVE_AI_API_KEY": "${GOOGLE_GENERATIVE_AI_API_KEY}"
      },
      "protocol": "json-lines",
      "costModel": "google/gemini-2.0"
    },
    "cursor-agent": {
      "type": "process",
      "binary": "cursor-agent",
      "args": ["--headless", "--workspace", "{{workspacePath}}"],
      "env": {},
      "protocol": "json-lines",
      "costModel": "anthropic/claude-sonnet-4-6"
    },
    "openclaw": {
      "type": "process",
      "binary": "openclaw",
      "args": ["run", "--lease-token", "{{leaseToken}}", "--mcp", "{{mcpUrl}}"],
      "env": {},
      "protocol": "json-lines",
      "costModel": "deepseek/deepseek-chat"
    }
  }
}
Template variables the adapter substitutes:
  • {{workspacePath}} — the run workspace absolute path
  • {{leaseToken}} — the current lease token
  • {{fencingToken}} — the fencing token
  • {{taskId}} — the task being worked on
  • {{runId}} — the run identifier
  • {{mcpUrl}} — the MCP server URL
  • Any env var in the format ${NAME}

The stdio protocol

The adapter expects newline-delimited JSON on both sides. Every line is a single event.

Events the agent writes to stdout

{"type": "progress", "summary": "...", "beliefs": [...], "attempted": [...]}
{"type": "tool_call", "id": "call_1", "tool": "read_file", "args": {...}}
{"type": "comment", "text": "...", "mentions": ["@board-operator"]}
{"type": "complete", "output": {...}, "cost": {...}}
{"type": "failed", "reason": "budget_exceeded", "details": "..."}

Events the adapter writes to stdin

{"type": "tool_result", "id": "call_1", "ok": true, "value": "..."}
{"type": "tool_result", "id": "call_1", "ok": false, "error": "..."}
{"type": "budget_update", "remaining": {"tokens": 180000, "usd": 1.1}}
{"type": "shutdown", "reason": "lease_expired"}
The agent must read from stdin continuously and respond to shutdown events by exiting cleanly within a few seconds.

Cost reporting

Because the Process adapter is generic, it does not know how to extract token counts from a random binary. The agent is responsible for reporting cost in each progress or complete event:
{
  "type": "complete",
  "output": {...},
  "cost": {
    "model": "gemini-2.0-flash",
    "inputTokens": 15200,
    "outputTokens": 3100,
    "extras": [{"label": "image_gen", "usd": 0.12}]
  }
}
The adapter looks up the model in the cost model table (costModel in the config) and computes the USD cost. If the agent reports extras, they are added verbatim.

Process group and shutdown

Every Process adapter run is launched with detached: true and a fresh process group. When the adapter needs to kill the run, it sends SIGTERM to the process group (kill(-pid, SIGTERM)) with a grace window before upgrading to SIGKILL. This is the zombie-safe pattern and it is baked into the SDK; you do not implement it yourself.

Writing a CLI that targets this adapter

If you are building a new agent runtime, the Process adapter is the zero-friction path. The contract is:
  1. Read the env vars (MCP_SERVER_URL, CA_LEASE_TOKEN, etc.)
  2. Read JSON lines from stdin
  3. Write JSON lines to stdout
  4. Exit cleanly on shutdown
A minimum example in Python, about 30 lines:
import json, sys, os

lease = os.environ["CA_LEASE_TOKEN"]
mcp_url = os.environ["MCP_SERVER_URL"]

def say(event):
    print(json.dumps(event), flush=True)

def listen():
    for line in sys.stdin:
        yield json.loads(line)

say({"type": "progress", "summary": "starting"})
say({"type": "tool_call", "id": "1", "tool": "read_task", "args": {}})

for event in listen():
    if event.get("type") == "shutdown":
        sys.exit(0)
    if event.get("type") == "tool_result" and event["id"] == "1":
        task = event["value"]
        # ... do the work ...
        say({"type": "complete", "output": {"done": True}, "cost": {"usd": 0}})
        break
In practice you want a real LLM loop and proper error handling, but this is the whole protocol.

When not to use Process

Use the HTTP adapter instead if:
  • The agent runs on a different host
  • The agent is a long-lived service that takes many sessions
  • You want to pool the agent across multiple runs to avoid cold-start costs
Use a custom adapter instead if:
  • The runtime has its own event protocol you cannot change
  • You need to translate between the runtime’s tool set and the orchestrator’s tool set
  • The runtime needs something the SDK does not offer
See Creating an adapter for the custom path.

Next