Sandboxing is mandatory. Everything downstream follows.

dclaw splits into a Go control plane (the daemon) and a Node + pi-mono data plane (agent containers). The diagram below is the interactive version. Hover to explore what crosses which boundary.

./architecture · dclaw — Platform Architecture v2 canonical · hover regions or arrows
USER
Discord · Slack · WhatsApp
LLM PROVIDER · EXTERNAL
Anthropic · OpenAI · Google · …
configurable per agent · TLS egress only
CHANNEL PLUGINS · independently versioned containers
discord v1.5.0 slack v2.0.3 whatsapp v0.8.2
OPERATIONAL SURFACE
fleet.yaml
declarative config
dclaw CLI · Go
up · status · logs · upgrade
Web Dashboard
TypeScript
control plane — runs on host, never calls the LLM
dclaw DAEMON · Go · CONTROL PLANE
Channel Router
message routing
Fleet Manager
spawn · health · teardown
Quota Enforce
max-concurrent · max-$
Agent Registry
SQLite
data plane — agent containers, sandboxed
MAIN AGENT · ~400MB · always-on
pi-mono · Node
LLM client · convo
workspace
bind mount
scoped network
allowlist
tools (sub-cnt)
bash · file · grep · web
WORKERS · ~400MB ea · ephemeral
Worker 1
scoped tools
Worker 2
scoped tools
Worker N
scoped tools
dclaw daemon
Go process on the host. THE control plane. Routes inbound messages, manages the fleet (spawn/health/teardown), enforces quotas (max-concurrent, max-$/day), persists agent registry to SQLite. Never calls the LLM.
user plugin control plane main agent · data plane workers · data plane external ops surface

Six ways agents get pwned. Six ways dclaw contains it.

This is the blast-radius argument. Sandboxing changes "one compromise = full system" into "one compromise = one agent." The rest of the architecture exists to keep that true.

◆ threat

Prompt injection

Attacker jailbreaks an agent via untrusted input. On bare-metal runtimes this walks the agent's bash tool to your ~/.ssh.

→ confined to one container. CapDrop ALL + ReadonlyRootfs + uid 1000. Cannot reach other agents, the host, the daemon.
◆ threat

mknod + raw block device

CAP_MKNOD lets the agent create /tmp/sda pointing at the host's first SCSI disk and read raw sectors — including /etc/shadow on the host.

→ CapDrop ALL removes CAP_MKNOD. mknod returns EPERM. Verified by smoke Test 17.
◆ threat

Setuid escalation

A setuid binary on the rootfs (or one written by the agent) lets execve hand the agent a more-privileged identity. CVE-2019-5736 territory.

→ no-new-privileges:true blocks execve from raising privileges. ReadonlyRootfs blocks writing new setuid binaries.
◆ threat

Fork bomb / PID DoS

Spawn :(){ :|:& };: and exhaust the host's PID table. On macOS this crashes Docker Desktop's VM. On Linux it crashes the kernel.

→ PidsLimit 256. The 257th fork returns EAGAIN. pi-mono's steady-state is 5.
◆ threat

docker.sock as workspace

Bind-mount /var/run/docker.sock as the agent's workspace and the agent now controls the host's Docker daemon — equivalent to root.

→ Three socket paths in the absolute denylist. --workspace-trust does NOT bypass. Pre-mount rejection with workspace_forbidden.
◆ threat

Symlink workspace bypass

Create ~/dclaw-agents/trojan as a symlink to /etc and pass --workspace=~/dclaw-agents/trojan. Naive containment misses this.

→ filepath.EvalSymlinks runs before the allow-root check. Symlinks resolve to canonical targets, then those are validated.

All of it. Not just bash.

Most "sandboxed agent" frameworks mean "we shelled out to docker for the bash tool." dclaw means the whole agent, including the LLM call and every tool, runs inside the box.

Agent loop
pi-mono runs inside the container. LLM calls originate from there.
Inside sandbox
Tool execution
bash, file I/O, web fetch — all execute inside the container.
Inside sandbox
Capabilities
CapDrop: ALL. Empty CapAdd. mknod, ptrace, raw sockets — denied.
All caps dropped
Privilege escalation
no-new-privileges:true. Setuid/setgid execve cannot raise effective uid.
Blocked
Rootfs
ReadonlyRootfs:true. /tmp 64m + /run 8m as tmpfs (noexec,nosuid,nodev).
Read-only + EROFS
User
HostConfig.User "1000:1000". Daemon overrides image USER. Workspace is uid 1000.
Non-root enforced
Process count
PidsLimit 256. The 257th fork returns EAGAIN. pi-mono uses ~5 steady-state.
Capped
Seccomp
Docker daemon-loaded default profile. unshare(CLONE_NEWUSER), keyctl denied.
Default profile
Network egress
Per-agent allowlist (wiring planned post-GA; CAP_NET_ADMIN was dropped in beta.2).
Roadmap
Filesystem
Only the bind-mounted workspace is visible. Rest of host is unreachable.
Workspace-scoped
Workspace path
Validated against denylist + allow-root. Symlinks resolved. docker.sock blocked.
Pre-mount checked
Credentials
API key passed as env var at start. Never written to disk, never shared.
Per-agent secret

We didn't rewrite the agent loop.

pi-mono by Mario Zechner is a proven, MIT-licensed TypeScript agent SDK with multi-model support. It ships inside our container. We add the sandbox, the fleet, and the channels.

pi-mono
Agent loop, tool execution, session management, multi-model routing. Runs inside every container.
runtime dep · MIT
Docker
The sandbox itself. We rely on cgroups, iptables and bind mounts. 24+ required.
host dep · apache-2
OpenClaw
Reference only. Not imported, not linked. We read their gateway and adapter patterns and built something different.
reference

Ready to ship agents you can't feel bad about on Monday?