Skip to main content

CLI backends (fallback runtime)

OpenClaw can run local AI CLIs as a text-only fallback when API providers are down, rate-limited, or temporarily misbehaving. This is intentionally conservative:
  • Tools are disabled (no tool calls).
  • Text in → text out (reliable, with Claude CLI partial text streaming when enabled).
  • Sessions are supported (so follow-up turns stay coherent).
  • Images can be passed through if the CLI accepts image paths.
This is designed as a safety net rather than a primary path. Use it when you want “always works” text responses without relying on external APIs. If you want 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.

Beginner-friendly quick start

You can use Claude CLI without any config (the bundled Anthropic plugin registers a default backend):
openclaw agent --message "hi" --model claude-cli/claude-sonnet-4-6
Codex CLI also works out of the box (via the bundled OpenAI plugin):
openclaw agent --message "hi" --model codex-cli/gpt-5.4
If your gateway runs under launchd/systemd and PATH is minimal, add just the command path:
{
  agents: {
    defaults: {
      cliBackends: {
        "claude-cli": {
          command: "/opt/homebrew/bin/claude",
        },
      },
    },
  },
}
That’s it. No keys, no extra auth config needed beyond the CLI itself. If you use a bundled CLI backend as the primary message provider on a gateway host, OpenClaw now auto-loads the owning bundled plugin when your config explicitly references that backend in a model ref or under agents.defaults.cliBackends.

Using it as a fallback

Add a CLI backend to your fallback list so it only runs when primary models fail:
{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["claude-cli/claude-sonnet-4-6", "claude-cli/claude-opus-4-6"],
      },
      models: {
        "anthropic/claude-opus-4-6": { alias: "Opus" },
        "claude-cli/claude-sonnet-4-6": {},
        "claude-cli/claude-opus-4-6": {},
      },
    },
  },
}
Notes:
  • If you use agents.defaults.models (allowlist), you must include claude-cli/....
  • If the primary provider fails (auth, rate limits, timeouts), OpenClaw will try the CLI backend next.
  • The bundled Claude CLI backend still accepts shorter aliases like claude-cli/opus, claude-cli/opus-4.6, or claude-cli/sonnet, but docs and config examples use the canonical claude-cli/claude-* refs.

Configuration overview

All CLI backends live under:
agents.defaults.cliBackends
Each entry is keyed by a provider id (e.g. claude-cli, my-cli). The provider id becomes the left side of your model ref:
<provider>/<model>

Example configuration

{
  agents: {
    defaults: {
      cliBackends: {
        "claude-cli": {
          command: "/opt/homebrew/bin/claude",
        },
        "my-cli": {
          command: "my-cli",
          args: ["--json"],
          output: "json",
          input: "arg",
          modelArg: "--model",
          modelAliases: {
            "claude-opus-4-6": "opus",
            "claude-sonnet-4-6": "sonnet",
          },
          sessionArg: "--session",
          sessionMode: "existing",
          sessionIdFields: ["session_id", "conversation_id"],
          systemPromptArg: "--system",
          systemPromptWhen: "first",
          imageArg: "--image",
          imageMode: "repeat",
          serialize: true,
        },
      },
    },
  },
}

How it works

  1. Selects a backend based on the provider prefix (claude-cli/...).
  2. Builds a system prompt using the same OpenClaw prompt + workspace context.
  3. Executes the CLI with a session id (if supported) so history stays consistent.
  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.

Sessions

  • If the CLI supports sessions, set sessionArg (e.g. --session-id) or sessionArgs (placeholder {sessionId}) when the ID needs to be inserted into multiple flags.
  • If the CLI uses a resume subcommand with different flags, set resumeArgs (replaces args when resuming) and optionally resumeOutput (for non-JSON resumes).
  • sessionMode:
    • always: always send a session id (new UUID if none stored).
    • existing: only send a session id if one was stored before.
    • none: never send a session id.
Serialization notes:
  • serialize: true keeps same-lane runs ordered.
  • Most CLIs serialize on one provider lane.
  • claude-cli is narrower: resumed runs serialize per Claude session id, and fresh runs serialize per workspace path. Independent workspaces can run in parallel.

Images (pass-through)

If your CLI accepts image paths, set imageArg:
imageArg: "--image",
imageMode: "repeat"
OpenClaw will write base64 images to temp files. If imageArg is set, those paths are passed as CLI args. If imageArg is missing, OpenClaw appends the file paths to the prompt (path injection), which is enough for CLIs that auto- load local files from plain paths (Claude CLI behavior).

Inputs / outputs

  • output: "json" (default) tries to parse JSON and extract text + session id.
  • For Gemini CLI JSON output, OpenClaw reads reply text from response and usage from stats when usage is missing or empty.
  • output: "jsonl" parses JSONL streams (for example Claude CLI stream-json and Codex CLI --json) and extracts the final agent message plus session identifiers when present.
  • output: "text" treats stdout as the final response.
Input modes:
  • input: "arg" (default) passes the prompt as the last CLI arg.
  • input: "stdin" sends the prompt via stdin.
  • If the prompt is very long and maxPromptArgChars is set, stdin is used.

Defaults (plugin-owned)

The bundled Anthropic plugin registers a default for claude-cli:
  • command: "claude"
  • args: ["-p", "--output-format", "stream-json", "--include-partial-messages", "--verbose", "--permission-mode", "bypassPermissions"]
  • resumeArgs: ["-p", "--output-format", "stream-json", "--include-partial-messages", "--verbose", "--permission-mode", "bypassPermissions", "--resume", "{sessionId}"]
  • output: "jsonl"
  • input: "stdin"
  • modelArg: "--model"
  • systemPromptArg: "--append-system-prompt"
  • sessionArg: "--session-id"
  • systemPromptWhen: "first"
  • sessionMode: "always"
The bundled OpenAI plugin also registers a default for codex-cli:
  • command: "codex"
  • args: ["exec","--json","--color","never","--sandbox","workspace-write","--skip-git-repo-check"]
  • resumeArgs: ["exec","resume","{sessionId}","--color","never","--sandbox","workspace-write","--skip-git-repo-check"]
  • output: "jsonl"
  • resumeOutput: "text"
  • modelArg: "--model"
  • imageArg: "--image"
  • sessionMode: "existing"
The bundled Google plugin also registers a default for google-gemini-cli:
  • command: "gemini"
  • args: ["--prompt", "--output-format", "json"]
  • resumeArgs: ["--resume", "{sessionId}", "--prompt", "--output-format", "json"]
  • modelArg: "--model"
  • sessionMode: "existing"
  • sessionIdFields: ["session_id", "sessionId"]
Prerequisite: the local Gemini CLI must be installed and available as gemini on PATH (brew install gemini-cli or npm install -g @google/gemini-cli). Gemini CLI JSON notes:
  • Reply text is read from the JSON response field.
  • Usage falls back to stats when usage is absent or empty.
  • stats.cached is normalized into OpenClaw cacheRead.
  • If stats.input is missing, OpenClaw derives input tokens from stats.input_tokens - stats.cached.
Override only if needed (common: absolute command path).

Plugin-owned defaults

CLI backend defaults are now part of the plugin surface:
  • Plugins register them with api.registerCliBackend(...).
  • The backend id becomes the provider prefix in model refs.
  • User config in agents.defaults.cliBackends.<id> still overrides the plugin default.
  • Backend-specific config cleanup stays plugin-owned through the optional normalizeConfig hook.

Bundle MCP overlays

CLI backends still do not receive OpenClaw tool calls, but a backend can opt into a generated MCP config overlay with bundleMcp: true. Current bundled behavior:
  • claude-cli: bundleMcp: true
  • codex-cli: no bundle MCP overlay
  • google-gemini-cli: no bundle MCP overlay
When bundle MCP is enabled, OpenClaw:
  • loads enabled bundle-MCP servers for the current workspace
  • merges them with any existing backend --mcp-config
  • rewrites the CLI args to pass --strict-mcp-config --mcp-config <generated-file>
If no MCP servers are enabled, OpenClaw still injects a strict empty config. That prevents background Claude CLI runs from inheriting ambient user/global MCP servers unexpectedly.

Limitations

  • No OpenClaw tools (the CLI backend never receives tool calls). Some CLIs may still run their own agent tooling. Backends with bundleMcp: true can still receive a generated MCP config overlay for their own CLI-native MCP support.
  • Streaming is backend-specific. Claude CLI forwards partial text from stream-json; other CLI backends may still be buffered until exit.
  • Structured outputs depend on the CLI’s JSON format.
  • Codex CLI sessions resume via text output (no JSONL), which is less structured than the initial --json run. OpenClaw sessions still work normally.

Troubleshooting

  • CLI not found: set command to a full path.
  • Wrong model name: use modelAliases to map provider/model → CLI model.
  • No session continuity: ensure sessionArg is set and sessionMode is not none (Codex CLI currently cannot resume with JSON output).
  • Images ignored: set imageArg (and verify CLI supports file paths).