#What's the correct way to set up a containerized second agent under an existing gateway?

1 messages · Page 1 of 1 (latest)

outer stoneBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

hollow zincBOT
#

Yes — the right pattern is:

Keep Gateway on bare metal, and run only the second agent in a Docker sandbox.

On macOS that works fine (Docker Desktop required).

Minimal setup

  1. Install/start Docker Desktop on the Mac.
  2. Build the sandbox image (from an OpenClaw source checkout):
scripts/sandbox-setup.sh
  1. Add a second agent in ~/.openclaw/openclaw.json with sandbox enabled:
#
{
agents: {
list: [
{
id: "main",
default: true,
workspace: "~/.openclaw/workspace",
sandbox: { mode: "off" }
},
{
id: "worker",
workspace: "~/.openclaw/workspace-worker",
sandbox: {
mode: "all",
scope: "agent" // one container for this agent
#
}
}
]
},

// route selected traffic to the containerized agent
bindings: [
{ agentId: "worker", match: { channel: "discord", accountId: "*" } }
]
}
  1. Restart gateway:
openclaw gateway restart
#

If you want, I can give you a Discord-thread-specific binding snippet so only this thread uses the containerized agent while everything else stays on main.

#

Perfect — then you want one-agent-per-human, with that user’s agent sandboxed.

That gives you both:

  1. hard context separation (workspace + sessions + auth), and
  2. containerized execution for that agent.

What to do

  • Create a dedicated agent for that human (openclaw agents add <id>).
  • Give it its own workspace (and optionally explicit agentDir).
  • Set that agent to sandbox.mode: "all" + scope: "agent".
  • Route that human’s traffic to that agent via bindings.

Why this works

Each agent already has isolated state:

  • workspace files (AGENTS/SOUL/MEMORY etc.)
#
  • session store (~/.openclaw/agents/<agentId>/sessions)
  • auth profiles (.../agent/auth-profiles.json)

So context won’t leak across agents unless you intentionally bridge it.

Config shape (example)

{
agents: {
list: [
{ id: "main", default: true, workspace: "~/.openclaw/workspace", sandbox: { mode: "off" } },
{
id: "user2",
workspace: "~/.openclaw/workspace-user2",
sandbox: { mode: "all", scope: "agent" }
#
}
]
},
bindings: [
// bind this user's traffic to user2 (account/peer/channel match as needed)
{ agentId: "user2", match: { channel: "discord", accountId: "*" } }
]
}

Then openclaw gateway restart and verify with:

openclaw agents list --bindings
docker ps --filter "name=openclaw-sbx-"
#

Yes — you can do that, but that becomes a separate OpenClaw instance/gateway, not a second agent under your current gateway.

  • Same gateway + second agent (recommended for your case): easier ops, still isolated context per agent.
  • New macOS user + fresh setup: strongest isolation (OS-level), but more overhead.

If you go the new-user route, treat it as “run another gateway”:

  1. Separate state/workspace/config (new user gives this naturally).
  2. Different port (avoid 18789 collision).
  3. Separate channel credentials/accounts where needed.
  4. Separate daemon/service under that user.

So: valid approach, just heavier.
If you only need separate human context + containerized execution, multi-agent + per-agent sandbox is the cleaner path.

Docs: https://docs.openclaw.ai/concepts/multi-agent · https://docs.openclaw.ai/gateway/sandboxing · https://docs.openclaw.ai/gateway/multiple-gateways

#

Great question. In one gateway + multiple agents, isolation is mixed:

  • openclaw.json → shared (one gateway-wide config file).
  • Cron job store/logs (~/.openclaw/cron/jobs.json, ~/.openclaw/cron/runs/...) → shared.
  • But each job can be pinned to an agentId, so execution context can still be agent-specific.
  • Auth profiles (~/.openclaw/agents/<agentId>/agent/auth-profiles.json) → isolated per agent.
  • Sessions/transcripts (~/.openclaw/agents/<agentId>/sessions/...) → isolated per agent.
  • Workspace/memory files (per-agent workspace path) → isolated per agent.

So: context/auth/session isolation is solid per agent, but scheduler/config files are gateway-global.

If you need everything isolated (including config + cron store), that’s when you run separate gateways (profile or OS-user split).

Refs: https://docs.openclaw.ai/concepts/multi-agent · https://docs.openclaw.ai/automation/cron-jobs · https://docs.openclaw.ai/help/faq#where-does-openclaw-store-its-data

hollow zincBOT
#

Yep — this is most likely one of two things on the second macOS user:

  1. global npm prefix not writable (common with separate user + Homebrew Node), and/or
  2. Node 25 edge-case (OpenClaw recommends Node 24).

Quick triage first (please paste output):

openclaw --version || true
node -v
npm -v
npm prefix -g
ls -ld "$(npm prefix -g)" "$(npm prefix -g)/lib" "$(npm prefix -g)/bin" 2>/dev/null

Fast fix path I’d use

A) Put npm globals in user-writable dir

#
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
exec zsh -l

B) Use Node 24 (recommended)

brew install node@24
echo 'export PATH="/opt/homebrew/opt/node@24/bin:$PATH"' >> ~/.zprofile
exec zsh -l
node -v

C) Install with pnpm (preferred)

#

You’re not wrong — macOS multi-OS-user + Homebrew is exactly where this gets annoying.

The easiest fix: don’t split by macOS user.
Run both gateways under one macOS account using OpenClaw profiles (this is the intended multi-gateway path).

Why profiles are better here

Each profile gets isolated:

  • config
  • state dir
  • sessions
  • auth
  • cron store
  • workspace

So you still get strong separation, without Homebrew permission hell.

Suggested layout

#
# existing gateway (main)
openclaw --profile main status

# second isolated gateway
openclaw --profile user2 onboard
# pick a DIFFERENT base port in onboarding (e.g. 19789; keep ~20 port gap)

openclaw --profile user2 gateway install
openclaw --profile user2 gateway start

# sanity checks
openclaw --profile main status
openclaw --profile user2 status

If you insist on separate macOS users, I’d avoid Homebrew there and use a user-local Node toolchain (fnm/nvm + user-local npm/pnpm prefix), but profiles are much cleaner.

#

You’re absolutely right about one gateway + multi-agent: some things are shared (notably gateway-level config/scheduler).

But if you run multiple gateways (with profiles or separate container/host), you do not share those files.

For profile-based multi-gateway, each profile gets its own state tree (default):

  • ~/.openclaw-main/...
  • ~/.openclaw-user2/...

That includes separate:

  • openclaw.json
  • cron/jobs.json + cron logs
  • agents/<agentId>/agent/auth-profiles.json
  • sessions/transcripts
  • channel creds

Shared is basically just:

  • installed binary/runtime (Node/OpenClaw install)
#

Yes — absolutely.
If you want hard isolation, running the second gateway in its own container is the cleanest route.

For true isolation, make sure the second gateway has:

  • its own state dir (/home/node/.openclaw in that container)
  • its own workspace
  • its own port (e.g. host 19789 -> container 18789)
  • its own auth/channel creds (or at least non-overlapping routing)

And importantly: do not mount the first gateway’s ~/.openclaw or workspace into that second container.

At that point, file/state isolation is real; the only shared layer is the host OS + Docker runtime.

Docs:

#

If you want, I can sketch a minimal two-gateway docker/host topology (host gateway + one container gateway) with port/state layout.

#

Yep — here’s the clean way to run a second gateway fully in Docker while your first one stays bare metal.

1) Pick isolated paths + port (do not reuse first gateway paths)

Use a different state/workspace and port (example 19789):

export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
export OPENCLAW_GATEWAY_PORT="19789"
export OPENCLAW_GATEWAY_BIND="lan"
export OPENCLAW_CONFIG_DIR="$HOME/.openclaw-gw2"
export OPENCLAW_WORKSPACE_DIR="$HOME/.openclaw-gw2/workspace"
mkdir -p "$OPENCLAW_WORKSPACE_DIR"

2) Run Docker setup in its own repo folder

#
git clone https://github.com/openclaw/openclaw.git ~/openclaw-gw2
cd ~/openclaw-gw2
./scripts/docker/setup.sh

That script handles onboarding + compose startup for the containerized gateway.

3) Verify second gateway is up

docker compose ps
curl -fsS "http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/healthz"

Open Control UI for gateway #2:
http://127.0.0.1:19789/

#

Agent isolation model

  • This second gateway already has its own independent default agent (main) in its own state/workspace.
  • If you want a named agent inside gateway #2, add it there:
docker compose run --rm openclaw-cli agents add user2

Important guardrails

  • Keep gateway #1 on 18789, gateway #2 on 19789 (or another non-overlapping base port).
  • Don’t point both gateways at the same OPENCLAW_CONFIG_DIR / workspace.
  • Prefer separate channel accounts/tokens per gateway to avoid duplicate replies.
hollow zincBOT
#

Yeah, that’s a legit complaint — and you’re right to call it out.

What usually causes that “it’s using host install” feeling is either:

  1. onboarding command ran on host by accident, or
  2. container got mounted to host ~/.openclaw instead of a fresh path.

Let’s do a hard-isolated flow (no setup.sh, no ambiguity):

mkdir -p ~/openclaw-gw2
cd ~/openclaw-gw2
mkdir -p "$HOME/.openclaw-gw2/workspace"

Create docker-compose.yml:

#
services:
gw2:
image: ghcr.io/openclaw/openclaw:latest
container_name: openclaw-gw2
restart: unless-stopped
environment:
HOME: /home/node
NODE_ENV: production
volumes:
- ${HOME}/.openclaw-gw2:/home/node/.openclaw
- ${HOME}/.openclaw-gw2/workspace:/home/node/.openclaw/workspace
ports:
- "127.0.0.1:19789:18789"
command:
["node","dist/index.js","gateway","--bind","lan","--port","18789","--allow-unconfigured"]
#

Now run onboarding inside container only:

docker compose run --rm gw2 node dist/index.js onboard

Then start gateway #2:

docker compose up -d gw2

Check it:

docker compose ps
#
curl -fsS http://127.0.0.1:19789/healthz

This guarantees gateway #2 uses only ~/.openclaw-gw2/*, not your host gateway state.

If you want, paste your current compose/env and I’ll sanity-check it line-by-line.

hollow zincBOT
#

Perfect — let’s do a clean multi-agent + sandbox setup on one gateway.

1) Preflight

openclaw --version
openclaw status
docker version

2) Build sandbox image (once)

From an OpenClaw source checkout:

git clone https://github.com/openclaw/openclaw.git ~/openclaw-ref
cd ~/openclaw-ref
scripts/sandbox-setup.sh
#

3) Create second agent

openclaw agents add worker --workspace ~/.openclaw/workspace-worker

4) Enable sandbox only for that agent

Edit ~/.openclaw/openclaw.json and set/merge this into the worker agent entry:

{
agents: {
list: [
{
id: "worker",
sandbox: {
mode: "all",
#
scope: "agent",
workspaceAccess: "none"
},
tools: {
allow: ["read"],
deny: ["exec", "write", "edit", "apply_patch", "process", "browser"]
}
}
]
}
}

(Leave your main agent unsandboxed with sandbox.mode: "off" if you want.)

5) Route traffic to worker

#

If you have a separate Discord account for worker:

openclaw agents bind --agent worker --bind discord:worker

(If same account + specific thread/channel routing, we can do a bindings entry for that peer.)

6) Restart + verify

openclaw gateway restart
openclaw agents list --bindings
openclaw sandbox explain --agent worker
docker ps --filter "name=openclaw-sbx-"

#

Short answer: not fully, yet.

There’s no dedicated “create sandboxed agent” wizard end-to-end right now.
The current flow is:

  1. Interactive-ish agent creation
openclaw agents add worker
  1. Manual per-agent sandbox toggle in openclaw.json (agents.list[].sandbox).

  2. Validate/recreate runtime

openclaw sandbox explain --agent worker
openclaw sandbox recreate --agent worker
hollow zincBOT
#

Perfect — for that exact assistant agent, use this config shape:

{
id: "assistant",
name: "assistant",
workspace: "/Users/myuser/.openclaw/workspace-assistant",
agentDir: "/Users/myuser/.openclaw/agents/assistant/agent",
sandbox: {
mode: "all", // always sandbox this agent
scope: "agent", // one persistent sandbox per agent
backend: "docker",
workspaceAccess: "rw", // sandbox can read/write workspace; changes persist
browser: {
enabled: true, // use sandboxed browser container
autoStart: true
#
}
// docker.network defaults to "none"; set to "bridge" only if exec needs internet
}
}

Then do:

  1. Build sandbox images (once):
cd ~/openclaw-ref
scripts/sandbox-setup.sh
scripts/sandbox-browser-setup.sh
  1. Apply runtime refresh:
#
openclaw sandbox recreate --agent assistant
openclaw gateway restart
  1. Verify:
openclaw sandbox explain --agent assistant
docker ps --filter "name=openclaw-sbx-"

If you currently have restrictive tool allow/deny policies, make sure assistant can use:
exec, process, browser, read, write, edit, apply_patch.

If you paste your full agents.list + tools block, I’ll give you an exact no-surprises merged config.