OpenClaw Advanced Features — Live Docs Reference
Changelog
- 2026-03-20: Created from live docs.openclaw.ai + local repos/openclaw/docs.acp.md. Covers: ACP, multi-agent routing, sessions, compaction, memory, context engine, heartbeat, sandboxing (Docker/SSH/OpenShell), remote gateway, multiple gateways, llm-task, subagents, ACP agents, BTW, Lobster.
1. ACP Protocol
What It Is
openclaw acp is a Gateway-backed ACP (Agent Client Protocol) bridge over stdio. It is NOT a full ACP-native editor runtime. It forwards IDE prompts to a running OpenClaw Gateway over WebSocket, mapping ACP sessions to Gateway session keys.
ACP = Agent Client Protocol — a standard protocol for IDEs and coding agents (Zed, Codex, Claude Code) to talk to AI backends.
Core Concepts
- ACP client spawns
openclaw acp, speaks ACP messages over stdio - The bridge connects to the Gateway using auth config (or CLI flags)
- ACP
prompt→ Gatewaychat.send - Gateway streaming events → ACP streaming events
- ACP
cancel→ Gatewaychat.abortfor active run
Compatibility Matrix
| ACP area | Status | Notes |
|---|---|---|
initialize, newSession, prompt, cancel |
Implemented | Core bridge flow over stdio |
listSessions, slash commands |
Implemented | Session list maps to Gateway sessions.list |
loadSession |
Partial | Replays user/assistant text history only; no tools/system |
| Prompt content (text, resource, images) | Partial | Text/resources flattened; images → Gateway attachments |
| Session modes | Partial | session/set_mode supported; thought level, tool verbosity, reasoning |
| Session info and usage updates | Partial | Best-effort session_info_update + usage_update from cached snapshots |
| Tool streaming | Partial | tool_call/tool_call_update include raw I/O, text content |
| Per-session MCP servers | Unsupported | Bridge mode rejects; configure MCP at Gateway/agent layer |
| Client filesystem methods | Unsupported | No fs/read_text_file, fs/write_text_file |
| Client terminal methods | Unsupported | No terminal/* |
| Session plans / thought streaming | Unsupported | Emits output text and tool status only |
Usage Examples
# Basic local
openclaw acp
# Remote Gateway
openclaw acp --url wss://gateway-host:18789 --token <token>
# Remote with token from file (preferred for security)
openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
# Attach to existing session key
openclaw acp --session agent:main:main
# Attach by label
openclaw acp --session-label "support inbox"
# Reset session before first prompt
openclaw acp --session agent:main:main --reset-session
Selecting Agents
ACP routes by Gateway session key, not by agent directly.
openclaw acp --session agent:main:main
openclaw acp --session agent:design:main
openclaw acp --session agent:qa:bug-123
Default: isolated acp:<uuid> session per ACP client session.
Session Mapping
Override session via CLI:
--session <key>: direct Gateway session key--session-label <label>: resolve by label--reset-session: mint new transcript for the key
Or via ACP metadata per session:
{
"_meta": {
"sessionKey": "agent:main:main",
"sessionLabel": "support inbox",
"resetSession": true,
"requireExisting": false
}
}
Zed Editor Setup
{
"agent_servers": {
"OpenClaw ACP": {
"type": "custom",
"command": "openclaw",
"args": ["acp"],
"env": {}
}
}
}
Target specific Gateway + agent:
{
"agent_servers": {
"OpenClaw ACP": {
"type": "custom",
"command": "openclaw",
"args": ["acp", "--url", "wss://gateway-host:18789", "--token", "<token>", "--session", "agent:design:main"],
"env": {}
}
}
}
Using with acpx (Codex, Claude Code)
# One-shot request
acpx openclaw exec "Summarize the active OpenClaw session state."
# Persistent named session
acpx openclaw sessions ensure --name codex-bridge
acpx openclaw -s codex-bridge --cwd /path/to/repo "Ask my work agent for recent context."
Override ~/.acpx/config.json for consistent target:
{
"agents": {
"openclaw": {
"command": "env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 openclaw acp --url ws://127.0.0.1:18789 --token-file ~/.openclaw/gateway.token --session agent:main:main"
}
}
}
Debug Client
openclaw acp client
# Point at remote Gateway
openclaw acp client --server-args --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
Options Reference
| Flag | Description |
|---|---|
--url <url> |
Gateway WebSocket URL |
--token <token> |
Gateway auth token |
--token-file <path> |
Read token from file (preferred) |
--password <password> |
Gateway auth password |
--password-file <path> |
Read password from file |
--session <key> |
Default session key |
--session-label <label> |
Default session label to resolve |
--require-existing |
Fail if session key/label not found |
--reset-session |
Reset session key before first use |
--no-prefix-cwd |
Don't prefix prompts with working directory |
--verbose, -v |
Verbose logging to stderr |
Security note: prefer --token-file over --token (avoids process listing exposure). Env vars: OPENCLAW_GATEWAY_TOKEN, OPENCLAW_GATEWAY_PASSWORD.
Known Limitations
loadSessiondoes NOT reconstruct historic tool calls, system notices, or richer event types- Multiple ACP clients sharing the same Gateway session key → best-effort routing, not isolated
- Stop state mapping less expressive than fully ACP-native runtime
- Usage/token data is approximate only, no cost data, only emitted when Gateway marks totals as fresh
- ACP runtime child processes receive
OPENCLAW_SHELL=acp
2. Multi-Agent Patterns
What "One Agent" Means
An agent is a fully scoped brain with its own:
- Workspace (files, AGENTS.md, SOUL.md, USER.md, persona rules)
- State directory (
agentDir) for auth profiles, model registry, per-agent config - Session store (
~/.openclaw/agents/<agentId>/sessions) with chat history + routing state
Auth profiles are per-agent (~/.openclaw/agents/<agentId>/agent/auth-profiles.json). Never reuse agentDir across agents — causes auth/session collisions.
Routing Rules (How Messages Pick an Agent)
Most-specific wins, deterministic:
peermatch (exact DM/group/channel id)parentPeermatch (thread inheritance)guildId + roles(Discord role routing)guildId(Discord)teamId(Slack)accountIdmatch for a channel- channel-level match (
accountId: "*") - fallback to default agent (
agents.list[].default, else first list entry, default:main)
If multiple bindings match in the same tier, first in config order wins. Multiple match fields = AND semantics.
Key: a binding that omits accountId matches the default account only. Use accountId: "*" for channel-wide fallback.
Session Key Patterns
- Direct chats:
agent:<agentId>:<mainKey>(defaultmain) per-peer:agent:<agentId>:direct:<peerId>per-channel-peer:agent:<agentId>:<channel>:direct:<peerId>- Group chats:
agent:<agentId>:<channel>:group:<id> - Telegram topics:
...:group:<id>:topic:<threadId> - Cron:
cron:<job.id>or customsession:<custom-id>
Agent-to-Agent (sessions_send)
Off by default. Enable explicitly:
{
"tools": {
"agentToAgent": {
"enabled": false,
"allow": ["home", "work"]
}
}
}
Subagent Spawning (sessions_spawn)
// sessions_spawn tool params:
{
"task": "required",
"label": "optional",
"agentId": "optional - spawn under another agent if allowed",
"model": "optional - overrides sub-agent model",
"thinking": "optional - overrides thinking level",
"runTimeoutSeconds": "optional - abort after N seconds",
"thread": false, // when true, requests channel thread binding
"mode": "run|session", // run = one-shot, session requires thread:true
"cleanup": "delete|keep", // default: keep
"sandbox": "inherit|require" // require rejects if target not sandboxed
}
Sub-agent session keys: agent:<agentId>:subagent:<uuid>
allowAgents List
{
"agents": {
"list": [
{
"id": "main",
"subagents": {
"allowAgents": ["*"] // or explicit list: ["ops", "research"]
}
}
]
}
}
maxSpawnDepth and maxChildrenPerAgent
{
"agents": {
"defaults": {
"subagents": {
"maxSpawnDepth": 2, // default: 1 (no nesting); 2 = orchestrator pattern
"maxChildrenPerAgent": 5, // max active children per agent session
"maxConcurrent": 8, // global concurrency lane cap
"runTimeoutSeconds": 900, // default timeout (0 = none)
"archiveAfterMinutes": 60 // auto-archive after completion
}
}
}
}
Depth table:
| Depth | Session key | Role | Can spawn? |
|---|---|---|---|
| 0 | agent:<id>:main |
Main agent | Always |
| 1 | agent:<id>:subagent:<uuid> |
Sub-agent (orchestrator if depth 2) | Only if maxSpawnDepth >= 2 |
| 2 | agent:<id>:subagent:<uuid>:subagent:<uuid> |
Leaf worker | Never |
Subagent Announce
When a sub-agent finishes, it announces back to the requester. Announce payload includes:
- Result (assistant reply text, or latest toolResult if empty)
- Status:
completed successfully/failed/timed out/unknown - Compact runtime/token stats
- Delivery instruction (rewrite in normal assistant voice, don't forward raw metadata)
sessionKey,sessionId, transcript path
If sub-agent replies exactly ANNOUNCE_SKIP, nothing is posted.
Nested announce chain (depth 2):
- Depth-2 worker → announces to depth-1 orchestrator
- Depth-1 orchestrator synthesizes → announces to main
- Main delivers to user
Tool Policy by Depth
- Depth 1 (leaf, maxSpawnDepth=1): No session tools
- Depth 1 (orchestrator, maxSpawnDepth≥2): Gets
sessions_spawn,subagents,sessions_list,sessions_history - Depth 2 (leaf worker): No session tools,
sessions_spawnalways denied
Subagent Context Injection
Sub-agents only get AGENTS.md + TOOLS.md. They do NOT get: SOUL.md, IDENTITY.md, USER.md, HEARTBEAT.md, BOOTSTRAP.md.
Cascade Stop
/stopin main chat → stops all depth-1 agents + cascades to their depth-2 children/subagents kill <id>→ stops specific sub-agent + cascades/subagents kill all→ stops all sub-agents for requester + cascades
Multi-Agent Config Example (Telegram per-agent)
{
"agents": {
"list": [
{ "id": "main", "workspace": "~/.openclaw/workspace-main" },
{ "id": "alerts", "workspace": "~/.openclaw/workspace-alerts" }
]
},
"bindings": [
{ "agentId": "main", "match": { "channel": "telegram", "accountId": "default" } },
{ "agentId": "alerts", "match": { "channel": "telegram", "accountId": "alerts" } }
],
"channels": {
"telegram": {
"accounts": {
"default": { "botToken": "123456:ABC...", "dmPolicy": "pairing" },
"alerts": { "botToken": "987654:XYZ...", "dmPolicy": "allowlist", "allowFrom": ["tg:123456789"] }
}
}
}
}
3. Session Management (Advanced)
DM Scope Options
{
"session": {
"dmScope": "per-channel-peer"
}
}
Options:
main(default): all DMs share main session — single-user onlyper-peer: isolate by sender id across channelsper-channel-peer: isolate by channel + sender (recommended for multi-user inboxes)per-account-channel-peer: isolate by account + channel + sender (recommended for multi-account)
Security: if multiple users can DM your agent, use per-channel-peer or session isolation. Without it, Bob can see Alice's context.
Session Maintenance
{
"session": {
"maintenance": {
"mode": "enforce",
"pruneAfter": "30d",
"maxEntries": 500,
"rotateBytes": "10mb",
"resetArchiveRetention": "14d",
"maxDiskBytes": "1gb",
"highWaterBytes": "800mb"
}
}
}
mode: "warn" = report only; mode: "enforce" = apply cleanup:
- prune stale entries older than
pruneAfter - cap count to
maxEntries(oldest first) - archive transcript files for removed entries
- purge old
*.deleted.*and*.reset.*archives - rotate
sessions.jsonwhen exceedingrotateBytes - if
maxDiskBytesset, enforce disk budget
Session Lifecycle
- Daily reset: default 4:00 AM local time on gateway host
- Idle reset: add
idleMinutesfor sliding window - Per-type overrides:
resetByTypefordirect,group,thread - Per-channel overrides:
resetByChannel - Manual reset:
/newor/reset+ optional model alias (/new claude-opus)
Send Policy
Block delivery for specific session types:
{
"session": {
"sendPolicy": {
"rules": [
{ "action": "deny", "match": { "channel": "discord", "chatType": "group" } },
{ "action": "deny", "match": { "keyPrefix": "cron:" } },
{ "action": "deny", "match": { "rawKeyPrefix": "agent:main:discord:" } }
],
"default": "allow"
}
}
}
4. Compaction (Advanced)
What Compaction Is
Compaction summarizes older conversation into a compact summary entry and keeps recent messages intact. Persists in JSONL session history. Different from pruning (pruning trims tool results in-memory only, doesn't persist).
Compaction Config
{
"agents": {
"defaults": {
"compaction": {
"model": "openrouter/anthropic/claude-sonnet-4-6",
"identifierPolicy": "strict",
"reserveTokensFloor": 20000,
"memoryFlush": {
"enabled": true,
"softThresholdTokens": 4000,
"systemPrompt": "Session nearing compaction. Store durable memories now.",
"prompt": "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
}
}
}
}
}
identifierPolicy:
"strict"(default): preserves opaque identifiers during summarization"off": disables identifier preservation"custom": use withidentifierInstructionsfor custom text
model override: use a different (potentially more capable) model for compaction summarization than the primary model. Works with local models too:
{
"agents": {
"defaults": {
"compaction": {
"model": "ollama/llama3.1:8b"
}
}
}
}
Auto-Compaction
Triggers automatically when session nears/exceeds context window. Indicators:
🧹 Auto-compaction completein verbose mode/statusshowing🧹 Compactions: <count>
memoryFlush (Pre-Compaction Memory Ping)
When session is close to auto-compaction, OpenClaw triggers a silent agentic turn reminding model to write durable memory BEFORE compaction.
- Soft threshold: flush triggers when
contextWindow - reserveTokensFloor - softThresholdTokensis exceeded - Silent by default: prompts include
NO_REPLY - Two prompts: user prompt + system prompt append
- One flush per compaction cycle (tracked in
sessions.json) - Skipped if workspace is
workspaceAccess: "ro"or"none"
Manual Compaction
/compact
/compact Focus on decisions and open questions
Compaction vs Pruning
| Compaction | Pruning | |
|---|---|---|
| What | Summarizes older conversation | Trims old tool results |
| Persistence | Yes (in JSONL) | No (in-memory only) |
| Trigger | Context window full or manual | Before each LLM call |
Context Engine Plugin Slot
Active context engine is selected via plugins.slots.contextEngine:
{
"plugins": {
"slots": {
"contextEngine": "lossless-claw"
},
"entries": {
"lossless-claw": {
"enabled": true
}
}
}
}
Default: "legacy". Plugin engines can implement any compaction strategy (DAG summaries, vector retrieval, etc.).
ownsCompaction: true → engine owns all compaction, OpenClaw disables built-in auto-compaction.
ownsCompaction: false → Pi's built-in may still run, but engine's compact() handles /compact and overflow recovery.
5. Memory (Advanced)
Memory Architecture
Memory = plain Markdown in agent workspace. Files are source of truth. Model only "remembers" what's written to disk.
Two layers:
memory/YYYY-MM-DD.md— daily log (append-only), read today + yesterday at session startMEMORY.md— curated long-term memory, loaded in main/private session only (never in group contexts).MEMORY.mdtakes precedence over lowercasememory.md.
Memory Tools
memory_search— semantic recall over indexed snippetsmemory_get— targeted read of specific Markdown file/line range
Both degrade gracefully when file doesn't exist (return empty instead of throwing ENOENT).
When to Write Memory
- Decisions, preferences, durable facts →
MEMORY.md - Day-to-day notes and running context →
memory/YYYY-MM-DD.md - "Remember this" requests → write immediately, don't keep in RAM
Memory Backend Options
Default backend: memory-core (builtin). Disable: plugins.slots.memory = "none".
Vector memory search: builds small vector index over MEMORY.md and memory/*.md for semantic queries. Supports:
- Multiple embedding providers: OpenAI, Gemini, Voyage, Mistral, Ollama, local GGUF
- Optional QMD sidecar backend for advanced retrieval
- Post-processing: MMR diversity re-ranking, temporal decay
- Hybrid search: BM25 + vector
Full config reference: /reference/memory-config (embedding provider setup, QMD backend, hybrid search tuning, multimodal memory).
Memory Flush Config (Pre-Compaction)
{
"agents": {
"defaults": {
"compaction": {
"reserveTokensFloor": 20000,
"memoryFlush": {
"enabled": true,
"softThresholdTokens": 4000,
"systemPrompt": "Session nearing compaction. Store durable memories now.",
"prompt": "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
}
}
}
}
}
6. Heartbeat (Advanced Config)
What Heartbeat Is
Heartbeat runs periodic agent turns in the main session so the model can surface anything that needs attention without spamming you.
Default interval: 30m (or 1h for Anthropic OAuth/setup-token).
Full Config Reference
{
"agents": {
"defaults": {
"heartbeat": {
"every": "30m",
"model": "anthropic/claude-opus-4-6",
"includeReasoning": false,
"lightContext": false,
"isolatedSession": false,
"target": "last",
"to": "+15551234567",
"accountId": "ops-bot",
"prompt": "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
"ackMaxChars": 300,
"suppressToolErrorWarnings": false,
"activeHours": {
"start": "09:00",
"end": "22:00",
"timezone": "America/New_York"
},
"session": "main",
"directPolicy": "allow"
}
}
}
}
Field Reference
| Field | Default | Description |
|---|---|---|
every |
30m |
Interval (duration string; 0m disables) |
model |
(agent primary) | Optional model override for heartbeat runs |
includeReasoning |
false |
Also deliver separate Reasoning: message |
lightContext |
false |
Only inject HEARTBEAT.md from workspace bootstrap files |
isolatedSession |
false |
Each heartbeat in fresh session (no conversation history) |
target |
none |
last | none | channel id (e.g. "telegram", "whatsapp") |
to |
— | Recipient override (E.164, chat id, <chatId>:topic:<threadId>) |
accountId |
— | Account id for multi-account channels |
prompt |
(default text) | Override default prompt body (not merged) |
ackMaxChars |
300 |
Max chars allowed after HEARTBEAT_OK |
suppressToolErrorWarnings |
false |
Suppress tool error warning payloads |
activeHours |
(always) | Time window: start (HH:MM), end (HH:MM, 24:00 ok), timezone |
session |
main |
Session key for heartbeat runs |
directPolicy |
allow |
allow or block for direct/DM targets |
HEARTBEAT_OK Response Contract
- If nothing needs attention → reply
HEARTBEAT_OK HEARTBEAT_OKat start or end of reply → stripped if remaining content ≤ackMaxCharsHEARTBEAT_OKin the middle → not treated specially- For alerts → do NOT include
HEARTBEAT_OK, return only alert text - If HEARTBEAT.md is missing → heartbeat still runs
- If HEARTBEAT.md is effectively empty (only blank lines + headers) → heartbeat SKIPPED
Cost Reduction Tips
isolatedSession: true→ ~100K tokens down to ~2-5K per runlightContext: true→ limit bootstrap to HEARTBEAT.md only- Set cheaper
model(e.g.ollama/llama3.2:1b) - Keep HEARTBEAT.md small
target: "none"for internal state updates only
Scope and Precedence
channels.<channel>.accounts.<id>.heartbeat(highest)channels.<channel>.heartbeatchannels.defaults.heartbeatagents.list[].heartbeat(per-agent override)agents.defaults.heartbeat(global baseline)
If ANY agents.list[] entry has a heartbeat block, ONLY those agents run heartbeats.
activeHours Timezone Options
- Omitted or
"user": usesagents.defaults.userTimezoneor host tz "local": always host tz- IANA identifier (e.g.
"Asia/Kuala_Lumpur"): used directly - Same
start+end= zero-width window = heartbeats always skipped (bug trap)
Visibility Controls
channels:
defaults:
heartbeat:
showOk: false # Hide HEARTBEAT_OK (default)
showAlerts: true # Show alert messages (default)
useIndicator: true # Emit indicator events (default)
telegram:
heartbeat:
showOk: true # Show OK acknowledgments on Telegram
If all three (showOk, showAlerts, useIndicator) are false, OpenClaw skips heartbeat run entirely.
Manual Wake
openclaw system event --text "Check for urgent follow-ups" --mode now
openclaw system event --text "Check for urgent follow-ups" --mode next-heartbeat
Telegram Topic Routing
Use to: "<chatId>:topic:<messageThreadId>":
{
"heartbeat": {
"every": "1h",
"target": "telegram",
"to": "12345678:topic:42",
"accountId": "ops-bot"
}
}
7. Remote Gateway
Core Idea
- Gateway WebSocket binds to loopback on port 18789 (default)
- For remote use: forward loopback port over SSH, or use tailnet/VPN
Remote Mode Config
{
"gateway": {
"mode": "remote",
"remote": {
"url": "ws://127.0.0.1:18789",
"token": "your-token"
}
}
}
Or with wss:
{
"gateway": {
"remote": {
"url": "wss://gateway-host:18789",
"token": "your-token",
"tlsFingerprint": "<fingerprint>"
}
}
}
CLI Config
openclaw config set gateway.remote.url wss://gateway-host:18789
openclaw config set gateway.remote.token <token>
Or flags per command:
openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
SSH Tunnel
ssh -N -L 18789:127.0.0.1:18789 user@host
With tunnel up: openclaw health, openclaw status --deep, openclaw gateway call all reach remote.
Credential Precedence
Local mode:
- token:
OPENCLAW_GATEWAY_TOKEN→gateway.auth.token→gateway.remote.token(fallback only if local unset) - password:
OPENCLAW_GATEWAY_PASSWORD→gateway.auth.password→gateway.remote.password
Remote mode:
- token:
gateway.remote.token→OPENCLAW_GATEWAY_TOKEN→gateway.auth.token - password:
OPENCLAW_GATEWAY_PASSWORD→gateway.remote.password→gateway.auth.password
Important: CLI --url overrides NEVER reuse implicit config/env credentials. Pass --token or --password explicitly.
Tailscale Integration
{
"gateway": {
"auth": {
"allowTailscale": true
}
}
}
Authenticates Control UI/WebSocket via Tailscale identity headers. HTTP API endpoints still require token/password auth.
Security Rules
- Keep Gateway loopback-only unless you need broader bind
- Plaintext
ws://loopback-only by default; for private networks:OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 - Non-loopback binds (
lan/tailnet/custom) MUST use auth tokens/passwords gateway.remote.token/.passwordare CLIENT credential sources, not server auth configgateway.remote.tlsFingerprintpins remote TLS cert forwss://
Bootstrap Token (Node Pairing)
Used for initial node pairing (iOS/Android companion apps). Separate from gateway auth token. See node-connect skill for details.
Multiple Gateways on Same Host
Isolation checklist (all required):
OPENCLAW_CONFIG_PATH— per-instance configOPENCLAW_STATE_DIR— per-instance sessions/creds/cachesagents.defaults.workspace— per-instance workspace rootgateway.port— unique per instance (use--port)- Derived ports (browser: base+2, CDP: auto-allocate) must not overlap
Recommended approach: profiles
openclaw --profile main gateway --port 18789
openclaw --profile rescue gateway --port 19001
Port spacing: leave at least 20 ports between base ports.
8. OpenShell Sandbox
What It Is
OpenShell is a managed sandbox backend for OpenClaw. Instead of local Docker containers, OpenClaw delegates sandbox lifecycle to the openshell CLI, which provisions remote environments with SSH-based command execution.
Reuses the same core SSH transport as the generic SSH backend, adds:
- OpenShell-specific lifecycle (
sandbox create/get/delete,sandbox ssh-config) - Optional
mirrorworkspace mode (bidirectional sync)
Sandbox Backend Comparison
| Docker | SSH | OpenShell | |
|---|---|---|---|
| Where it runs | Local container | Any SSH host | OpenShell managed |
| Setup | scripts/sandbox-setup.sh |
SSH key + target | OpenShell plugin + CLI |
| Workspace model | Bind-mount or copy | Remote-canonical (seed once) | mirror or remote |
| Network control | docker.network |
Depends on remote | Depends on OpenShell |
| Browser sandbox | Supported | Not supported | Not supported yet |
| Best for | Local dev, full isolation | Offloading to remote | Managed remote + optional two-way sync |
Enable OpenShell
{
"agents": {
"defaults": {
"sandbox": {
"mode": "all",
"backend": "openshell",
"scope": "session",
"workspaceAccess": "rw"
}
}
},
"plugins": {
"entries": {
"openshell": {
"enabled": true,
"config": {
"from": "openclaw",
"mode": "remote",
"remoteWorkspaceDir": "/sandbox",
"remoteAgentWorkspaceDir": "/agent"
}
}
}
}
}
Workspace Modes
mirror (local stays canonical)
Behavior:
- Before
exec: OpenClaw syncs local workspace INTO OpenShell sandbox - After
exec: OpenClaw syncs remote workspace BACK to local workspace - File tools operate through sandbox bridge
- Local workspace = source of truth between turns
Use when:
- You edit files locally outside OpenClaw and want changes visible in sandbox
- You want OpenShell to behave like Docker backend
- You want host workspace to reflect sandbox writes after each turn
Tradeoff: extra sync cost before and after exec.
remote (OpenShell workspace is canonical)
Behavior:
- First sandbox creation: OpenClaw seeds remote workspace from local workspace ONCE
- After that:
exec,read,write,edit,apply_patchoperate DIRECTLY against remote OpenShell - OpenClaw does NOT sync remote changes back
Use when:
- Sandbox should live primarily on the remote side
- Lower per-turn sync overhead
- Don't want host-local edits to silently overwrite remote state
Important: after host edits, use openclaw sandbox recreate to re-seed.
OpenShell Config Reference
All config under plugins.entries.openshell.config:
| Key | Type | Default | Description |
|---|---|---|---|
mode |
"mirror" or "remote" |
"mirror" |
Workspace sync mode |
command |
string | "openshell" |
Path/name of openshell CLI |
from |
string | "openclaw" |
Sandbox source for first-time create |
gateway |
string | — | OpenShell gateway name (--gateway) |
gatewayEndpoint |
string | — | OpenShell gateway endpoint URL |
policy |
string | — | OpenShell policy ID for sandbox creation |
providers |
string[] | [] |
Provider names to attach when sandbox created |
gpu |
boolean | false |
Request GPU resources |
autoProviders |
boolean | true |
Pass --auto-providers during create |
remoteWorkspaceDir |
string | "/sandbox" |
Primary writable workspace inside sandbox |
remoteAgentWorkspaceDir |
string | "/agent" |
Agent workspace mount (read-only access) |
timeoutSeconds |
number | 120 |
Timeout for openshell CLI operations |
Sandbox Scope Options
"session"(default): one container per session"agent": one container per agent"shared": one container shared by all sandboxed sessions
Sandbox Mode Options
"off": no sandboxing"non-main": sandbox only non-main sessions (group/channel)"all": every session sandboxed
workspaceAccess Options
"none"(default): tools see sandbox workspace under~/.openclaw/sandboxes"ro": mounts agent workspace read-only at/agent"rw": mounts agent workspace read/write at/workspace
Lifecycle Commands
openclaw sandbox list
openclaw sandbox explain
openclaw sandbox recreate --all
Recreate after changing: backend, from, mode, policy.
Current OpenShell Limitations
- Sandbox browser NOT supported
sandbox.docker.bindsdoes NOT apply- Docker-specific
sandbox.docker.*knobs only for Docker backend
9. LLM-Task Tool
What It Does
llm-task is an optional plugin tool that runs a JSON-only LLM task and returns structured output, optionally validated against JSON Schema.
Use case: workflow engines like Lobster — add a single LLM step without custom OpenClaw code. Keeps workflow deterministic while allowing classification/summarization/drafting.
Enable
{
"plugins": {
"entries": {
"llm-task": { "enabled": true }
}
},
"agents": {
"list": [
{
"id": "main",
"tools": { "allow": ["llm-task"] }
}
]
}
}
Note: registered with optional: true, so must be explicitly allowlisted.
Optional Config
{
"plugins": {
"entries": {
"llm-task": {
"enabled": true,
"config": {
"defaultProvider": "openai-codex",
"defaultModel": "gpt-5.4",
"defaultAuthProfileId": "main",
"allowedModels": ["openai-codex/gpt-5.4"],
"maxTokens": 800,
"timeoutMs": 30000
}
}
}
}
}
allowedModels: allowlist of provider/model strings. If set, requests outside list are rejected.
Tool Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt |
string | Yes | The task prompt |
input |
any | No | Input data passed to model |
schema |
object | No | JSON Schema for output validation |
provider |
string | No | Override provider |
model |
string | No | Override model |
thinking |
string | No | Reasoning preset: "low", "medium" |
authProfileId |
string | No | Override auth profile |
temperature |
number | No | Override temperature |
maxTokens |
number | No | Override max tokens |
timeoutMs |
number | No | Override timeout |
Output
Returns details.json containing parsed JSON, validated against schema when provided.
Example: Lobster Workflow Step
openclaw.invoke --tool llm-task --action json --args-json '{
"prompt": "Given the input email, return intent and draft.",
"thinking": "low",
"input": {
"subject": "Hello",
"body": "Can you help?"
},
"schema": {
"type": "object",
"properties": {
"intent": { "type": "string" },
"draft": { "type": "string" }
},
"required": ["intent", "draft"],
"additionalProperties": false
}
}'
Safety Notes
- JSON-only: model instructed to output only JSON (no code fences, no commentary)
- No tools exposed to model for this run
- Treat output as untrusted unless validated with
schema - Put approvals BEFORE any side-effecting step (send, post, exec)
When to Use vs Regular Agent Turn
- Use llm-task: when you need structured JSON output in a deterministic pipeline (Lobster workflow, data transformation, classification)
- Use regular agent turn: when you need tools, tool loops, file access, or conversational flow
10. BTW Side Questions
What It Does
/btw lets you ask a quick side question about the current session without polluting conversation history.
Mental model:
- Same session context (as background only)
- Separate one-shot side query
- No tool calls
- No future context pollution
- No transcript persistence (ephemeral)
How to Use
/btw what changed?
/btw what file are we editing?
/btw what does this error mean?
/btw summarize the current task in one sentence
/btw what is 17 * 19?
What It Does NOT Do
- Does NOT create a new durable session
- Does NOT continue unfinished main task
- Does NOT run tools or agent tool loops
- Does NOT write BTW question/answer to transcript history
- Does NOT appear in
chat.history - Does NOT survive a reload
How Context Works
If main run is currently active, OpenClaw snapshots current message state and includes in-flight main prompt as background context, while explicitly telling model:
- Answer only the side question
- Do not resume or complete the unfinished main task
- Do not emit tool calls or pseudo-tool calls
Delivery Model
BTW uses chat.side_result event (NOT chat event). This separation prevents clients from treating it as regular conversation history.
Platform behavior:
- TUI: rendered inline, visibly distinct, dismissible with
EnterorEsc, not replayed on reload - External channels (Telegram, WhatsApp, Discord): delivered as clearly labeled one-off reply
- Control UI/web: Gateway emits correctly as
chat.side_result; client-side rendering still pending in some UIs
When to Use BTW vs Normal
- BTW: quick clarification, factual side answer, temporary answer that should NOT become future context
- Normal message: when you WANT the answer to become part of session's future working context
11. Lobster (Deterministic Workflow Pipelines)
What It Is
Lobster is a workflow shell that lets OpenClaw run multi-step tool sequences as a single, deterministic operation with explicit approval checkpoints.
Key value props:
- One call instead of many: one Lobster tool call → structured result (vs many back-and-forth)
- Approvals built in: side effects halt workflow until explicitly approved
- Resumable: halted workflows return a token; approve and resume without re-running
How It Works
OpenClaw launches local lobster CLI in tool mode and parses a JSON envelope from stdout. If pipeline pauses for approval, tool returns resumeToken to continue later.
Enable Lobster
Lobster is an optional plugin tool (not enabled by default).
Recommended (additive, safe — doesn't override core tools):
{
"tools": {
"alsoAllow": ["lobster"]
}
}
Per-agent:
{
"agents": {
"list": [
{
"id": "main",
"tools": {
"alsoAllow": ["lobster"]
}
}
]
}
}
Avoid tools.allow: ["lobster"] unless you want restrictive allowlist mode.
Tool Parameters
run action
{
"action": "run",
"pipeline": "gog.gmail.search --query 'newer_than:1d' | email.triage",
"cwd": "workspace",
"timeoutMs": 30000,
"maxStdoutBytes": 512000
}
Run workflow file with args:
{
"action": "run",
"pipeline": "/path/to/inbox-triage.lobster",
"argsJson": "{\"tag\":\"family\"}"
}
resume action
{
"action": "resume",
"token": "<resumeToken>",
"approve": true
}
Set approve: false to cancel/deny.
Optional params
| Param | Default | Description |
|---|---|---|
cwd |
— | Relative working directory (must stay within process cwd) |
timeoutMs |
20000 |
Kill subprocess if exceeded |
maxStdoutBytes |
512000 |
Kill subprocess if stdout exceeds this |
argsJson |
— | JSON string passed to lobster run --args-json (workflow files only) |
Output Envelope
Returns JSON with one of three statuses:
{
"ok": true,
"status": "needs_approval",
"output": [{ "summary": "5 need replies, 2 need action" }],
"requiresApproval": {
"type": "approval_request",
"prompt": "Send 2 draft replies?",
"items": [],
"resumeToken": "..."
}
}
Status values:
ok→ finished successfullyneeds_approval→ paused;requiresApproval.resumeTokenneeded to resumecancelled→ explicitly denied or cancelled
Workflow Files (.lobster)
YAML/JSON with name, args, steps, env, condition, approval fields:
name: inbox-triage
args:
tag:
default: "family"
steps:
- id: collect
command: inbox list --json
- id: categorize
command: inbox categorize --json
stdin: $collect.stdout
- id: approve
command: inbox apply --approve
stdin: $categorize.stdout
approval: required
- id: execute
command: inbox apply --execute
stdin: $categorize.stdout
condition: $approve.approved
Notes:
stdin: $step.stdoutandstdin: $step.jsonpass prior step outputcondition(orwhen) can gate steps on$step.approved
Inline Pipeline Syntax
# Map items into tool calls
gog.gmail.search --query 'newer_than:1d' \
| openclaw.invoke --tool message --action send --each --item-key message --args-json '{"provider":"telegram","to":"..."}'
# Chain small CLIs
inbox list --json | inbox categorize --json | inbox apply --json
Approval Gates Pattern
- Pipeline runs → hits
approvestep - OpenClaw tool returns
needs_approvalwithresumeToken - User reviews, sends approve/reject
- OpenClaw calls
resumewithtoken+approve: true/false - Pipeline continues (or cancels)
Cron + Heartbeat Integration
Lobster pairs well with cron and heartbeat for automated pipelines:
- Cron triggers a session turn at scheduled time
- Agent calls Lobster tool with pipeline
- If needs approval → pauses, notifies user
- User approves → pipeline resumes
Example: daily email triage at 9am via cron → Lobster email.triage → pause for approval → resume on approval.
Lobster + LLM-Task
For pipelines needing structured LLM steps within Lobster, use llm-task:
openclaw.invoke --tool llm-task --action json --args-json '{
"prompt": "Classify this email as urgent/routine/spam.",
"input": { "subject": "...", "body": "..." },
"schema": {
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["urgent", "routine", "spam"] }
},
"required": ["category"]
}
}'
Safety Properties
- Local subprocess only: no network calls from plugin itself
- No secrets management: calls OpenClaw tools that handle OAuth
- Sandbox-aware: disabled when tool context is sandboxed
- Hardened: fixed executable name (
lobster) on PATH; timeouts and output caps enforced
…(truncated)