Skip to content
$ roman --notes
Go back

OpenClaw: the Mac minis you don't need

If you spend any time on AI Twitter you have probably seen the photos: a neat stack of Mac minis on someone’s desk, captioned “my personal AI cluster.” Some of those people are actually running LLMs locally, splitting a big model across the boxes, which is fair. But if you are stacking hardware just to run more agents against a hosted model, you are solving a problem you do not have. You do not need a Mac mini per agent.

The reason people reach for more agents is that one agent doing everything gets confused. It mixes work tasks with family tasks and the Telegram channel turns into a soup of unrelated conversations. Splitting things across separate boxes feels like the clean fix. But what you actually want is isolation, not more hardware, and OpenClaw can give you that on a single machine.

Even the first Mac mini is optional. I have been hosting my agents on my laptop for a while. It travels with me sometimes and the agent goes offline for a few hours, but that has not been a real problem so far. The upside is that everything lives on the same machine I actually work on, so file sharing is trivial. If you eventually want an always-on box, fine, but you do not need one to start, and you definitely do not need a stack of them.

Sandboxes

By default, an OpenClaw agent runs its tools directly on your host. Fine if you only have one agent and trust it with everything you trust yourself with. The moment you want a second agent that should not see the same files or the same shell history, that stops working.

Sandboxing is the opt-in fix. The gateway still runs on your host, but the agent’s tools (files, bash, the actual hands of the LLM) execute inside a Docker container. Folders from the host get mounted in explicitly, so each agent only sees what you hand it. Nothing else is reachable.

That is what makes “many agents on one box” safe instead of just convenient.

openclaw.json

The two pieces that matter are agents.defaults and agents.list. Defaults apply to every agent. Each entry in the list can add its own bits on top.

"agents": {
  "defaults": {
    "sandbox": {
      "mode": "all",
      "scope": "session",
      "docker": {
        "image": "openclaw-sandbox:my-image",
        "readOnlyRoot": true,
        "tmpfs": ["/tmp", "/var/tmp", "/run"],
        "binds": [
          "/Users/roman/dev/ai/openclaw:/home/openclaw-src:ro"
        ]
      }
    }
  },
  "list": [
    {
      "id": "main",
      "default": true,
      "workspace": "/Users/roman/.openclaw/workspace",
      "sandbox": {
        "docker": {
          "binds": [
            "/Users/roman/.openclaw/agents/main:/home/.openclaw/agents/main:ro",
            // mount your own folders here, e.g.
            "/Users/roman/notes:/home/notes:rw"
          ]
        }
      }
    }
  ]
}

The trick is that agents.list[].sandbox extends the defaults rather than overwriting them. The docker.binds array on main gets added on top of the default binds, not replacing them. So you keep one shared sandbox image and just hand each agent the extra folders it needs. Full config (with a second agent and channel bindings) is in this gist.

Routing messages to the right agent

Defining multiple agents is only half the job. You also need to tell the gateway which incoming messages go to which agent, otherwise everything keeps landing on the default one and you wonder why you bothered. That is what the top-level bindings array is for.

"bindings": [
  {
    "agentId": "slack-agent",
    "match": { "channel": "slack" }
  },
  {
    "agentId": "family-agent",
    "match": {
      "channel": "telegram",
      "accountId": "family"
    }
  }
]

Each binding is a match rule plus the agent it routes to. The first one says anything coming in over Slack goes to slack-agent. The second one is more specific: only Telegram messages from the family bot account (defined under channels.telegram.accounts.family) go to family-agent. Anything that does not match a binding falls through to the default agent, which in my case is main.

This is how you actually get separation in practice. The family Telegram bot has its own token, its own group, and its own agent with its own workspace and history. The work Slack lands somewhere else entirely. Same machine, same gateway, but the conversations never touch each other.

Docker image

The image is a plain debian:bookworm-slim with the things I actually want my agents to reach for: curl, git, jq, ripgrep, sqlite3, ffmpeg, yt-dlp, plus Bun, Node, and the Lobster CLIs. Full Dockerfile is in the gist.

Do not forget to run openclaw sandbox recreate --all after updating the Dockerfile, otherwise the agents keep using the old image and you will wonder why your changes did nothing.

Let Claude Code help with the config

I still hand-edit openclaw.json, but I almost always have Claude Code open next to me when I do. It is faster to ask “how to enable family agent in group chat” and have it dig through the OpenClaw docs and source than to read all those docs myself. I get the suggestion, apply the change, restart the agent, push back if it does not make sense. Same loop, often faster.

Wrapping up

The agents are the unit of separation here, not the hardware. Once each one has its own sandbox, its own workspace, and its own channel binding, it does not really matter whether they live on a Mac mini, a laptop, or an old NUC sitting in a closet. So next time you scroll past a photo of someone’s neat little Mac mini cluster, just know that most of it is furniture. A few lines of openclaw.json would have done the same job.


Share this post on:

Next Post
Stop Making Your AI Agent Click Buttons