OPERATIONS · WHERE XMESH RUNS

Where xMesh runs

In the sandbox you already have. xMesh is a process beside your agent session, not a service you rent — the cloud container exists so a team can give it one always-on host, and it is the same artifact either way. What decides whether a deployment is real is not the host; it is three questions a container forces you to answer and a laptop lets you skip: where does the state live, what does it think with, and who chose what it may spend.

1. Your own machine — the primary path

npm i -g @sym-bot/[email protected]
xmesh
# [xmesh] passcode: ········   (generated on first run — stored in ~/.xmesh/passcode)

That is the whole deployment for an individual or a team on one machine: a local process, state under ~/.xmesh, the MCP bridge wired into your Claude Code or Codex session with xmesh mcp install, and your own provider login held by the CLI it spawns — xMesh never sees the credential. No proxy, no volume, no licence: evaluation is free for non-production use, and nothing on this page is required until you want the board up when your session is not.

WHAT A SANDBOX MUST KEEP
xMesh needs its own persistent process — a board that is up when your agent session is not — for missions to run, review to staff and recall to accumulate. A sandbox torn down with the task (a fresh container per session) loses the team root each time. Your machine keeps state by default; an ephemeral sandbox needs a volume, which is exactly what the container’s /state path is for.

2. A team’s own host — one container, one volume

A team wants the board on a host that stays up: a spare machine, a VPS, a cluster. Any host that can run one container with one volume can run xMesh — there is nothing platform-specific in the image. This is what “deploy it yourself” means, and it is three commands.

01
Pull a version you can name
docker pull symbotdocker/xmesh:0.9.40

The image is public on Docker Hub — no registry credentials, no repository access, no build step. Pin the version tag: an evaluator must be able to say exactly what they ran, and a floating latest makes that unanswerable a week later. The tag resolves to a multi-architecture index (amd64 and arm64), and the npm package and container of the same version are built from the same gated artifact.

02
One volume is one tenant
docker volume create xmesh-acme

docker run -d --name xmesh -p 8787:8787 \
  -v xmesh-acme:/state \
  symbotdocker/xmesh:0.9.40

Everything the deployment owns lives under that one path — /state/xmesh for server state, /state/sym for the mesh node tree. Back the volume up and you have backed up the team. Delete it and the team is gone. That is the whole tenancy model, and it is deliberately boring.

MEASURED
Never run two containers against one volume. The state root is a single writable tree with no coordination between processes, so two containers on one volume are two servers writing one tenant. In Kubernetes this is why you pin replicas: 1 and use a StatefulSet rather than a Deployment — a Deployment’s rolling update runs the new pod alongside the old one, which is the same collision on a schedule.
03
Prove it is actually listening — and read what it says
curl -s localhost:8787/api/health
# {"verification":"on — every mission is independently reviewed by a second
#   model (opt out: XMESH_MISSION_CRITIC=0)",
#  "spend":{"protection":"workload-cap","chosen":false},
#  "ok":true,"ready":false,"grounded":false}

ok means a process is listening. ready means it has a usable mind. grounded means it is on a live mesh with cognition to reason from. They are reported separately because the most likely way this goes wrong does not look like a failure: a container that answers every endpoint, accepts missions, and thinks nothing. Here ready:false is correct — no provider has been declared yet — and the authenticated /api/readiness names which half is missing and what to set.

THE PASSCODE
The API passcode is printed once at first start; read it from the log:
docker logs xmesh | grep passcode
# [xmesh] passcode: ········   (generated on first run — stored in /state/xmesh/passcode)
Authenticate with it at POST /api/session (the field is credential; passcode works too) and every call after that carries the returned token. On a public URL, do not rely on the generated one — see the boundary section below.

Give it a mind

Since 0.9.39 the image carries its worker mind — a pinned codex CLI, proved present when the image is built. Declare the provider and hand the entrypoint a key and ready becomes true:

docker run -d --name xmesh -p 8787:8787 \
  -v xmesh-acme:/state \
  -e XMESH_LLM_PROVIDER=api:openai \
  -e OPENAI_API_KEY=<your key> \
  -e XMESH_PASSCODE=<credential> -e XMESH_REQUIRE_CREDENTIAL=1 \
  symbotdocker/xmesh:0.9.40

curl -s localhost:8787/api/health
# {…,"ok":true,"ready":true,"grounded":false}

The entrypoint logs the CLI in over stdin into an ephemeral home, verifies the login, and unsets the key before the server starts — it never appears on a command line and never reaches the long-lived process, which is the usual objection to a key in an environment variable answered at the point it matters. ready:true with grounded:false is the normal shape of a fresh team deployment: it can do work, and it has no peers to ground in yet.

WHY A SUBSCRIPTION CANNOT RIDE INSIDE THE CONTAINER
A personal Claude subscription works by spawning the claude CLI, which authenticates from a login in your home directory. There is no API key that represents it, and the container does not carry that CLI. Mounting a personal login into a shared service ties the service to one person’s session and cannot be rotated, scoped or attributed to a team. So: your own subscription, run xMesh natively (section 1) and let the CLI hold the credential; a team or a customer, use a key.

The number you did not choose

A fresh deployment runs under a default cap of 40 commissioned work units per UTC day — generous against measured normal use of about six missions a day. It is a default, not a decision: /api/health reports chosen:false while it is in effect, and when it binds, the refusal says so and hands you the number you did not have on day one — what a day of your own work actually costs. xMesh never meters you for money; budgets are limits you set on what it may spend from your own provider account. To replace the default, as the deployment’s owner:

curl -s -X POST localhost:8787/api/spend/choice \
  -H "x-mesh-token: $TOKEN" -H 'content-type: application/json' \
  -d '{"mode":"workload","dailyUnits":40}'
# {"ok":true,"spend":{"protection":"workload-cap",
#   "detail":"0/40 work units today (UTC)","remaining":40}}

A daily workload cap, a daily USD line, or explicitly unbounded spend — recorded with a name against it. xMesh will never accept unbounded spend on its own, and the choice is deliberately not an MCP tool: the mesh never sets its own budget. After a choice, chosen flips to true on an unauthenticated endpoint, so a fleet can be audited with one probe per host.

The boundary, when the host is public

A team host usually sits behind a platform’s HTTPS proxy or your own. Three settings turn the container from a laptop posture into a public one, and the image refuses to start in the public posture if any of them is wrong:

-e XMESH_REQUIRE_CREDENTIAL=1        # refuse to start without an explicit credential
-e XMESH_PASSCODE=$(openssl rand -base64 24)   # ≥16 chars, GENERATED — never a typed phrase
-e XMESH_TRUST_PROXY=1               # a HOP COUNT: exactly the edges in front of you

The login door is throttled per client address and globally, which is what the passcode’s entropy assumes; XMESH_TRUST_PROXY is a hop count rather than a switch because “trust every proxy” reads the address the client wrote, and a guesser would choose its own throttle key. Never expose raw :8787 on a public interface — bearer auth over plain HTTP is a credential in the clear.

YOUR EGRESS BOUNDARY
Set XMESH_LLM_BASE_URL to an in-house gateway and every API request is pinned to that origin. A redirect to a vendor host is refused before a socket is opened — not on retry, not on failover. This is enforced in the client, and the product’s own suite tests it from both directions, because a guard that only ever says no is indistinguishable from a broken client.

A persistent host: name the team root

On a platform that mounts a network volume, name the team root beneath the mount and tell the image where the mount is:

-e XMESH_VOLUME_MOUNT=/workspace
-e XMESH_TEAM_ROOT=/workspace/xmesh-teams/acme-prod
ON 0.9.40, ADD A THIRD LINE
-e SYM_STATE_DIR=/workspace/xmesh-teams/acme-prod/sym. On that release a team root owns the server’s state, but the node stores — what the mesh has learned, and what every later mission is briefed from — are written by the mesh library, which reads only SYM_STATE_DIR. Without it they land under the container user’s home, which is not on the volume: measured on a fresh 0.9.40 install with only the team root set, the second mission’s brief carried none of the first’s cognition, and a replaced container would start with none. 0.9.41 derives it from the team root: the published symbotdocker/xmesh:0.9.41 image, run with only the two lines above, wrote its first node store under the team root’s sym/nodes and nothing under the home directory but the loopback registry, which is a heartbeat, not memory. On 0.9.41 the third line is redundant but harmless.

The team root then owns all state, and the volume carries a deployment-identity marker: a later container naming a different team root on the same volume refuses rather than quietly becoming a different deployment. The image starts as root only to take ownership of those two paths, validates every path before it touches anything (a mount of /, a root outside the mount, or a mount without a root each refuse by name), and drops to an unprivileged user before any login or server code runs. A production-team licence binds to that root’s canonical path — the same path is the same team, on any host.

Removing it

docker rm -f xmesh && docker volume rm xmesh-acme

Two commands, and the second is the one that matters. A removed container leaves the tenant intact on its volume; removing the volume is what ends the deployment. Verify the volume is gone rather than assuming — an empty deployment and a healthy one are hard to tell apart from the outside, which is the theme of this entire recipe.

Kubernetes

The container is the whole deployment surface, so any orchestrator that can run one container with one volume can run xMesh. If that orchestrator is Kubernetes, four decisions matter and the rest is taste: a StatefulSet with replicas: 1 (see the collision note above); start as the user the image drops to — uid 1000, with fsGroup: 1000 so the claim is writable and the root-start path is never needed; readOnlyRootFilesystem with writable emptyDirs at /tmp and /home/node (the login home lives there); and no PodDisruptionBudget, because minAvailable: 1 on a single replica blocks every node drain and turns routine maintenance into an outage that looks like a cluster fault. That posture was run against the published 0.9.40 image the day this page was rewritten and answered ready:true. A reference manifest embodying exactly these decisions is part of the evaluation pilot — ask through [email protected].