From 256171cc62a0c39e20cb4d19141668024e6da3c2 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 07:54:03 -0600 Subject: [PATCH] feat(docker): OpenClaw agent fleet compose + real configs (MS22-P1a) --- docker/OPENCLAW-FLEET.md | 90 ++++++++++ docker/openclaw-compose.yml | 166 ++++++++++++++++++ docker/openclaw-instances/README.md | 47 +++++ docker/openclaw-instances/jarvis-main.env | 3 + docker/openclaw-instances/jarvis-main.json | 41 +++++ .../openclaw-instances/jarvis-operations.env | 3 + .../openclaw-instances/jarvis-operations.json | 40 +++++ docker/openclaw-instances/jarvis-projects.env | 3 + .../openclaw-instances/jarvis-projects.json | 39 ++++ docker/openclaw-instances/jarvis-research.env | 3 + .../openclaw-instances/jarvis-research.json | 39 ++++ 11 files changed, 474 insertions(+) create mode 100644 docker/OPENCLAW-FLEET.md create mode 100644 docker/openclaw-compose.yml create mode 100644 docker/openclaw-instances/README.md create mode 100644 docker/openclaw-instances/jarvis-main.env create mode 100644 docker/openclaw-instances/jarvis-main.json create mode 100644 docker/openclaw-instances/jarvis-operations.env create mode 100644 docker/openclaw-instances/jarvis-operations.json create mode 100644 docker/openclaw-instances/jarvis-projects.env create mode 100644 docker/openclaw-instances/jarvis-projects.json create mode 100644 docker/openclaw-instances/jarvis-research.env create mode 100644 docker/openclaw-instances/jarvis-research.json diff --git a/docker/OPENCLAW-FLEET.md b/docker/OPENCLAW-FLEET.md new file mode 100644 index 0000000..3d6f3cf --- /dev/null +++ b/docker/OPENCLAW-FLEET.md @@ -0,0 +1,90 @@ +# OpenClaw Agent Fleet + +OpenClaw multi-agent deployment for Mosaic Stack using Docker Swarm and Portainer. + +## Agent Roster + +| Agent | Service | Primary Model | Role | +| ----------------- | ------------------- | --------------- | ---------------------------------- | +| jarvis-main | `jarvis-main` | `zai/glm-5` | Orchestrator / user-facing gateway | +| jarvis-projects | `jarvis-projects` | `zai/glm-5` | Development and coding tasks | +| jarvis-research | `jarvis-research` | `zai/glm-5` | Research and web search | +| jarvis-operations | `jarvis-operations` | `ollama/cogito` | Monitoring, health checks, alerts | + +## Prerequisites + +1. Docker Swarm initialized on the target host +2. Existing Docker network `mosaic-stack_internal` (external swarm network) +3. Z.ai API access key (`ZAI_API_KEY`) +4. Ollama reachable at `10.1.1.42:11434` for the `cogito` model + +## Quick Start + +### 1. Configure each agent env file + +Set values in: + +- `docker/openclaw-instances/jarvis-main.env` +- `docker/openclaw-instances/jarvis-projects.env` +- `docker/openclaw-instances/jarvis-research.env` +- `docker/openclaw-instances/jarvis-operations.env` + +Required variables: + +- `OPENCLAW_CONFIG_PATH=/config/openclaw.json` +- `ZAI_API_KEY=` +- `OPENCLAW_GATEWAY_TOKEN=` + +### 2. Generate unique gateway tokens + +Generate one token per service: + +```bash +openssl rand -hex 32 +``` + +### 3. Deploy the fleet + +From repo root: + +```bash +docker stack deploy -c docker/openclaw-compose.yml jarvis +``` + +### 4. Verify service status + +```bash +docker stack services jarvis +docker service logs jarvis-jarvis-main --tail 100 +docker service logs jarvis-jarvis-projects --tail 100 +docker service logs jarvis-jarvis-research --tail 100 +docker service logs jarvis-jarvis-operations --tail 100 +``` + +### 5. First-time auth (if required) + +Exec into a container and run OpenClaw auth device flow: + +```bash +docker exec -it $(docker ps -q -f name=jarvis-jarvis-main) sh +openclaw auth +``` + +You can also complete this in the Mosaic WebUI terminal (xterm.js). + +## Management Commands + +| Command | Description | +| ----------------------------------------------------------- | ---------------------- | +| `docker stack deploy -c docker/openclaw-compose.yml jarvis` | Deploy/update fleet | +| `docker stack services jarvis` | List services in stack | +| `docker service logs jarvis-` | View service logs | +| `docker service update --force jarvis-` | Restart rolling update | +| `docker service scale jarvis-=N` | Scale a service | +| `docker stack rm jarvis` | Remove fleet | + +## Notes + +- Each service stores persistent local OpenClaw state in `/home/node/.openclaw`. +- Each service mounts a read-only per-agent JSON config at `/config/openclaw.json`. +- `chatCompletions` endpoint is enabled in each agent config for Mosaic API usage. diff --git a/docker/openclaw-compose.yml b/docker/openclaw-compose.yml new file mode 100644 index 0000000..7fb2686 --- /dev/null +++ b/docker/openclaw-compose.yml @@ -0,0 +1,166 @@ +services: + jarvis-main: + image: alpine/openclaw:latest + command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + env_file: + - ./openclaw-instances/jarvis-main.env + environment: + OPENCLAW_CONFIG_PATH: /config/openclaw.json + volumes: + - jarvis-main-config:/config/openclaw.json:ro + - jarvis-main-state:/home/node/.openclaw + networks: + - mosaic-stack_internal + healthcheck: + test: ["CMD", "openclaw", "gateway", "health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + resources: + limits: + memory: 2G + reservations: + memory: 512M + labels: + - com.mosaic.agent=jarvis-main + - com.mosaic.role=orchestrator + + jarvis-projects: + image: alpine/openclaw:latest + command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + env_file: + - ./openclaw-instances/jarvis-projects.env + environment: + OPENCLAW_CONFIG_PATH: /config/openclaw.json + volumes: + - jarvis-projects-config:/config/openclaw.json:ro + - jarvis-projects-state:/home/node/.openclaw + networks: + - mosaic-stack_internal + healthcheck: + test: ["CMD", "openclaw", "gateway", "health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + resources: + limits: + memory: 4G + reservations: + memory: 1G + labels: + - com.mosaic.agent=jarvis-projects + - com.mosaic.role=development + + jarvis-research: + image: alpine/openclaw:latest + command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + env_file: + - ./openclaw-instances/jarvis-research.env + environment: + OPENCLAW_CONFIG_PATH: /config/openclaw.json + volumes: + - jarvis-research-config:/config/openclaw.json:ro + - jarvis-research-state:/home/node/.openclaw + networks: + - mosaic-stack_internal + healthcheck: + test: ["CMD", "openclaw", "gateway", "health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + resources: + limits: + memory: 1G + reservations: + memory: 256M + labels: + - com.mosaic.agent=jarvis-research + - com.mosaic.role=research + + jarvis-operations: + image: alpine/openclaw:latest + command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + env_file: + - ./openclaw-instances/jarvis-operations.env + environment: + OPENCLAW_CONFIG_PATH: /config/openclaw.json + volumes: + - jarvis-operations-config:/config/openclaw.json:ro + - jarvis-operations-state:/home/node/.openclaw + networks: + - mosaic-stack_internal + healthcheck: + test: ["CMD", "openclaw", "gateway", "health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + resources: + limits: + memory: 1G + reservations: + memory: 256M + labels: + - com.mosaic.agent=jarvis-operations + - com.mosaic.role=operations + +networks: + mosaic-stack_internal: + external: true + +volumes: + jarvis-main-config: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/docker/openclaw-instances/jarvis-main.json + jarvis-projects-config: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/docker/openclaw-instances/jarvis-projects.json + jarvis-research-config: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/docker/openclaw-instances/jarvis-research.json + jarvis-operations-config: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/docker/openclaw-instances/jarvis-operations.json + jarvis-main-state: + jarvis-projects-state: + jarvis-research-state: + jarvis-operations-state: diff --git a/docker/openclaw-instances/README.md b/docker/openclaw-instances/README.md new file mode 100644 index 0000000..e0a2de6 --- /dev/null +++ b/docker/openclaw-instances/README.md @@ -0,0 +1,47 @@ +# OpenClaw Agent Instance Setup + +Each service in the OpenClaw fleet reads: + +- A per-agent environment file: `docker/openclaw-instances/.env` +- A per-agent JSON5 config: `docker/openclaw-instances/.json` + +## 1. Fill in API keys in `.env` files + +Set `ZAI_API_KEY` in each instance env file: + +- `jarvis-main.env` +- `jarvis-projects.env` +- `jarvis-research.env` +- `jarvis-operations.env` + +## 2. Generate unique gateway tokens per agent + +Generate one token per instance: + +```bash +openssl rand -hex 32 +``` + +Set a different `OPENCLAW_GATEWAY_TOKEN` in each `.env` file. + +## 3. Deploy the Docker Swarm stack + +From repository root: + +```bash +docker stack deploy -c docker/openclaw-compose.yml jarvis +``` + +## 4. First-time auth (if needed) + +If an instance requires first-time login, exec into the running container and run: + +```bash +openclaw auth +``` + +This uses OpenClaw's headless OAuth device-code flow. + +## 5. Use Mosaic WebUI terminal for auth + +You can complete the device-code auth flow from the Mosaic WebUI terminal (xterm.js) attached to the service container. diff --git a/docker/openclaw-instances/jarvis-main.env b/docker/openclaw-instances/jarvis-main.env new file mode 100644 index 0000000..719d4bc --- /dev/null +++ b/docker/openclaw-instances/jarvis-main.env @@ -0,0 +1,3 @@ +OPENCLAW_CONFIG_PATH=/config/openclaw.json +ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY +OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN diff --git a/docker/openclaw-instances/jarvis-main.json b/docker/openclaw-instances/jarvis-main.json new file mode 100644 index 0000000..5feeae9 --- /dev/null +++ b/docker/openclaw-instances/jarvis-main.json @@ -0,0 +1,41 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "zai/glm-5" } + } + }, + // Z.ai is built in and uses ZAI_API_KEY. + // Ollama is configured for optional local reasoning fallback. + "models": { + "mode": "merge", + "providers": { + "ollama": { + "baseUrl": "http://10.1.1.42:11434/v1", + "api": "openai-completions", + "models": [ + { + "id": "cogito", + "name": "Cogito (Local Reasoning)", + "reasoning": false, + "input": ["text"], + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, + "contextWindow": 128000, + "maxTokens": 8192 + } + ] + } + } + } +} diff --git a/docker/openclaw-instances/jarvis-operations.env b/docker/openclaw-instances/jarvis-operations.env new file mode 100644 index 0000000..719d4bc --- /dev/null +++ b/docker/openclaw-instances/jarvis-operations.env @@ -0,0 +1,3 @@ +OPENCLAW_CONFIG_PATH=/config/openclaw.json +ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY +OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN diff --git a/docker/openclaw-instances/jarvis-operations.json b/docker/openclaw-instances/jarvis-operations.json new file mode 100644 index 0000000..87ce1af --- /dev/null +++ b/docker/openclaw-instances/jarvis-operations.json @@ -0,0 +1,40 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "ollama/cogito" } + } + }, + // Operations uses local Ollama Cogito as the primary model. + "models": { + "mode": "merge", + "providers": { + "ollama": { + "baseUrl": "http://10.1.1.42:11434/v1", + "api": "openai-completions", + "models": [ + { + "id": "cogito", + "name": "Cogito (Local Reasoning)", + "reasoning": false, + "input": ["text"], + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, + "contextWindow": 128000, + "maxTokens": 8192 + } + ] + } + } + } +} diff --git a/docker/openclaw-instances/jarvis-projects.env b/docker/openclaw-instances/jarvis-projects.env new file mode 100644 index 0000000..719d4bc --- /dev/null +++ b/docker/openclaw-instances/jarvis-projects.env @@ -0,0 +1,3 @@ +OPENCLAW_CONFIG_PATH=/config/openclaw.json +ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY +OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN diff --git a/docker/openclaw-instances/jarvis-projects.json b/docker/openclaw-instances/jarvis-projects.json new file mode 100644 index 0000000..71c8c00 --- /dev/null +++ b/docker/openclaw-instances/jarvis-projects.json @@ -0,0 +1,39 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "zai/glm-5" } + } + }, + "models": { + "mode": "merge", + "providers": { + "ollama": { + "baseUrl": "http://10.1.1.42:11434/v1", + "api": "openai-completions", + "models": [ + { + "id": "cogito", + "name": "Cogito (Local Reasoning)", + "reasoning": false, + "input": ["text"], + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, + "contextWindow": 128000, + "maxTokens": 8192 + } + ] + } + } + } +} diff --git a/docker/openclaw-instances/jarvis-research.env b/docker/openclaw-instances/jarvis-research.env new file mode 100644 index 0000000..719d4bc --- /dev/null +++ b/docker/openclaw-instances/jarvis-research.env @@ -0,0 +1,3 @@ +OPENCLAW_CONFIG_PATH=/config/openclaw.json +ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY +OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN diff --git a/docker/openclaw-instances/jarvis-research.json b/docker/openclaw-instances/jarvis-research.json new file mode 100644 index 0000000..71c8c00 --- /dev/null +++ b/docker/openclaw-instances/jarvis-research.json @@ -0,0 +1,39 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "zai/glm-5" } + } + }, + "models": { + "mode": "merge", + "providers": { + "ollama": { + "baseUrl": "http://10.1.1.42:11434/v1", + "api": "openai-completions", + "models": [ + { + "id": "cogito", + "name": "Cogito (Local Reasoning)", + "reasoning": false, + "input": ["text"], + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, + "contextWindow": 128000, + "maxTokens": 8192 + } + ] + } + } + } +}