OpenClaw Agent CLI: Gateway Turns and Exec Runs
Reference for openclaw agent turns via Gateway and isolated agent exec runs. Useful for developers automating CI or coding tasks with headless agent execution.
Read this when
- You want to run one agent turn from scripts (optionally deliver reply)
- You want a strict, ephemeral one-shot agent run for CI
openclaw agent
The Gateway can execute a single agent turn through this command. The explicit --local flag represents the only path for embedded execution.
You must supply at least one session selector: --to, --session-key, --session-id, or --agent.
When a turn completes successfully, the process terminates with 0. If an error, timeout, or cancellation occurs, the exit code is 1, provided any text or JSON output has already been written. Should a SIGINT or SIGTERM be received, the process instead preserves the signal-specific exit status detailed below.
See also: Agent send tool
agent exec
An embedded agent turn that bypasses the Gateway entirely is initiated with openclaw agent exec. Because it handles setup, cleanup, output projection, and process status on its own, it serves as the preferred headless entry point for CI and coding automation.
openclaw agent exec "Run the focused tests and fix failures"
openclaw agent exec --message-file task.md --cwd ./repo
cat task.md | openclaw agent exec --message-file - --json
A temporary state directory is created and subsequently removed by default, and the command operates against your standard OpenClaw config, meaning configured providers, credentials, and agentRuntime harness selection behave identically to other contexts. The process working directory is the default for --cwd, which is used both as the agent workspace and the tool working directory.
Configuration is assembled entirely in memory across three layers: exec builds the run config and exposes it as the runtime config for this process, without writing anything to disk. Exec defaults fill in only where your config leaves a setting unspecified: workspace bootstrap files are skipped, the agent sandbox is off, the coding tool profile is chosen, filesystem tools are limited to --cwd, and exec operates under the full execution policy required by a headless turn. Any value your config sets overrides these defaults, so a configured sandbox, shell env, or tool profile is never reduced, and exec host routing respects the sandbox when your config enables one. The invocation itself has final authority: the run is limited to --cwd and never bootstraps.
To keep sessions and other run state, pass --state-dir <dir>. The directory must pre-exist; the command will not create or delete it. Exclusive ownership of a retained state directory is mandatory: exec will not start if a Gateway or another embedded writer holds it, and it then keeps the state lock for the entire run. For isolated temporary state, leave out --state-dir, or shut down the Gateway beforehand with openclaw gateway stop.
When exec uses the ambient or a pinned config, installed plugins still resolve from the operator's usual plugin roots, while sessions and other run state rely on the ephemeral directory. In these modes, --state-dir governs run state only; configured providers, channels, or harnesses supplied by installed plugins do not require it.
For reproducible runs, pin the config rather than inheriting it. --config <path> executes against precisely that config file, loaded via the standard loader so JSON5 syntax and $include resolve relative to it; a missing or invalid file causes the run to fail instead of reverting to defaults, and the same applies to an ambient config that exists but cannot be parsed. The ambient config is completely ignored by --isolated, which relies solely on the exec defaults above. Both options suit CI, where inheriting operator state would make runs machine-dependent.
Stored credentials are used by default, so a folder-scoped run accesses the same logins as the rest of the CLI. To limit the run to provider keys already in the process environment, pass --auth-env-only. This mode loads no config whatsoever, and combining it with --config is rejected rather than quietly ignored, because a config supplies provider credentials through multiple surfaces at once: inline keys and secret headers, an env block, and login-shell import. OpenClaw auth profiles and external Codex, Claude, or other CLI credential stores are also skipped. Provider auth variables stay available for model authentication but are excluded from agent-launched host commands.
Repeatable flags let you choose a primary model and an ordered fallback chain:
openclaw agent exec "Implement the change" \
--model openai/gpt-5.6-sol \
--fallback anthropic/claude-sonnet-4-6 \
--fallback google/gemini-3.1-pro-preview
For this command only, explicit --fallback values remain in effect alongside explicit --model. Other agent entry points keep their existing rule that a user-selected model disables configured fallbacks.
When comparing local or smaller models, explicitly select the one-shot tool surface:
openclaw agent exec "Inspect this repository" \
--model ollama/qwen3.5:9b \
--code-mode code \
--local-model-lean \
--json
Code Mode is disabled by --code-mode direct, auto relies on model capability metadata, and code forces the generic Code Mode surface for tool-capable runs. High-latency and channel-dependent tools are removed by --local-model-lean, which also enables the bounded Tool Search defaults for the isolated run.
For agent exec, the timeout defaults to 600 seconds; the existing embedded agent --local default is unchanged. A successful run exits with 0, any model or result error exits with 1, and a timeout exits with 2. Failure encompasses meta.error, aborted runs, exhausted model fallbacks, an error stop reason, and any error payload.
Plain output sends only the final assistant text to stdout. Diagnostics go to stderr. --json reserves stdout for this stable envelope:
{
"ok": true,
"status": "ok",
"final": "The focused tests pass.",
"payloads": [{ "text": "The focused tests pass." }],
"usage": { "input": 120, "output": 8, "total": 128 },
"costUsd": 0.0021,
"codeModeEngaged": false,
"assistantTurns": 2,
"bridgeCalls": { "search": 1, "describe": 0, "call": 3 },
"toolSummary": { "calls": 2, "tools": ["read", "write"], "totalToolTimeMs": 48 },
"model": "gpt-5.6-sol",
"provider": "openai",
"sessionId": "019..."
}
status can be ok, error, or timeout. When unavailable, usage is omitted. Failed envelopes include error: { message, kind }; model and provider are null when failure occurs before model selection.
Run-stat fields are additive and may be absent:
costUsd: the total of per-call USD costs as recorded, which preserves request pricing tiers and retry-model prices, and covers cache reads and writes. If per-call costs are incomplete, only flat-price estimates remain available; tiered estimates are left out instead of combining usage into a single request price. This field is absent when cost data is missing.codeModeEngaged:trueappears only when code mode genuinely controlled the model tool surface for that run.tools.codeMode.enabled=trueby itself does not guarantee engagement, and harnesses that manage their own native tool surface always readfalse, since OpenClaw code mode never takes ownership of their tools.assistantTurns: completed assistant/provider round trips within the run; omitted when none finished.bridgeCalls: counts of inner tool-search/code-mode bridge calls (search/describe/call). These remain hidden from the provider; outer tool calls are recorded inmeta.toolSummary.callsof the complete run metadata.toolSummary: the outer model-visible tool-call count, tool names, failures, and total tool time derived from the embedded run.
The agent run-stat fields are exposed at meta.agentMeta in the openclaw agent --json response; the outer tool summary stays at meta.toolSummary.
Code Mode model matrix
With a source checkout in hand, execute the bounded evaluation matrix against any explicit model reference:
pnpm qa:code-mode-models -- --model ollama/qwen3.5:9b
Run --model again to compare models, or apply --mode, --task, and --repetitions to trim the default direct/automatic/forced Code Mode matrix. Every cell runs an isolated agent exec task and captures model/provider identity, timing, result status, failure class, outer tool calls, Code Mode bridge calls, and verified output/effects.
The output directory holds canonical QA Lab qa-evidence.json. summary.json and results.jsonl serve as supporting aggregate and per-cell artifacts; manifest.json documents the requested matrix and source identity.
This evidence is for evaluation only, not a CI or release gate. Outcomes do not alter model capabilities, runtime routing, fallback, or repair policy.
agent exec options
[message]: prompt text given positionally--message-file <path>: load a UTF-8 prompt from a file;-pulls from stdin--cwd <dir>: assign both the agent workspace and the tool working directory--state-dir <dir>: reuse an existing state directory without wiping it--config <path>: run against this config file instead of the ambient one (JSON5 and$includeare accepted)--isolated: disregard the ambient config and rely solely on exec defaults--model <provider/model>: explicit primary model--code-mode <mode>: pickdirect,auto, or forcedcodetool mode--local-model-lean: apply the reduced local-model tool surface--thinking <level>: thinking level for a single run--fallback <provider/model>: ordered fallback model; repeatable and demands--model--auth-env-only: rely exclusively on environment provider keys; skips stored credentials, external CLI credentials, and config entirely--no-auth-env-only: permit stored and external CLI credentials (the default)--timeout <seconds>: deadline measured in seconds (default600;0turns it off)--json: output the stable JSON envelope
Options
-m, --message <text>: the payload of the message--message-file <path>: load the message payload from a UTF-8 encoded file-t, --to <dest>: the recipient that determines the session key--session-key <key>: a manually specified session key for routing--session-id <id>: an explicit session identifier--agent <id>: the agent identifier, which supersedes routing bindings--model <id>: model selection for this execution (eitherprovider/modelor a model id)--thinking <level>: the agent's thinking level (off,minimal,low,medium,high, as well as provider-defined custom levels likexhigh,adaptive, ormax)--verbose <on|off>: store the verbose setting for the session--channel <channel>: the delivery channel; leave empty to use the primary session channel--reply-to <target>: an override for the delivery target--reply-channel <channel>: an override for the delivery channel--reply-account <id>: an override for the delivery account--local: execute the embedded agent directly (following plugin registry preload)--deliver: return the response to the chosen channel or target--timeout <seconds>: set a custom agent-turn deadline for this command (defaults to 600, oragents.defaults.timeoutSeconds);0turns off the overall deadline. The 600-second default applies only to this CLI command, not to standard Gateway turns, which default to 48 hours.--json: output as JSON
Examples
openclaw agent --to +15555550123 --message "status update" --deliver
openclaw agent --agent ops --message "Summarize logs"
openclaw agent --agent ops --message-file ./task.md
openclaw agent --agent ops --model openai/gpt-5.4 --message "Summarize logs"
openclaw agent --session-key agent:ops:incident-42 --message "Summarize status"
openclaw agent --agent ops --session-key incident-42 --message "Summarize status"
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
openclaw agent --to +15555550123 --message "Trace logs" --verbose on --json
openclaw agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
openclaw agent --agent ops --message "Run locally" --local
Notes
- Supply either
--messageor--message-file, but not both. Using--message-fileremoves a leading UTF-8 BOM while keeping multiline content intact, and it only accepts files that are valid UTF-8. Any file exceeding 4 MiB gets rejected before dispatch. - The channel slash-command dispatcher is not invoked by
--message. The only exception is for recognized$skill-namereferences and a leading/skill-name [input]: OpenClaw converts these into model instructions so the skill is read prior to acting. Other messages starting with a slash behave like a normal agent turn, while/compactgets refused with a reference toopenclaw sessions compact <key>. - A
--localrun is one-shot in nature: any bundled MCP loopback resources and warm Claude stdio sessions opened for that run are cleaned up after the reply, so scripted calls leave no local child processes behind. Gateway-backed runs instead keep Gateway-owned MCP loopback resources alive under the running Gateway process. - Exclusive control of the configured state directory is required by
--local. It will not start if a Gateway or anotheragent --localrun currently owns that directory, and it then keeps the same state lock throughout the entire embedded turn. To use the active Gateway, run without--local; alternatively, shut it down first withopenclaw gateway stop. - When restart recovery is still pending, standalone embedded execution via
--localwill not reuse an existing main session. Route the turn through a healthy Gateway, or reset it there using/newor/reset; an independent embedded process cannot safely coordinate that recovery owner with the Gateway scanner. - Combining
--agent,--channel, and--tomakes session routing follow the channel's canonical recipient andsession.dmScope. Channels that have a stable outbound-only recipient identity use a provider-owned session that is separate from the agent's main session.--reply-channeland--reply-accountonly influence delivery. - An explicit session key is chosen with
--session-key. Keys prefixed with an agent must useagent:<agent-id>:<session-key>, and when both are present,--agenthas to match the key's agent id. Bare non-sentinel keys are scoped to--agentif provided, otherwise they fall back to the configured default agent; for instance,--agent ops --session-key incident-42directs traffic toagent:ops:incident-42. The literal keysglobalandunknownremain unscoped only when no--agentis given. - stdout is reserved by
--jsonfor the JSON response; Gateway, plugin, and--localdiagnostics are written to stderr, allowing scripts to parse stdout directly. - Once transient handshake retries are used up, a Gateway timeout or closed connection causes the command to fail; the CLI never silently reruns the turn embedded. Transport loss is ambiguous, since the Gateway may have accepted and may still complete the turn, so the stderr hint advises checking
openclaw gateway statusand the session transcript before retrying or rerunning with--local, to prevent the turn from executing twice. - A waiting Gateway-backed request is interrupted by
SIGTERM/SIGINT; if the Gateway already accepted the run, the CLI also sendschat.abortfor that run id before exiting.--localruns receive the same signal but do not transmitchat.abort. A launcher child that terminates from the first forwardedSIGINTorSIGTERMexits with status 130 or 143, respectively. If the internal run-dedup key already has an active run for this session, the response reportsstatus: "in_flight"and the non-JSON CLI prints a stderr diagnostic rather than an empty reply. For external cron/systemd wrappers, keep a hard-kill backstop such astimeout -k 60 600 openclaw agent ...so the supervisor can reap the process if shutdown cannot drain. - When this command triggers
models.jsonregeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names,secretref-env:ENV_VAR_NAME, orsecretref-managed), never resolved secret plaintext. Marker writes come from the active source config snapshot, not from resolved runtime secret values.
JSON failures
Failures keep the CLI error envelope: ok: false and
error.type: "cli_error". When the Gateway returned a run ID, the envelope also
includes top-level runId and origin: "gateway". This includes cached final
errors without a fresh acceptance response, and a timeout or lost connection
after acceptance.
origin identifies the run's Gateway ownership; it does not prove the run failed
or stopped. After transport loss, check the session transcript before retrying.
Omitted provenance means the CLI observed no Gateway run identity, not that no
run happened. Local errors and rejections without a Gateway run ID omit these
fields. A locally generated idempotency key alone is not Gateway provenance.
JSON delivery status
With --json --deliver, the CLI JSON response includes top-level deliveryStatus so scripts can distinguish delivered, suppressed, partial, and failed sends:
{
"payloads": [{ "text": "Report ready", "mediaUrl": null }],
"meta": { "durationMs": 1200 },
"deliveryStatus": {
"requested": true,
"attempted": true,
"status": "sent",
"succeeded": true,
"resultCount": 1
}
}
Gateway-backed CLI responses also preserve the raw Gateway result shape at result.deliveryStatus.
deliveryStatus.status is one of:
| Status | Meaning |
|---|---|
sent | The delivery finished successfully. |
suppressed | The delivery was deliberately skipped, for instance a message-sending hook blocked it or nothing visible came out of it. Final state, no further attempts. |
partial_failed | One or more payloads went out before a subsequent payload encountered an error. |
failed | No durable send ever succeeded, or the preflight check for delivery failed. |
Fields shared across all entries:
requested: set totruewhenever the object exists.attempted: becomestrueafter the durable send path has executed;falseappears for preflight failures or when no payloads were visible.succeeded: one oftrue,false, or"partial";"partial"is tied tostatus: "partial_failed".reason: a lowercase snake-case reason coming from either durable delivery or preflight validation. Recognized values arecancelled_by_message_sending_hook,no_visible_payload,no_visible_result,channel_resolved_to_internal,unknown_channel,invalid_delivery_target, andno_delivery_target; a failed durable send can additionally report the stage that failed. Since the list may grow, treat unrecognized values as opaque.resultCount: the count of channel send results, provided that data is present.sentBeforeError:trueapplies when a partial failure managed to send at least one payload before hitting an error.error:trueis used for sends that failed or only partially failed.errorMessage: included only when an underlying delivery error message was recorded. Preflight failures come witherror/reasonbut never witherrorMessage.payloadOutcomes: optional results per payload, carryingindex,status,reason,resultCount,error,stage,sentBeforeError, or hook metadata when that information exists.