Skip to main content

Private Cloud Setup

Private Cloud lets your team run Agentrix tasks on servers or VMs that you control. Team members create tasks in the Agentrix web app, and Agentrix dispatches those tasks to your runner pool. Repositories, system dependencies, and local credentials stay in your environment.

When to use it​

  • Your team wants a stable pool of Linux runners instead of relying on each developer's laptop.
  • Your repositories, internal dependencies, build tools, or model gateway are only reachable from your company network.
  • You want team members to submit tasks without giving everyone the runner secret.
  • You need multiple machines under one private cloud, capped by your subscription's machine limit.

For single-user local execution, start with CLI Setup or Desktop App Setup.

Prerequisites​

  • An Agentrix account with Private Cloud enabled.
  • A Linux server or VM that can run continuously.
  • Outbound access from the server to the Agentrix backend API. The hosted service defaults to https://agentrix.xmz.ai; replace it with your API URL for self-hosted deployments.
  • Node.js 18+, npm, git, and the agent CLIs required by your tasks, such as Claude Code or Codex.
  • A dedicated system user for the runner, for example agentrix.

See Dependencies for install and check commands. Run agentrix doctor before starting the runner.

Create a Private Cloud​

  1. Open the Agentrix web app.
  2. Go to Settings -> Private Clouds.
  3. Use the top-right menu to create a Private Cloud.
  4. Open the detail page and copy the runner secret.

The runner secret registers runner machines for this cloud. It is not an invite link. Do not send it to regular members or commit it to a repository.

If the create action is unavailable, your current subscription may not include Private Cloud, or you may have reached the cloud limit.

Start a runner on the server​

Install the Agentrix CLI and the required agent:

npm install -g @agentrix/cli
npm install -g @anthropic-ai/claude-code

Install Codex CLI too if this runner will execute Codex tasks:

npm install -g @codex-ai/codex-cli

Start the daemon with the runner secret:

export AGENTRIX_SERVER_URL=https://agentrix.xmz.ai
export AGENTRIX_WEBAPP_URL=https://agentrix.xmz.ai
export AGENTRIX_HOME_DIR=/opt/agentrix
export CLOUD_AUTH_TOKEN=<runner-secret>

agentrix start
agentrix status

For self-hosted deployments, replace AGENTRIX_SERVER_URL and AGENTRIX_WEBAPP_URL with your own URLs. When CLOUD_AUTH_TOKEN is set, the CLI skips browser sign-in and registers the machine as a cloud runner with that secret.

Start a runner with Docker​

Use the CLI Docker image when you do not want to install Node.js, Claude Code, Codex, and sandbox dependencies on the host. The image runs agentrix daemon by default and requires CLOUD_AUTH_TOKEN, the runner secret from the Private Cloud detail page.

docker run -d \
--name agentrix-runner \
--restart unless-stopped \
-e AGENTRIX_SERVER_URL=https://agentrix.xmz.ai \
-e AGENTRIX_WEBAPP_URL=https://agentrix.xmz.ai \
-e CLOUD_AUTH_TOKEN=<runner-secret> \
-e AGENTRIX_BASE_URL=https://api.xmz.ai \
-e AGENTRIX_API_KEY=<llm-gateway-api-key> \
-e GITLAB_BASE_URL=https://gitlab.example.com \
-e GITLAB_TOKEN=<gitlab-token> \
-e AGENTRIX_GITLAB_USE_AS_PROXY=true \
-v agentrix-home:/home/agentrix/.agentrix \
-v agentrix-workspaces:/home/agentrix/.agentrix/workspaces \
ghcr.io/xmz-ai/agentrix/agentrix-cli:latest

For self-hosted deployments, replace AGENTRIX_SERVER_URL and AGENTRIX_WEBAPP_URL with your own Agentrix URLs and, if needed, use the agentrix-cli image from your registry. In this Docker flow, AGENTRIX_API_KEY is the LLM gateway key. Runner registration and task auth still use CLOUD_AUTH_TOKEN.

Before the container starts agentrix daemon or agentrix start, the entrypoint runs agentrix llm-config apply and generates Claude, Codex, PI, and Gemini config from the environment:

ConfigDefault path
Claude Code/home/agentrix/.claude/settings.json
Codex/home/agentrix/.codex/config.toml
PI$AGENTRIX_PI_HOME/models.json and settings.json, defaulting to /home/agentrix/.pi/agent
Gemini$AGENTRIX_GEMINI_HOME/settings.json, defaulting to $AGENTRIX_HOME_DIR/config/gemini/settings.json

Auto-config writes missing files by default and leaves existing config alone. When AGENTRIX_API_KEY is missing and AGENTRIX_API_KEY_FILE is absent or unreadable, the entrypoint fails before daemon startup with a clear error. Set AGENTRIX_LLM_CONFIG_FORCE=true to rebuild the generated files. Set AGENTRIX_LLM_AUTO_CONFIG=false when you mount and manage all agent config yourself.

If the container needs access to a private GitLab instance, pass GITLAB_BASE_URL and GITLAB_TOKEN as well. Authenticated Private Cloud runners default AGENTRIX_GITLAB_BIND_USER to false when it is omitted, keeping the token on the runner and avoiding binding a shared bot as each runner owner's Git identity. AGENTRIX_GITLAB_USE_AS_PROXY=true independently registers the runner as a proxy node. Multiple runners can safely share this GitLab bot config; API Git operations pick one online proxy node. Set AGENTRIX_GITLAB_BIND_USER=true only when the PAT represents the current Agentrix user and should appear in that user's Git server and repository lists.

Common variables:

VariableDefaultDescription
AGENTRIX_API_KEY—LLM gateway key used to generate Claude, Codex, PI, and Gemini config
AGENTRIX_API_KEY_FILE—Read the LLM gateway key from a file, useful with Docker secrets
AGENTRIX_BASE_URLhttps://api.xmz.aiLLM gateway URL; Codex automatically uses /v1 under this URL
AGENTRIX_CODEX_MODELgpt-5.5Default Codex model
AGENTRIX_PI_MODELSame as AGENTRIX_CODEX_MODELDefault PI model
AGENTRIX_CLAUDE_MODELclaude-sonnet-4-6Default Claude model
AGENTRIX_CLAUDE_FAST_MODELclaude-haiku-4-5Claude fast model
AGENTRIX_CLAUDE_SUBAGENT_MODELSame as AGENTRIX_CLAUDE_MODELClaude subagent model
AGENTRIX_GEMINI_IMAGE_MODELgemini-2.5-flash-imageGemini image model
AGENTRIX_CLAUDE_HOME/home/agentrix/.claudeClaude Code config directory
AGENTRIX_CODEX_HOME/home/agentrix/.codexCodex config directory
AGENTRIX_PI_HOME/home/agentrix/.pi/agentPI config directory; PI_CODING_AGENT_DIR is also supported
AGENTRIX_CLAUDE_PATH/usr/local/bin/claudeClaude Code binary path
AGENTRIX_CODEX_PATH/usr/local/bin/codexCodex binary path
AGENTRIX_GEMINI_HOME$AGENTRIX_HOME_DIR/config/geminiGemini config directory
CLOUD_AUTH_TOKEN_FILE—Read the runner secret from a file, useful with Docker secrets
AGENTRIX_EVENT_FORWARD_WS_URL—Enterprise event forwarding receiver address, see "Event forwarding" below
AGENTRIX_EVENT_FORWARD_TOKEN_FILE—Read the event forwarding auth token from a file, useful with Docker secrets
GITLAB_BASE_URL—GitLab site URL for auto-configuration
GITLAB_API_URL<base-url>/api/v4GitLab API URL used by auto-configuration
GITLAB_TOKEN—GitLab token saved to the local encrypted store and, when user binding is enabled, synced as an API-side credential
GITLAB_TOKEN_FILE—Read the GitLab token from a file, useful with Docker secrets
AGENTRIX_GITLAB_BIND_USERfalseWhether to bind the GitLab account to the runner owner's Agentrix user; set true only for a personal PAT
AGENTRIX_GITLAB_USE_AS_PROXYtrueWhether to register the current runner as a Git proxy node
AGENTRIX_GITLAB_AUTO_CONFIGtrueSet to false to disable GitLab auto-configuration

The image also checks the global Git identity on startup and fills in Agentrix <[email protected]> when it is missing. Set AGENTRIX_GIT_USER_NAME and AGENTRIX_GIT_USER_EMAIL to use a team identity.

Run it with systemd​

Use systemd or another process manager in production. First, confirm the installed agentrix path:

command -v agentrix

If you have not created the dedicated system user yet, create one:

sudo useradd --system --create-home --home-dir /opt/agentrix --shell /usr/sbin/nologin agentrix

Create an environment file:

sudo mkdir -p /etc/agentrix
sudo install -m 600 /dev/null /etc/agentrix/private-cloud.env
sudoedit /etc/agentrix/private-cloud.env

Add:

AGENTRIX_SERVER_URL=https://agentrix.xmz.ai
AGENTRIX_WEBAPP_URL=https://agentrix.xmz.ai
AGENTRIX_HOME_DIR=/opt/agentrix
CLOUD_AUTH_TOKEN=<runner-secret>

Create /etc/systemd/system/agentrix-private-cloud.service:

[Unit]
Description=Agentrix Private Cloud Runner
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=agentrix
WorkingDirectory=/opt/agentrix
EnvironmentFile=/etc/agentrix/private-cloud.env
ExecStart=/usr/local/bin/agentrix daemon
ExecStop=/usr/local/bin/agentrix stop
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Replace the ExecStart and ExecStop paths with the output from command -v agentrix. Use agentrix daemon under systemd because agentrix start launches a background daemon and exits. Then start the service:

sudo mkdir -p /opt/agentrix
sudo chown -R agentrix:agentrix /opt/agentrix
sudo systemctl daemon-reload
sudo systemctl enable --now agentrix-private-cloud
sudo systemctl status agentrix-private-cloud

View logs:

journalctl -u agentrix-private-cloud -f
tail -f /opt/agentrix/logs/daemon.log

Verify the runner​

Return to Settings -> Private Clouds, open the cloud detail page, and check that the runner count shows an online machine, such as 1/3. You can also run these commands on the server:

sudo systemctl status agentrix-private-cloud
agentrix status
agentrix doctor

If systemd runs the daemon as the agentrix user, use the same AGENTRIX_HOME_DIR environment when checking from the command line.

If the server looks healthy but the web app has not updated, wait a few seconds and refresh. Runners report presence over WebSocket, so short delays can happen during network changes or daemon restarts.

Invite teammates​

The Private Cloud owner can create invite links from the cloud detail page. A teammate opens the link, signs in, and joins the private cloud under their own account.

Invite links add members. They do not start runners. Members can select the private cloud when creating tasks, but they cannot see the runner secret.

Use it in tasks​

When creating a task, select the private cloud in the runner or execution environment picker. When submitting work with agentrix-run, pass the matching cloud-* runner ID:

agentrix-run \
--prompt "Run the test suite and fix failures" \
--runner-id cloud-xxx

See agentrix-run for CI usage.

Event forwarding​

Event forwarding lets runners push the full task event stream (user messages, assistant replies, tool calls and results, worker lifecycle events, and metering events such as task-usage-report) to a WebSocket receiver you host yourself. Data flows directly from the runner to your internal service without passing through third parties, which makes it a good fit for audit, archival, and notification systems.

Forwarding is enabled when both conditions hold:

  • The runner is in cloud mode (CLOUD_AUTH_TOKEN is set). Local mode messages are end-to-end encrypted, so forwarding ciphertext would be meaningless and is not supported.
  • AGENTRIX_EVENT_FORWARD_WS_URL is configured. Setting the URL is the only switch.

Configuration​

VariableRequiredDescription
AGENTRIX_EVENT_FORWARD_WS_URLYes (setting it enables forwarding)Enterprise WS receiver address, ws:// or wss://
AGENTRIX_EVENT_FORWARD_TOKENNoSent as the Authorization: Bearer <token> header on connect; validated by your receiver
AGENTRIX_EVENT_FORWARD_RETENTION_HOURSNo, default 24How long acked events are retained in the local outbox
AGENTRIX_EVENT_FORWARD_MAX_DB_MBNo, default 512Local outbox size limit; oldest events are dropped beyond it with a WARN

The Docker image also supports AGENTRIX_EVENT_FORWARD_TOKEN_FILE, which works like CLOUD_AUTH_TOKEN_FILE.

How it works​

  • Events are first persisted to a runner-local outbox ($AGENTRIX_HOME_DIR/forward/outbox.db); a daemon-resident forwarder pushes them in cursor order. Events survive daemon restarts and receiver downtime, and are replayed by cursor after reconnect.
  • Delivery is at-least-once: receivers must deduplicate by eventId.
  • cursor is a per-runner monotonic counter. When multiple runners feed one receiver, track cursors per machineId.
  • The forwarded stream does not carry the server-side global sequence (it may not be assigned yet at forward time). For task-wide total order or full reconciliation, fall back to REST GET /v1/tasks/:taskId/events; server-only events that never reach runner workers are also not forwarded.
  • If the outbox exceeds its size limit and unacked events had to be dropped, the first events frame after the next handshake carries a droppedBeforeCursor field marking the gap.
  • Forwarding status is visible in the daemon log ($AGENTRIX_HOME_DIR/logs/daemon.log, search for [FORWARD]).

Protocol​

Plain WebSocket with JSON text frames; any language can implement the receiver:

// CLI → receiver: handshake after connect (cloudId is the private cloud this runner belongs to)
{ "type": "hello", "protocolVersion": 1, "machineId": "machine-xxx", "cloudId": "cloud-xxx", "hostname": "runner-01", "cliVersion": "0.45.0" }

// Receiver → CLI: handshake confirmation. resumeFromCursor is optional and
// declares the cursor the receiver has already consumed; when omitted, the
// runner resumes from its locally acked cursor
{ "type": "hello-ack", "resumeFromCursor": 12345 }

// CLI → receiver: event batch (droppedBeforeCursor only appears after the
// outbox had to drop unacked events)
{ "type": "events", "events": [
{ "cursor": 12346, "eventId": "evt-xxx", "taskId": "task-xxx", "chatId": "chat-xxx",
"eventType": "task-message", "direction": "outbound",
"eventData": { /* full event payload, e.g. TaskMessageEventData */ },
"createdAt": "2026-07-02T08:00:00.000Z" }
] }

// Receiver → CLI: cumulative acknowledgement (acks everything up to cursor)
{ "type": "ack", "cursor": 12346 }

direction is outbound for events produced by workers (assistant replies, tool calls, lifecycle events) and inbound for events delivered to the runner (user messages, initial create/resume messages). Heartbeats use protocol-level WebSocket ping/pong frames; the application layer does not need to handle them.

Reference receiver​

A minimal working Node receiver (run after npm install ws):

const { WebSocketServer } = require('ws');

const seenEventIds = new Set(); // use persistent storage in production
const cursors = new Map(); // machineId -> acked cursor

const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket, request) => {
const auth = request.headers.authorization;
if (process.env.FORWARD_TOKEN && auth !== `Bearer ${process.env.FORWARD_TOKEN}`) {
socket.close(4401, 'unauthorized');
return;
}

let machineId = null;
socket.on('message', (raw) => {
const frame = JSON.parse(raw);

if (frame.type === 'hello') {
machineId = frame.machineId;
const resumeFromCursor = cursors.get(machineId) ?? 0;
socket.send(JSON.stringify({ type: 'hello-ack', resumeFromCursor }));
console.log(`runner ${machineId} connected, resume from ${resumeFromCursor}`);
return;
}

if (frame.type === 'events') {
if (frame.droppedBeforeCursor) {
console.warn(`gap: events before cursor ${frame.droppedBeforeCursor} were dropped`);
}
for (const event of frame.events) {
if (seenEventIds.has(event.eventId)) continue; // at-least-once dedup
seenEventIds.add(event.eventId);
console.log(`[${event.direction}] ${event.eventType} task=${event.taskId} cursor=${event.cursor}`);
// feed your audit / archival / notification systems here
}
const last = frame.events[frame.events.length - 1];
cursors.set(machineId, last.cursor);
socket.send(JSON.stringify({ type: 'ack', cursor: last.cursor }));
}
});
});

console.log('forward receiver listening on :8080');

Security notes​

  • If the runner secret leaks, stop the old runners and recreate the private cloud or contact an administrator.
  • Run the daemon with a dedicated low-privilege system user.
  • Restrict permissions on AGENTRIX_HOME_DIR, the environment file, and log directories.
  • Do not store the runner secret in repositories, plaintext CI variables, or shared shell profiles.
  • Runner servers usually need outbound access to the Agentrix API. They do not need a public inbound port.

Troubleshooting​

Runner does not come online​

Check that CLOUD_AUTH_TOKEN is present in the daemon process environment. With systemd, inspect the EnvironmentFile. Then confirm AGENTRIX_SERVER_URL points to the correct API and review daemon.log.

Startup opens browser sign-in​

The daemon did not receive CLOUD_AUTH_TOKEN. Export it again, or restart the systemd service and check the environment file permissions.

Tasks stay queued​

Confirm that the private cloud detail page shows an online runner. Then run agentrix doctor on the runner and check git, Claude Code, Codex, and sandbox dependencies. Task logs are written to $AGENTRIX_HOME_DIR/logs/worker-<taskId>.log.

Token is invalid or cloud is missing​

This usually means the wrong secret was copied, or the runner connected to the wrong Agentrix API. Copy the runner secret again from the detail page and confirm AGENTRIX_SERVER_URL uses the same deployment.