CLI Backends: Local AI Fallback with MCP Bridge

Configure and operate CLI backends for text-only fallback when API providers fail, including optional MCP tool bridge and JSONL streaming. For operators and developers needing reliable local AI responses.

Read this when

  • You want a reliable fallback when API providers fail
  • You are running local AI CLIs and want to reuse them
  • You want to understand the MCP loopback bridge for CLI backend tool access

OpenClaw offers a text-only fallback through a local AI CLI when API providers are down, rate-limited, or otherwise unreliable. Its design is deliberately cautious:

  • Gateway tools aren't injected directly, but a backend using bundleMcp: true can access them through a loopback MCP bridge.
  • JSONL streaming is available for CLIs that support it.
  • Sessions are supported, keeping follow-up turns coherent.
  • Images are passed through when the CLI accepts image paths.

Treat it as a safety net for "always works" text responses, not as the main route. For a full harness runtime with ACP session controls, background tasks, thread/conversation binding, and persistent external coding sessions, use ACP Agents instead; CLI backends are not ACP.

Tip

Creating a new backend plugin? See CLI backend plugins. This page covers configuring and operating an already-registered backend.

Quick start

The bundled Anthropic plugin registers a default claude-cli backend, so it functions without configuration beyond having Claude Code installed and logged in:

openclaw agent --agent main --message "hi" --model claude-cli/claude-sonnet-4-6

When no explicit agent list is configured, main is the default agent id; otherwise, substitute your own agent id.

The gateway service must have the CLI on its PATH. If a deployment needs a nonstandard executable path or arguments, register that adapter in a CLI backend plugin rather than embedding launch mechanics in openclaw.json.

OpenClaw auto-loads an owning bundled plugin when model selection or a model-scoped agentRuntime.id references its backend.

Using it as a fallback

Add the CLI backend to your fallback list so it runs only when primary models fail:

{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["claude-cli/claude-sonnet-4-6"],
      },
      models: {
        "anthropic/claude-opus-4-6": { alias: "Opus" },
        "claude-cli/claude-sonnet-4-6": {},
      },
    },
  },
}

Configured fallbacks remain eligible when the primary provider fails (auth, rate limits, timeouts), even when they are not in agents.defaults.modelPolicy.allow. Add a CLI backend model to that policy only when users should also be able to select it directly through /model, a session override, or --model. agents.defaults.models only owns per-model aliases, parameters, and metadata.

Configuration

Users select a registered backend through the model and runtime policy. Keep the model ref canonical and choose the CLI runtime per model:

{
  agents: {
    defaults: {
      model: "anthropic/claude-opus-5",
      models: {
        "anthropic/claude-opus-5": {
          agentRuntime: { id: "claude-cli" },
        },
      },
    },
  },
}

Credentials remain in OpenClaw auth profiles or the owning plugin's config. Command, argv, environment, parsing, session, image, and watchdog mechanics are plugin code registered with api.registerCliBackend(...).

How it works

  1. Chooses a backend by provider prefix (claude-cli/...).
  2. Constructs a system prompt using the same OpenClaw prompt and workspace context.
  3. Runs the CLI with a session id (if supported) so history stays consistent. The bundled claude-cli backend maintains a Claude stdio process per OpenClaw session and sends follow-up turns over stream-json stdin.
  4. Parses output (JSON or plain text) and returns the final text.
  5. Persists session ids per backend so follow-ups reuse the same CLI session.

Timeouts and long-running work

CLI backends have two independent limits:

  • agents.defaults.timeoutSeconds limits the whole agent turn. Normal Gateway turns inherit the 48-hour default; 0 makes the turn budget unlimited. A stored override such as 600 replaces that default.
  • The CLI no-output watchdog stops a subprocess that remains silent. Each backend plugin owns separate fresh/resume profiles, and the watchdog remains active even when the overall turn budget is unlimited.

Remove a short overall-timeout override to return to the 48-hour default, or set an explicit budget such as 12 hours:

# Return to the 48-hour default:
openclaw config unset agents.defaults.timeoutSeconds

# Or choose an explicit 12-hour limit:
openclaw config set agents.defaults.timeoutSeconds 43200

Background work started inside a CLI is still part of that CLI subprocess. If the parent turn reaches its overall limit, OpenClaw stops the subprocess and its CLI-internal background tasks together. For durable long work, use a detached OpenClaw sub-agent or ACP agent; detached sub-agents have no run timeout by default.

The openclaw agent command also has its own request deadline. Its 600-second fallback default applies to that command invocation, not to ordinary Gateway turns; see openclaw agent.

Claude CLI specifics

OpenClaw's managed Claude stdio sessions require the msg_lifecycle_v1 capability, first observed in the published Claude Code 2.1.206 build. At runtime OpenClaw does not trust the version string alone: it waits for Claude Code's system/init record to advertise msg_lifecycle_v1, then accepts assistant, tool, and result records only after the matching input lifecycle has started. Unknown capabilities are ignored. A CLI that omits the required capability fails immediately with claude update and gateway-restart guidance instead of waiting for the no-output watchdog.

Setup and Doctor treat 2.1.206 as advisory, so a lower-version compatible backport or wrapper remains selectable and is verified by the runtime gate.

claude --version
claude update
# Restart the OpenClaw gateway after updating.

Claude Code's public CLI documentation covers stream-json mode and updates but does not currently document the lifecycle event itself. OpenClaw therefore feature-detects the advertised capability; 2.1.206 is the first published Claude Code build observed to provide it.

The bundled claude-cli backend prefers Claude Code's native skill resolver. When the current skills snapshot has at least one selected skill with a materialized path, OpenClaw passes a temporary Claude Code plugin via --plugin-dir and omits the duplicate OpenClaw skills catalog from the appended system prompt. Without a materialized plugin skill, OpenClaw keeps the prompt catalog as a fallback. Skill env/API key overrides still apply to the child process environment for the run.

Claude CLI has its own noninteractive permission mode; OpenClaw maps that to the existing exec policy instead of adding Claude-specific config. For OpenClaw-managed Claude live sessions, the effective exec policy is authoritative: YOLO (tools.exec.mode: "full") normally launches Claude with --permission-mode bypassPermissions, while a restrictive policy launches it with --permission-mode default. Root-run gateways also use default because Claude Code rejects bypass mode for root. Per-agent agents.entries.*.tools.exec settings override the global tools.exec for that agent. The Anthropic plugin normalizes Claude's permission flags to match the effective policy and host restriction.

Under a restrictive policy, Claude asks OpenClaw over stdio before using one of its native or extension tools (its own Bash, WebFetch, or Claude in Chrome browser tools). When the effective exec ask setting is on-miss or always, OpenClaw relays each request as an interactive approval to the session's channel: Allow once permits the single call, Allow always permits that tool name for the rest of the live Claude session (in memory only, never persisted), and Deny, a timeout, or an unreachable approval route all deny the call. Policies that never prompt keep their old behavior: security: "deny" rejects every request, and ask off with less than full security (exec mode allowlist) denies without asking.

Claude browser tools and 1Password sign-in

Claude Code can drive a Chrome browser through the Claude in Chrome extension, including 1Password for Claude credential autofill. The bundled backend does not enable it; register a CLI backend plugin that appends --chrome to the launch args of a claude-stream-json-dialect backend. OpenClaw preserves a configured --chrome on normal runs and always forces --no-chrome on runs with a restricted tool policy, such as side questions. The Chrome window, the extension, and any 1Password approval prompts live on the gateway host, so someone must be at that machine to approve credential use.

The backend translates OpenClaw /think levels into Claude Code's native --effort flag: minimal/low becomes low, medium becomes medium, while high/xhigh/max are forwarded unchanged. For models supporting fixed thinking budgets, Claude Code is also launched with MAX_THINKING_TOKENS: off=0, minimal=1024, low=2048, medium=8192, high/xhigh=16384, and max=32768; positive fixed budgets turn off adaptive thinking. Models needing adaptive thinking skip the fixed budget and keep relying on --effort. With adaptive, configured effort flags and fixed-budget environment overrides are removed, so Claude Code determines effective thinking from its own environment, settings, and model defaults. Other CLI backends require their owning plugin to map the selected level before /think takes effect on the spawned CLI.

For OpenClaw to make use of claude-cli, Claude Code must already be logged in on the same machine:

claude auth login
claude auth status --text
openclaw models auth login --provider anthropic --method cli --set-default

With Docker setups, Claude Code has to be installed and logged in inside the persisted container home, not just on the host; see Claude CLI backend in Docker.

The gateway service needs to resolve claude on PATH. For a path that is not standard, register a small wrapper backend plugin.

Sessions

  • When the CLI supports sessions, set sessionArgs with a {sessionId} placeholder (for example ["--session-id", "{sessionId}"]).
  • If the CLI has a resume subcommand with different flags, set resumeArgs (takes the place of args during resume) and optionally resumeOutput for non-JSON resumes.
  • sessionMode:
    • always: a session id is always sent (a new UUID is generated when none is stored).
    • existing: a session id is sent only if one was previously stored.
    • none: a session id is never sent.
  • claude-cli defaults to liveSession: "claude-stdio", output: "jsonl", and input: "stdin", so follow-up turns reuse the live Claude process while it remains active, including for custom configs that leave out transport fields. If the gateway restarts or the idle process terminates, OpenClaw resumes from the stored Claude session id. Before resume, stored session ids are checked against a readable project transcript; a missing transcript clears the binding (logged as reason=transcript-missing) rather than quietly starting a new session under --resume.
  • Claude live sessions keep bounded JSONL output guards: 8 MiB and 20,000 raw JSONL lines per turn.
  • Stored CLI sessions are provider-owned continuity. Automatic reset is off by default; /reset and explicit daily or idle session.reset policies still terminate them.
  • Fresh CLI sessions normally reseed only from OpenClaw's compaction summary plus the post-compaction tail. To recover short sessions invalidated before compaction, a backend can opt in with reseedFromRawTranscriptWhenUncompacted: true. Raw transcript reseed stays bounded and limited to safe invalidations, such as a missing CLI transcript, an orphaned tool-use tail, message-policy/system-prompt/cwd/MCP changes, or a session-expired retry; auth profile or credential-epoch changes never reseed raw transcript history.

Serialization: serialize: true keeps same-lane runs ordered (most CLIs serialize on one provider lane). OpenClaw also drops stored CLI session reuse when the selected auth identity changes, including a changed auth profile id, static API key, static token, or OAuth account identity when the CLI exposes one; OAuth access/refresh token rotation alone does not cut the session. If a CLI has no stable OAuth account id, OpenClaw lets that CLI enforce its own resume permissions.

Fallback prelude from claude-cli sessions

When a claude-cli attempt fails over to a non-CLI candidate in agents.defaults.model.fallbacks, OpenClaw seeds the next attempt with a context prelude harvested from Claude Code's local JSONL transcript (under ~/.claude/projects/, keyed per workspace). Without this seed the fallback provider starts cold, since OpenClaw's own session transcript is empty for claude-cli runs.

  • The prelude favors the most recent /compact summary or compact_boundary marker, then attaches the latest turns after the boundary, staying within a character limit. Turns before the boundary are omitted since the summary covers them.
  • Tool blocks get merged into compact (tool call: name) and (tool result: …) hints to keep the prompt budget accurate; if the summary grows too large, it gets cut off and tagged (truncated).
  • Fallbacks from claude-cli to claude-cli within the same provider rely on Claude's own --resume and bypass the prelude.
  • The seed applies the same Claude session-file path validation already in place, so arbitrary paths stay unreadable.

Images

Plugin authors signal image-path support via imageArg:

imageArg: "--image",
imageMode: "repeat"

OpenClaw converts base64 images into temp files. When imageArg is enabled, those paths go in as CLI args; otherwise, OpenClaw appends the file paths to the prompt (path injection), which suits CLIs that pull local files straight from plain paths.

Inputs and outputs

  • output: "text" (default) treats stdout as the final answer.
  • output: "json" attempts JSON parsing to pull out text and a session id.
  • output: "jsonl" reads a JSONL stream and grabs the last agent message plus session identifiers when they exist.
  • For Gemini CLI JSON output, OpenClaw gets reply text from response and usage from stats when usage is absent or empty. The included Gemini CLI adapter uses stream-json.

Input modes:

  • input: "arg" (default) hands the prompt as the final CLI argument.
  • input: "stdin" delivers the prompt through stdin.
  • If the prompt runs long and maxPromptArgChars is set, stdin takes over.

Plugin-owned defaults

CLI backend defaults sit within the plugin surface:

  • Plugins declare them using api.registerCliBackend(...).
  • The backend id acts as the provider prefix in model refs.
  • Command, argv, environment, parser, session, and watchdog behavior stays in plugin code.
  • Backend-specific normalization remains plugin-owned via the optional normalizeConfig hook.

Anthropic handles claude-cli and Google handles google-gemini-cli. OpenAI Codex agent runs rely on the Codex app-server harness through openai/*; OpenClaw no longer ships a bundled codex-cli backend.

The bundled Anthropic plugin registers for claude-cli:

KeyValue
commandclaude
args-p --output-format stream-json --include-partial-messages --verbose --setting-sources user --allowedTools mcp__openclaw__* --disallowedTools ScheduleWakeup,CronCreate,Bash(run_in_background:true),Monitor
outputjsonl
inputstdin
modelArg--model
sessionArgs["--session-id", "{sessionId}"]
sessionModealways
live-session requirementmsg_lifecycle_v1 (first observed in Claude Code 2.1.206)
imageArg@
imagePathScopeworkspace
systemPromptFileArg--append-system-prompt-file
systemPromptModeappend

On Claude Code 2.1.98 or later, the bundled backend adds --exclude-dynamic-system-prompt-sections after its bounded Gateway-startup version probe. Older, unknown, or failed probes keep the established argv.

The bundled Google plugin registers for google-gemini-cli:

KeyValue
commandgemini
args--skip-trust --approval-mode auto_edit --output-format stream-json --prompt {prompt}
resumeArgsidentical, but with --resume {sessionId}
output / resumeOutputjsonl
jsonlDialectgemini-stream-json
imageArg@
imagePathScopeworkspace
modelArg--model
sessionModeexisting
sessionIdFields["session_id", "sessionId"]

Before anything else, the local Gemini CLI needs to be present and reachable as gemini on PATH (via brew install gemini-cli or npm install -g @google/gemini-cli), and the chosen model must carry a supported Google AI Studio API-key profile. Older, valid legacy Gemini CLI OAuth profiles still work at runtime, but OpenClaw will neither generate nor fix them.

What to expect from Gemini CLI output:

  • With the default stream-json parser, assistant message events, tool events, final result usage, and fatal Gemini error events are all picked up.
  • When usage is missing or blank, usage defaults to stats; stats.cached maps into OpenClaw cacheRead, and if stats.input is absent, input tokens are derived from stats.input_tokens - stats.cached.

Text transform overlays

Backends that only need modest prompt or message compatibility shims can apply bidirectional text transforms without swapping out a provider or CLI backend:

api.registerTextTransforms({
  input: [{ from: /red basket/g, to: "blue basket" }],
  output: [{ from: /blue basket/g, to: "red basket" }],
});

The system prompt and user prompt handed to the CLI are rewritten by input. Streamed assistant text and parsed final text get rewritten by output before OpenClaw processes its own control markers and channel delivery; for provider-backed model calls, string values inside structured tool-call arguments are also restored after stream repair and before tool execution. Raw provider JSON fragments stay untouched; consumers should rely on the structured partial, end, or result payload.

For CLIs emitting provider-specific JSONL events, set jsonlDialect on that backend's config: claude-stream-json suits Claude Code-compatible streams, while gemini-stream-json handles Gemini CLI stream-json events.

Native compaction ownership

Certain CLI backends run an agent that compacts its own transcript, so OpenClaw must skip its safeguard summarizer for them, otherwise it fights the backend's compaction and can hard-fail the turn.

claude-cli exposes no harness endpoint (Claude Code compacts internally), hence it declares ownsNativeCompaction: true. Automatic OpenClaw compaction defers to Claude Code, while an explicit /compact resumes the bound Claude Code session and issues its native /compact command. OpenClaw forwards the run's effective context budget through Claude Code's documented CLAUDE_CODE_AUTO_COMPACT_WINDOW, so native auto-compaction lines up with configured Anthropic contextTokens limits. Native-harness sessions like Codex keep routing to their harness compaction endpoint.

api.registerCliBackend({
  id: "my-cli",
  ownsNativeCompaction: true,
  manualCompaction: {
    buildPrompt: (instructions) => (instructions ? `/compact ${instructions}` : "/compact"),
    input: "arg",
    validateOutput: (rawOutput) =>
      rawOutput.includes('"type":"compaction_complete"')
        ? { ok: true }
        : { ok: false, reason: "CLI did not confirm compaction." },
  },
  // ...
});

Only set ownsNativeCompaction for a backend that truly owns compaction: it must reliably keep its transcript bounded near the context window and persist a resumable session (e.g. --resume / --session-id), or a deferred session may exceed the budget.

Add the atomic manualCompaction capability only when its command compacts the resumed session in place. Its input picks the transport the backend command actually recognizes, and validateOutput must demand a positive backend acknowledgement rather than treating a zero exit as success. OpenClaw treats it as an internal control operation: it is not recorded as a user turn and skips agent or context-engine turn hooks.

Bundle MCP overlays

CLI backends never receive OpenClaw tool calls directly, but a backend can opt into a generated MCP config overlay via bundleMcp: true. Current bundled behavior:

  • claude-cli: the generated strict MCP configuration file.
  • google-gemini-cli: the generated Gemini system settings file.

When bundle MCP is turned on, OpenClaw performs these actions:

  • it starts a loopback HTTP MCP server that presents gateway tools to the CLI process, secured by a per-run context grant (OPENCLAW_MCP_TOKEN) valid only for the current execution attempt;
  • tool access is tied to the session, account, and channel context chosen by the Gateway, rather than relying on headers from the child process;
  • bundle-MCP servers enabled for the current workspace are loaded and combined with any existing backend MCP config or settings structure;
  • the launch config gets rewritten according to the backend-owned integration mode supplied by the owning plugin.

Restricted runs, such as cron jobs using toolsAllow, demand an exact backend-owned translation. The bundled claude-cli backend turns off Claude's native tools and user, project, and local customizations, covering hooks, plugins, agents, skills, and CLAUDE.md. After that, every permitted OpenClaw tool is exposed through the grant-scoped MCP server. This arrangement keeps filesystem, process, exec, approval, and sandbox policy under OpenClaw's control, instead of extending authority to Claude's native tools or customization processes. The same MCP list is applied both in Claude's generated config and again by the Gateway during tool listing and execution. Before the grant is minted, core rejects backend translations that reference any MCP permission outside the original allowlist. Backends lacking an exact translation still fail closed.

When no MCP servers are active, OpenClaw still inserts a strict config if a backend opts into bundle MCP, ensuring background runs remain isolated.

Session-scoped bundled MCP runtimes are cached for reuse within a session and then cleaned up after 10 minutes of idle time. One-shot embedded runs, like auth probes, slug generation, and active-memory recall, request cleanup at run end so stdio children and Streamable HTTP/SSE streams do not outlive the run.

For claude-cli, a compatible selected or ordered OpenClaw OAuth/token profile gets forwarded to that Claude child. This makes per-agent profiles authoritative for the turn while preserving Claude's native host login when no compatible profile exists.

Reseed history cap

When a fresh CLI session is seeded from a prior OpenClaw transcript (for instance after a session_expired retry), the rendered <conversation_history> block is capped to keep reseed prompts from growing too large. The default is 12,288 characters (about 3,000 tokens).

Claude CLI backends adjust this cap based on the resolved Claude context window instead: larger context windows receive a larger prior-history slice, up to a fixed ceiling; other CLI backends stick with the conservative default. This cap only applies to the reseed prompt's prior-history block.

Limitations

  • OpenClaw does not inject tool calls into the CLI backend protocol. Backends only see gateway tools when they opt into bundleMcp: true.
  • Streaming varies by backend: some stream JSONL, others buffer until exit.
  • Structured outputs depend on the CLI's own JSON format.

Troubleshooting

SymptomFix
CLI not foundPlace the CLI on the gateway service's PATH, or adjust the owning plugin's registered command.
Wrong model nameModify the plugin's modelAliases mapping.
No session continuityVerify the plugin's sessionArgs and sessionMode.
Images ignoredCheck the plugin's imageArg and the CLI's file-path support.
3,478 words · updated Aug 24, 2026