From 256171cc62a0c39e20cb4d19141668024e6da3c2 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 07:54:03 -0600 Subject: [PATCH 1/5] 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 + } + ] + } + } + } +} -- 2.49.1 From 11136e2f233c42d952485194d2b24d3044b602aa Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 07:54:28 -0600 Subject: [PATCH 2/5] =?UTF-8?q?fix(docker):=20use=20envsubst=20template=20?= =?UTF-8?q?pattern=20=E2=80=94=20no=20hardcoded=20URLs=20or=20keys=20(MS22?= =?UTF-8?q?-P1a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/migrations/002_agent_fleet.sql | 154 ++++++++++++++++++ docker/openclaw-compose.yml | 16 +- docker/openclaw-instances/entrypoint.sh | 23 +++ docker/openclaw-instances/jarvis-main.env | 1 + ...is-main.json => jarvis-main.json.template} | 2 +- .../openclaw-instances/jarvis-operations.env | 1 + ...s.json => jarvis-operations.json.template} | 2 +- docker/openclaw-instances/jarvis-projects.env | 1 + ...cts.json => jarvis-projects.json.template} | 2 +- docker/openclaw-instances/jarvis-research.env | 1 + ...rch.json => jarvis-research.json.template} | 2 +- 11 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 docker/migrations/002_agent_fleet.sql create mode 100755 docker/openclaw-instances/entrypoint.sh rename docker/openclaw-instances/{jarvis-main.json => jarvis-main.json.template} (95%) rename docker/openclaw-instances/{jarvis-operations.json => jarvis-operations.json.template} (94%) rename docker/openclaw-instances/{jarvis-projects.json => jarvis-projects.json.template} (94%) rename docker/openclaw-instances/{jarvis-research.json => jarvis-research.json.template} (94%) diff --git a/docker/migrations/002_agent_fleet.sql b/docker/migrations/002_agent_fleet.sql new file mode 100644 index 0000000..f27ce5b --- /dev/null +++ b/docker/migrations/002_agent_fleet.sql @@ -0,0 +1,154 @@ +-- Migration: Agent Fleet Infrastructure +-- Adds tables for multi-instance OpenClaw orchestration +-- Run after base schema exists + +-- ============================================ +-- INSTANCE REGISTRY +-- ============================================ +CREATE TABLE IF NOT EXISTS instances ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL UNIQUE, + role VARCHAR(50) NOT NULL CHECK (role IN ('gateway', 'department', 'task_worker', 'user_session')), + parent_id UUID REFERENCES instances(id) ON DELETE SET NULL, + status VARCHAR(20) NOT NULL DEFAULT 'starting' CHECK (status IN ('starting', 'running', 'paused', 'stopped', 'error')), + config JSONB NOT NULL DEFAULT '{}', + capabilities JSONB NOT NULL DEFAULT '[]', + handles JSONB NOT NULL DEFAULT '[]', -- Projects/channels this instance handles + max_children INT DEFAULT 5, + max_workers INT DEFAULT 10, + idle_timeout_minutes INT DEFAULT 30, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_heartbeat_at TIMESTAMPTZ, + metadata JSONB DEFAULT '{}' +); + +CREATE INDEX idx_instances_role ON instances(role); +CREATE INDEX idx_instances_parent ON instances(parent_id); +CREATE INDEX idx_instances_status ON instances(status); + +-- ============================================ +-- SESSIONS (User/Project Sessions) +-- ============================================ +CREATE TABLE IF NOT EXISTS sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, + session_key VARCHAR(255) NOT NULL UNIQUE, + user_id VARCHAR(255), -- Discord user ID, etc. + channel_id VARCHAR(255), -- Discord channel ID + project_context VARCHAR(255), -- Which project this session is for + status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'idle', 'paused', 'closed')), + context_window JSONB DEFAULT '[]', -- Conversation history + metadata JSONB DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + closed_at TIMESTAMPTZ +); + +CREATE INDEX idx_sessions_instance ON sessions(instance_id); +CREATE INDEX idx_sessions_user ON sessions(user_id); +CREATE INDEX idx_sessions_channel ON sessions(channel_id); +CREATE INDEX idx_sessions_status ON sessions(status); + +-- ============================================ +-- SESSION SUMMARIES (For RAG Context) +-- ============================================ +CREATE TABLE IF NOT EXISTS session_summaries ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, + instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, + summary_type VARCHAR(50) NOT NULL CHECK (summary_type IN ('decision', 'key_info', 'action_item', 'lesson', 'milestone')), + content TEXT NOT NULL, + embedding VECTOR(1024), -- bge-m3 dimension + metadata JSONB DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_summaries_instance ON session_summaries(instance_id); +CREATE INDEX idx_summaries_type ON session_summaries(summary_type); +CREATE INDEX idx_summaries_session ON session_summaries(session_id); + +-- pgvector index for semantic search +CREATE INDEX IF NOT EXISTS idx_summaries_embedding ON session_summaries +USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); + +-- ============================================ +-- EVENT LOG (Audit Trail) +-- ============================================ +CREATE TABLE IF NOT EXISTS event_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, + session_id UUID REFERENCES sessions(id) ON DELETE SET NULL, + event_type VARCHAR(100) NOT NULL, + event_data JSONB DEFAULT '{}', + severity VARCHAR(20) NOT NULL DEFAULT 'info' CHECK (severity IN ('debug', 'info', 'warn', 'error')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_event_log_instance ON event_log(instance_id); +CREATE INDEX idx_event_log_session ON event_log(session_id); +CREATE INDEX idx_event_log_type ON event_log(event_type); +CREATE INDEX idx_event_log_severity ON event_log(severity); +CREATE INDEX idx_event_log_created ON event_log(created_at); + +-- ============================================ +-- CHANNEL MAPPINGS (Routing) +-- ============================================ +CREATE TABLE IF NOT EXISTS channel_mappings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + channel_id VARCHAR(255) NOT NULL UNIQUE, + channel_type VARCHAR(50) NOT NULL, -- discord, telegram, etc. + instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, + project_context VARCHAR(255), + skills JSONB DEFAULT '[]', -- Allowed skills for this channel + metadata JSONB DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_channel_mappings_instance ON channel_mappings(instance_id); +CREATE INDEX idx_channel_mappings_channel ON channel_mappings(channel_id); + +-- ============================================ +-- TASK QUEUE (For Worker Spawning) +-- ============================================ +CREATE TABLE IF NOT EXISTS task_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + task_type VARCHAR(50) NOT NULL, + parent_instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'claimed', 'running', 'completed', 'failed', 'cancelled')), + task_data JSONB NOT NULL DEFAULT '{}', + result_data JSONB, + assigned_worker_id UUID REFERENCES instances(id) ON DELETE SET NULL, + priority INT DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ +); + +CREATE INDEX idx_task_queue_status ON task_queue(status); +CREATE INDEX idx_task_queue_parent ON task_queue(parent_instance_id); +CREATE INDEX idx_task_queue_priority ON task_queue(priority DESC, created_at); + +-- ============================================ +-- DEFAULT INSTANCES (Seed Data) +-- ============================================ +INSERT INTO instances (name, role, status, config, capabilities, handles, max_children, max_workers) +VALUES + ('jarvis-main', 'gateway', 'running', + '{"skills": ["orchestration", "mosaic-bootstrap", "all-projects-access"]}', + '["spawn", "kill", "route", "delegate"]', + '[]', 5, 10), + ('jarvis-projects', 'department', 'running', + '{"skills": ["coding-agent", "next-best-practices", "nestjs-best-practices", "fastapi"]}', + '["spawn-workers", "code-review", "ci-cd"]', + '["mosaic-stack", "sagephr", "lark-path"]', 3, 5), + ('jarvis-research', 'department', 'running', + '{"skills": ["web-search", "web-fetch", "brainstorming"]}', + '["research", "discovery", "analysis"]', + '[]', 2, 3), + ('jarvis-operations', 'department', 'running', + '{"skills": ["monitoring", "healthcheck", "maintenance"]}', + '["monitoring", "maintenance", "alerts"]', + '[]', 2, 3) +ON CONFLICT (name) DO NOTHING; diff --git a/docker/openclaw-compose.yml b/docker/openclaw-compose.yml index 7fb2686..4bd306b 100644 --- a/docker/openclaw-compose.yml +++ b/docker/openclaw-compose.yml @@ -1,13 +1,13 @@ services: jarvis-main: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - ./openclaw-instances/jarvis-main.env environment: OPENCLAW_CONFIG_PATH: /config/openclaw.json volumes: - - jarvis-main-config:/config/openclaw.json:ro + - jarvis-main-config:/config:ro - jarvis-main-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -34,13 +34,13 @@ services: jarvis-projects: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - ./openclaw-instances/jarvis-projects.env environment: OPENCLAW_CONFIG_PATH: /config/openclaw.json volumes: - - jarvis-projects-config:/config/openclaw.json:ro + - jarvis-projects-config:/config:ro - jarvis-projects-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -67,13 +67,13 @@ services: jarvis-research: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - ./openclaw-instances/jarvis-research.env environment: OPENCLAW_CONFIG_PATH: /config/openclaw.json volumes: - - jarvis-research-config:/config/openclaw.json:ro + - jarvis-research-config:/config:ro - jarvis-research-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -100,13 +100,13 @@ services: jarvis-operations: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - ./openclaw-instances/jarvis-operations.env environment: OPENCLAW_CONFIG_PATH: /config/openclaw.json volumes: - - jarvis-operations-config:/config/openclaw.json:ro + - jarvis-operations-config:/config:ro - jarvis-operations-state:/home/node/.openclaw networks: - mosaic-stack_internal diff --git a/docker/openclaw-instances/entrypoint.sh b/docker/openclaw-instances/entrypoint.sh new file mode 100755 index 0000000..3b2fde6 --- /dev/null +++ b/docker/openclaw-instances/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# OpenClaw container entrypoint — renders config template via envsubst then starts gateway +set -e + +TEMPLATE="/config/openclaw.json.template" +CONFIG="/tmp/openclaw.json" + +if [ ! -f "$TEMPLATE" ]; then + echo "ERROR: Config template not found at $TEMPLATE" + exit 1 +fi + +# Validate required env vars +: "${ZAI_API_KEY:?ZAI_API_KEY is required}" +: "${OPENCLAW_GATEWAY_TOKEN:?OPENCLAW_GATEWAY_TOKEN is required}" +: "${OLLAMA_BASE_URL:?OLLAMA_BASE_URL is required (e.g. http://10.1.1.42:11434)}" + +# Render template -> final config (no hardcoded values in image or volumes) +envsubst < "$TEMPLATE" > "$CONFIG" + +export OPENCLAW_CONFIG_PATH="$CONFIG" + +exec openclaw gateway run --bind lan --auth token "$@" diff --git a/docker/openclaw-instances/jarvis-main.env b/docker/openclaw-instances/jarvis-main.env index 719d4bc..893d930 100644 --- a/docker/openclaw-instances/jarvis-main.env +++ b/docker/openclaw-instances/jarvis-main.env @@ -1,3 +1,4 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN +OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-main.json b/docker/openclaw-instances/jarvis-main.json.template similarity index 95% rename from docker/openclaw-instances/jarvis-main.json rename to docker/openclaw-instances/jarvis-main.json.template index 5feeae9..4752124 100644 --- a/docker/openclaw-instances/jarvis-main.json +++ b/docker/openclaw-instances/jarvis-main.json.template @@ -22,7 +22,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "http://10.1.1.42:11434/v1", + "baseUrl": "${OLLAMA_BASE_URL}/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-operations.env b/docker/openclaw-instances/jarvis-operations.env index 719d4bc..893d930 100644 --- a/docker/openclaw-instances/jarvis-operations.env +++ b/docker/openclaw-instances/jarvis-operations.env @@ -1,3 +1,4 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN +OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-operations.json b/docker/openclaw-instances/jarvis-operations.json.template similarity index 94% rename from docker/openclaw-instances/jarvis-operations.json rename to docker/openclaw-instances/jarvis-operations.json.template index 87ce1af..afef855 100644 --- a/docker/openclaw-instances/jarvis-operations.json +++ b/docker/openclaw-instances/jarvis-operations.json.template @@ -21,7 +21,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "http://10.1.1.42:11434/v1", + "baseUrl": "${OLLAMA_BASE_URL}/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-projects.env b/docker/openclaw-instances/jarvis-projects.env index 719d4bc..893d930 100644 --- a/docker/openclaw-instances/jarvis-projects.env +++ b/docker/openclaw-instances/jarvis-projects.env @@ -1,3 +1,4 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN +OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-projects.json b/docker/openclaw-instances/jarvis-projects.json.template similarity index 94% rename from docker/openclaw-instances/jarvis-projects.json rename to docker/openclaw-instances/jarvis-projects.json.template index 71c8c00..e12decf 100644 --- a/docker/openclaw-instances/jarvis-projects.json +++ b/docker/openclaw-instances/jarvis-projects.json.template @@ -20,7 +20,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "http://10.1.1.42:11434/v1", + "baseUrl": "${OLLAMA_BASE_URL}/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-research.env b/docker/openclaw-instances/jarvis-research.env index 719d4bc..893d930 100644 --- a/docker/openclaw-instances/jarvis-research.env +++ b/docker/openclaw-instances/jarvis-research.env @@ -1,3 +1,4 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN +OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-research.json b/docker/openclaw-instances/jarvis-research.json.template similarity index 94% rename from docker/openclaw-instances/jarvis-research.json rename to docker/openclaw-instances/jarvis-research.json.template index 71c8c00..e12decf 100644 --- a/docker/openclaw-instances/jarvis-research.json +++ b/docker/openclaw-instances/jarvis-research.json.template @@ -20,7 +20,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "http://10.1.1.42:11434/v1", + "baseUrl": "${OLLAMA_BASE_URL}/v1", "api": "openai-completions", "models": [ { -- 2.49.1 From 50f0dc601805d410578999832b5e19f18c1ac47e Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 07:55:32 -0600 Subject: [PATCH 3/5] =?UTF-8?q?Revert=20"fix(docker):=20use=20envsubst=20t?= =?UTF-8?q?emplate=20pattern=20=E2=80=94=20no=20hardcoded=20URLs=20or=20ke?= =?UTF-8?q?ys=20(MS22-P1a)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 11136e2f233c42d952485194d2b24d3044b602aa. --- docker/migrations/002_agent_fleet.sql | 154 ------------------ docker/openclaw-compose.yml | 16 +- docker/openclaw-instances/entrypoint.sh | 23 --- docker/openclaw-instances/jarvis-main.env | 1 - ...is-main.json.template => jarvis-main.json} | 2 +- .../openclaw-instances/jarvis-operations.env | 1 - ...s.json.template => jarvis-operations.json} | 2 +- docker/openclaw-instances/jarvis-projects.env | 1 - ...cts.json.template => jarvis-projects.json} | 2 +- docker/openclaw-instances/jarvis-research.env | 1 - ...rch.json.template => jarvis-research.json} | 2 +- 11 files changed, 12 insertions(+), 193 deletions(-) delete mode 100644 docker/migrations/002_agent_fleet.sql delete mode 100755 docker/openclaw-instances/entrypoint.sh rename docker/openclaw-instances/{jarvis-main.json.template => jarvis-main.json} (95%) rename docker/openclaw-instances/{jarvis-operations.json.template => jarvis-operations.json} (94%) rename docker/openclaw-instances/{jarvis-projects.json.template => jarvis-projects.json} (94%) rename docker/openclaw-instances/{jarvis-research.json.template => jarvis-research.json} (94%) diff --git a/docker/migrations/002_agent_fleet.sql b/docker/migrations/002_agent_fleet.sql deleted file mode 100644 index f27ce5b..0000000 --- a/docker/migrations/002_agent_fleet.sql +++ /dev/null @@ -1,154 +0,0 @@ --- Migration: Agent Fleet Infrastructure --- Adds tables for multi-instance OpenClaw orchestration --- Run after base schema exists - --- ============================================ --- INSTANCE REGISTRY --- ============================================ -CREATE TABLE IF NOT EXISTS instances ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL UNIQUE, - role VARCHAR(50) NOT NULL CHECK (role IN ('gateway', 'department', 'task_worker', 'user_session')), - parent_id UUID REFERENCES instances(id) ON DELETE SET NULL, - status VARCHAR(20) NOT NULL DEFAULT 'starting' CHECK (status IN ('starting', 'running', 'paused', 'stopped', 'error')), - config JSONB NOT NULL DEFAULT '{}', - capabilities JSONB NOT NULL DEFAULT '[]', - handles JSONB NOT NULL DEFAULT '[]', -- Projects/channels this instance handles - max_children INT DEFAULT 5, - max_workers INT DEFAULT 10, - idle_timeout_minutes INT DEFAULT 30, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_heartbeat_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}' -); - -CREATE INDEX idx_instances_role ON instances(role); -CREATE INDEX idx_instances_parent ON instances(parent_id); -CREATE INDEX idx_instances_status ON instances(status); - --- ============================================ --- SESSIONS (User/Project Sessions) --- ============================================ -CREATE TABLE IF NOT EXISTS sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, - session_key VARCHAR(255) NOT NULL UNIQUE, - user_id VARCHAR(255), -- Discord user ID, etc. - channel_id VARCHAR(255), -- Discord channel ID - project_context VARCHAR(255), -- Which project this session is for - status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'idle', 'paused', 'closed')), - context_window JSONB DEFAULT '[]', -- Conversation history - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - closed_at TIMESTAMPTZ -); - -CREATE INDEX idx_sessions_instance ON sessions(instance_id); -CREATE INDEX idx_sessions_user ON sessions(user_id); -CREATE INDEX idx_sessions_channel ON sessions(channel_id); -CREATE INDEX idx_sessions_status ON sessions(status); - --- ============================================ --- SESSION SUMMARIES (For RAG Context) --- ============================================ -CREATE TABLE IF NOT EXISTS session_summaries ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, - instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, - summary_type VARCHAR(50) NOT NULL CHECK (summary_type IN ('decision', 'key_info', 'action_item', 'lesson', 'milestone')), - content TEXT NOT NULL, - embedding VECTOR(1024), -- bge-m3 dimension - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_summaries_instance ON session_summaries(instance_id); -CREATE INDEX idx_summaries_type ON session_summaries(summary_type); -CREATE INDEX idx_summaries_session ON session_summaries(session_id); - --- pgvector index for semantic search -CREATE INDEX IF NOT EXISTS idx_summaries_embedding ON session_summaries -USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); - --- ============================================ --- EVENT LOG (Audit Trail) --- ============================================ -CREATE TABLE IF NOT EXISTS event_log ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, - session_id UUID REFERENCES sessions(id) ON DELETE SET NULL, - event_type VARCHAR(100) NOT NULL, - event_data JSONB DEFAULT '{}', - severity VARCHAR(20) NOT NULL DEFAULT 'info' CHECK (severity IN ('debug', 'info', 'warn', 'error')), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_event_log_instance ON event_log(instance_id); -CREATE INDEX idx_event_log_session ON event_log(session_id); -CREATE INDEX idx_event_log_type ON event_log(event_type); -CREATE INDEX idx_event_log_severity ON event_log(severity); -CREATE INDEX idx_event_log_created ON event_log(created_at); - --- ============================================ --- CHANNEL MAPPINGS (Routing) --- ============================================ -CREATE TABLE IF NOT EXISTS channel_mappings ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - channel_id VARCHAR(255) NOT NULL UNIQUE, - channel_type VARCHAR(50) NOT NULL, -- discord, telegram, etc. - instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, - project_context VARCHAR(255), - skills JSONB DEFAULT '[]', -- Allowed skills for this channel - metadata JSONB DEFAULT '{}', - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_channel_mappings_instance ON channel_mappings(instance_id); -CREATE INDEX idx_channel_mappings_channel ON channel_mappings(channel_id); - --- ============================================ --- TASK QUEUE (For Worker Spawning) --- ============================================ -CREATE TABLE IF NOT EXISTS task_queue ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - task_type VARCHAR(50) NOT NULL, - parent_instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, - status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'claimed', 'running', 'completed', 'failed', 'cancelled')), - task_data JSONB NOT NULL DEFAULT '{}', - result_data JSONB, - assigned_worker_id UUID REFERENCES instances(id) ON DELETE SET NULL, - priority INT DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - started_at TIMESTAMPTZ, - completed_at TIMESTAMPTZ -); - -CREATE INDEX idx_task_queue_status ON task_queue(status); -CREATE INDEX idx_task_queue_parent ON task_queue(parent_instance_id); -CREATE INDEX idx_task_queue_priority ON task_queue(priority DESC, created_at); - --- ============================================ --- DEFAULT INSTANCES (Seed Data) --- ============================================ -INSERT INTO instances (name, role, status, config, capabilities, handles, max_children, max_workers) -VALUES - ('jarvis-main', 'gateway', 'running', - '{"skills": ["orchestration", "mosaic-bootstrap", "all-projects-access"]}', - '["spawn", "kill", "route", "delegate"]', - '[]', 5, 10), - ('jarvis-projects', 'department', 'running', - '{"skills": ["coding-agent", "next-best-practices", "nestjs-best-practices", "fastapi"]}', - '["spawn-workers", "code-review", "ci-cd"]', - '["mosaic-stack", "sagephr", "lark-path"]', 3, 5), - ('jarvis-research', 'department', 'running', - '{"skills": ["web-search", "web-fetch", "brainstorming"]}', - '["research", "discovery", "analysis"]', - '[]', 2, 3), - ('jarvis-operations', 'department', 'running', - '{"skills": ["monitoring", "healthcheck", "maintenance"]}', - '["monitoring", "maintenance", "alerts"]', - '[]', 2, 3) -ON CONFLICT (name) DO NOTHING; diff --git a/docker/openclaw-compose.yml b/docker/openclaw-compose.yml index 4bd306b..7fb2686 100644 --- a/docker/openclaw-compose.yml +++ b/docker/openclaw-compose.yml @@ -1,13 +1,13 @@ services: jarvis-main: image: alpine/openclaw:latest - command: ["/config/entrypoint.sh"] + 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:ro + - jarvis-main-config:/config/openclaw.json:ro - jarvis-main-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -34,13 +34,13 @@ services: jarvis-projects: image: alpine/openclaw:latest - command: ["/config/entrypoint.sh"] + 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:ro + - jarvis-projects-config:/config/openclaw.json:ro - jarvis-projects-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -67,13 +67,13 @@ services: jarvis-research: image: alpine/openclaw:latest - command: ["/config/entrypoint.sh"] + 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:ro + - jarvis-research-config:/config/openclaw.json:ro - jarvis-research-state:/home/node/.openclaw networks: - mosaic-stack_internal @@ -100,13 +100,13 @@ services: jarvis-operations: image: alpine/openclaw:latest - command: ["/config/entrypoint.sh"] + 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:ro + - jarvis-operations-config:/config/openclaw.json:ro - jarvis-operations-state:/home/node/.openclaw networks: - mosaic-stack_internal diff --git a/docker/openclaw-instances/entrypoint.sh b/docker/openclaw-instances/entrypoint.sh deleted file mode 100755 index 3b2fde6..0000000 --- a/docker/openclaw-instances/entrypoint.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# OpenClaw container entrypoint — renders config template via envsubst then starts gateway -set -e - -TEMPLATE="/config/openclaw.json.template" -CONFIG="/tmp/openclaw.json" - -if [ ! -f "$TEMPLATE" ]; then - echo "ERROR: Config template not found at $TEMPLATE" - exit 1 -fi - -# Validate required env vars -: "${ZAI_API_KEY:?ZAI_API_KEY is required}" -: "${OPENCLAW_GATEWAY_TOKEN:?OPENCLAW_GATEWAY_TOKEN is required}" -: "${OLLAMA_BASE_URL:?OLLAMA_BASE_URL is required (e.g. http://10.1.1.42:11434)}" - -# Render template -> final config (no hardcoded values in image or volumes) -envsubst < "$TEMPLATE" > "$CONFIG" - -export OPENCLAW_CONFIG_PATH="$CONFIG" - -exec openclaw gateway run --bind lan --auth token "$@" diff --git a/docker/openclaw-instances/jarvis-main.env b/docker/openclaw-instances/jarvis-main.env index 893d930..719d4bc 100644 --- a/docker/openclaw-instances/jarvis-main.env +++ b/docker/openclaw-instances/jarvis-main.env @@ -1,4 +1,3 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN -OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-main.json.template b/docker/openclaw-instances/jarvis-main.json similarity index 95% rename from docker/openclaw-instances/jarvis-main.json.template rename to docker/openclaw-instances/jarvis-main.json index 4752124..5feeae9 100644 --- a/docker/openclaw-instances/jarvis-main.json.template +++ b/docker/openclaw-instances/jarvis-main.json @@ -22,7 +22,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "${OLLAMA_BASE_URL}/v1", + "baseUrl": "http://10.1.1.42:11434/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-operations.env b/docker/openclaw-instances/jarvis-operations.env index 893d930..719d4bc 100644 --- a/docker/openclaw-instances/jarvis-operations.env +++ b/docker/openclaw-instances/jarvis-operations.env @@ -1,4 +1,3 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN -OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-operations.json.template b/docker/openclaw-instances/jarvis-operations.json similarity index 94% rename from docker/openclaw-instances/jarvis-operations.json.template rename to docker/openclaw-instances/jarvis-operations.json index afef855..87ce1af 100644 --- a/docker/openclaw-instances/jarvis-operations.json.template +++ b/docker/openclaw-instances/jarvis-operations.json @@ -21,7 +21,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "${OLLAMA_BASE_URL}/v1", + "baseUrl": "http://10.1.1.42:11434/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-projects.env b/docker/openclaw-instances/jarvis-projects.env index 893d930..719d4bc 100644 --- a/docker/openclaw-instances/jarvis-projects.env +++ b/docker/openclaw-instances/jarvis-projects.env @@ -1,4 +1,3 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN -OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-projects.json.template b/docker/openclaw-instances/jarvis-projects.json similarity index 94% rename from docker/openclaw-instances/jarvis-projects.json.template rename to docker/openclaw-instances/jarvis-projects.json index e12decf..71c8c00 100644 --- a/docker/openclaw-instances/jarvis-projects.json.template +++ b/docker/openclaw-instances/jarvis-projects.json @@ -20,7 +20,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "${OLLAMA_BASE_URL}/v1", + "baseUrl": "http://10.1.1.42:11434/v1", "api": "openai-completions", "models": [ { diff --git a/docker/openclaw-instances/jarvis-research.env b/docker/openclaw-instances/jarvis-research.env index 893d930..719d4bc 100644 --- a/docker/openclaw-instances/jarvis-research.env +++ b/docker/openclaw-instances/jarvis-research.env @@ -1,4 +1,3 @@ OPENCLAW_CONFIG_PATH=/config/openclaw.json ZAI_API_KEY=REPLACE_WITH_ZAI_API_KEY OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_UNIQUE_GATEWAY_TOKEN -OLLAMA_BASE_URL=REPLACE_WITH_OLLAMA_BASE_URL diff --git a/docker/openclaw-instances/jarvis-research.json.template b/docker/openclaw-instances/jarvis-research.json similarity index 94% rename from docker/openclaw-instances/jarvis-research.json.template rename to docker/openclaw-instances/jarvis-research.json index e12decf..71c8c00 100644 --- a/docker/openclaw-instances/jarvis-research.json.template +++ b/docker/openclaw-instances/jarvis-research.json @@ -20,7 +20,7 @@ "mode": "merge", "providers": { "ollama": { - "baseUrl": "${OLLAMA_BASE_URL}/v1", + "baseUrl": "http://10.1.1.42:11434/v1", "api": "openai-completions", "models": [ { -- 2.49.1 From 89767e26ef41b8538a2ddab05cd8c045ff557480 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 08:02:31 -0600 Subject: [PATCH 4/5] fix(docker): generic naming (mosaic-*), env-var-only config, no hardcoded values - Renamed all jarvis-* to mosaic-* (generic for any deployment) - Config files are .json.template with ${VAR} placeholders - entrypoint.sh renders templates via envsubst at startup - Ollama is optional: set OLLAMA_BASE_URL to auto-inject provider - Model is configurable via OPENCLAW_MODEL env var - No hardcoded IPs, keys, model names, or user preferences - Updated README with full env var reference --- docker/OPENCLAW-FLEET.md | 42 +++---- docker/openclaw-compose.yml | 104 ++++++++---------- docker/openclaw-instances/README.md | 104 +++++++++++++----- docker/openclaw-instances/entrypoint.sh | 53 +++++++++ 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 ------- docker/openclaw-instances/mosaic-main.env | 14 +++ .../mosaic-main.json.template | 19 ++++ .../openclaw-instances/mosaic-operations.env | 14 +++ .../mosaic-operations.json.template | 19 ++++ docker/openclaw-instances/mosaic-projects.env | 14 +++ .../mosaic-projects.json.template | 19 ++++ docker/openclaw-instances/mosaic-research.env | 14 +++ .../mosaic-research.json.template | 19 ++++ 20 files changed, 327 insertions(+), 279 deletions(-) create mode 100755 docker/openclaw-instances/entrypoint.sh delete mode 100644 docker/openclaw-instances/jarvis-main.env delete mode 100644 docker/openclaw-instances/jarvis-main.json delete mode 100644 docker/openclaw-instances/jarvis-operations.env delete mode 100644 docker/openclaw-instances/jarvis-operations.json delete mode 100644 docker/openclaw-instances/jarvis-projects.env delete mode 100644 docker/openclaw-instances/jarvis-projects.json delete mode 100644 docker/openclaw-instances/jarvis-research.env delete mode 100644 docker/openclaw-instances/jarvis-research.json create mode 100644 docker/openclaw-instances/mosaic-main.env create mode 100644 docker/openclaw-instances/mosaic-main.json.template create mode 100644 docker/openclaw-instances/mosaic-operations.env create mode 100644 docker/openclaw-instances/mosaic-operations.json.template create mode 100644 docker/openclaw-instances/mosaic-projects.env create mode 100644 docker/openclaw-instances/mosaic-projects.json.template create mode 100644 docker/openclaw-instances/mosaic-research.env create mode 100644 docker/openclaw-instances/mosaic-research.json.template diff --git a/docker/OPENCLAW-FLEET.md b/docker/OPENCLAW-FLEET.md index 3d6f3cf..ebc0564 100644 --- a/docker/OPENCLAW-FLEET.md +++ b/docker/OPENCLAW-FLEET.md @@ -6,10 +6,10 @@ OpenClaw multi-agent deployment for Mosaic Stack using Docker Swarm and Portaine | 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 | +| mosaic-main | `mosaic-main` | `zai/glm-5` | Orchestrator / user-facing gateway | +| mosaic-projects | `mosaic-projects` | `zai/glm-5` | Development and coding tasks | +| mosaic-research | `mosaic-research` | `zai/glm-5` | Research and web search | +| mosaic-operations | `mosaic-operations` | `ollama/cogito` | Monitoring, health checks, alerts | ## Prerequisites @@ -24,10 +24,10 @@ OpenClaw multi-agent deployment for Mosaic Stack using Docker Swarm and Portaine 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` +- `docker/openclaw-instances/mosaic-main.env` +- `docker/openclaw-instances/mosaic-projects.env` +- `docker/openclaw-instances/mosaic-research.env` +- `docker/openclaw-instances/mosaic-operations.env` Required variables: @@ -48,17 +48,17 @@ openssl rand -hex 32 From repo root: ```bash -docker stack deploy -c docker/openclaw-compose.yml jarvis +docker stack deploy -c docker/openclaw-compose.yml mosaic ``` ### 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 +docker stack services mosaic +docker service logs mosaic-mosaic-main --tail 100 +docker service logs mosaic-mosaic-projects --tail 100 +docker service logs mosaic-mosaic-research --tail 100 +docker service logs mosaic-mosaic-operations --tail 100 ``` ### 5. First-time auth (if required) @@ -66,7 +66,7 @@ docker service logs jarvis-jarvis-operations --tail 100 Exec into a container and run OpenClaw auth device flow: ```bash -docker exec -it $(docker ps -q -f name=jarvis-jarvis-main) sh +docker exec -it $(docker ps -q -f name=mosaic-mosaic-main) sh openclaw auth ``` @@ -76,12 +76,12 @@ You can also complete this in the Mosaic WebUI terminal (xterm.js). | 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 | +| `docker stack deploy -c docker/openclaw-compose.yml mosaic` | Deploy/update fleet | +| `docker stack services mosaic` | List services in stack | +| `docker service logs mosaic-` | View service logs | +| `docker service update --force mosaic-` | Restart rolling update | +| `docker service scale mosaic-=N` | Scale a service | +| `docker stack rm mosaic` | Remove fleet | ## Notes diff --git a/docker/openclaw-compose.yml b/docker/openclaw-compose.yml index 7fb2686..18c0bf9 100644 --- a/docker/openclaw-compose.yml +++ b/docker/openclaw-compose.yml @@ -1,14 +1,18 @@ +# Mosaic Agent Fleet — OpenClaw Docker Swarm Stack +# Deploy: docker stack deploy -c docker/openclaw-compose.yml mosaic-agents +# All config via env vars — see openclaw-instances/*.env + services: - jarvis-main: + mosaic-main: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - - ./openclaw-instances/jarvis-main.env + - ./openclaw-instances/mosaic-main.env environment: - OPENCLAW_CONFIG_PATH: /config/openclaw.json + OPENCLAW_CONFIG_PATH: /tmp/openclaw.json volumes: - - jarvis-main-config:/config/openclaw.json:ro - - jarvis-main-state:/home/node/.openclaw + - mosaic-main-config:/config:ro + - mosaic-main-state:/home/node/.openclaw networks: - mosaic-stack_internal healthcheck: @@ -29,19 +33,19 @@ services: reservations: memory: 512M labels: - - com.mosaic.agent=jarvis-main + - com.mosaic.agent=mosaic-main - com.mosaic.role=orchestrator - jarvis-projects: + mosaic-projects: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - - ./openclaw-instances/jarvis-projects.env + - ./openclaw-instances/mosaic-projects.env environment: - OPENCLAW_CONFIG_PATH: /config/openclaw.json + OPENCLAW_CONFIG_PATH: /tmp/openclaw.json volumes: - - jarvis-projects-config:/config/openclaw.json:ro - - jarvis-projects-state:/home/node/.openclaw + - mosaic-projects-config:/config:ro + - mosaic-projects-state:/home/node/.openclaw networks: - mosaic-stack_internal healthcheck: @@ -54,7 +58,7 @@ services: replicas: 1 restart_policy: condition: on-failure - delay: 5s + delay: 10s max_attempts: 3 resources: limits: @@ -62,19 +66,19 @@ services: reservations: memory: 1G labels: - - com.mosaic.agent=jarvis-projects - - com.mosaic.role=development + - com.mosaic.agent=mosaic-projects + - com.mosaic.role=developer - jarvis-research: + mosaic-research: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - - ./openclaw-instances/jarvis-research.env + - ./openclaw-instances/mosaic-research.env environment: - OPENCLAW_CONFIG_PATH: /config/openclaw.json + OPENCLAW_CONFIG_PATH: /tmp/openclaw.json volumes: - - jarvis-research-config:/config/openclaw.json:ro - - jarvis-research-state:/home/node/.openclaw + - mosaic-research-config:/config:ro + - mosaic-research-state:/home/node/.openclaw networks: - mosaic-stack_internal healthcheck: @@ -87,7 +91,7 @@ services: replicas: 1 restart_policy: condition: on-failure - delay: 5s + delay: 10s max_attempts: 3 resources: limits: @@ -95,19 +99,19 @@ services: reservations: memory: 256M labels: - - com.mosaic.agent=jarvis-research + - com.mosaic.agent=mosaic-research - com.mosaic.role=research - jarvis-operations: + mosaic-operations: image: alpine/openclaw:latest - command: ["gateway", "run", "--bind", "lan", "--auth", "token"] + command: ["/config/entrypoint.sh"] env_file: - - ./openclaw-instances/jarvis-operations.env + - ./openclaw-instances/mosaic-operations.env environment: - OPENCLAW_CONFIG_PATH: /config/openclaw.json + OPENCLAW_CONFIG_PATH: /tmp/openclaw.json volumes: - - jarvis-operations-config:/config/openclaw.json:ro - - jarvis-operations-state:/home/node/.openclaw + - mosaic-operations-config:/config:ro + - mosaic-operations-state:/home/node/.openclaw networks: - mosaic-stack_internal healthcheck: @@ -120,7 +124,7 @@ services: replicas: 1 restart_policy: condition: on-failure - delay: 5s + delay: 10s max_attempts: 3 resources: limits: @@ -128,7 +132,7 @@ services: reservations: memory: 256M labels: - - com.mosaic.agent=jarvis-operations + - com.mosaic.agent=mosaic-operations - com.mosaic.role=operations networks: @@ -136,31 +140,11 @@ networks: 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: + mosaic-main-config: + mosaic-main-state: + mosaic-projects-config: + mosaic-projects-state: + mosaic-research-config: + mosaic-research-state: + mosaic-operations-config: + mosaic-operations-state: diff --git a/docker/openclaw-instances/README.md b/docker/openclaw-instances/README.md index e0a2de6..13037e2 100644 --- a/docker/openclaw-instances/README.md +++ b/docker/openclaw-instances/README.md @@ -1,47 +1,97 @@ -# OpenClaw Agent Instance Setup +# Mosaic Agent Fleet — Setup Guide -Each service in the OpenClaw fleet reads: +## Prerequisites -- A per-agent environment file: `docker/openclaw-instances/.env` -- A per-agent JSON5 config: `docker/openclaw-instances/.json` +- Docker Swarm initialized on target host +- Mosaic Stack running (Postgres, Valkey on `mosaic-stack_internal` network) -## 1. Fill in API keys in `.env` files +## 1. Configure Environment Variables -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: +Copy and fill in each agent's `.env` file: ```bash -openssl rand -hex 32 +cd docker/openclaw-instances + +# Required for each agent: +# ZAI_API_KEY — Your Z.ai API key (or other LLM provider key) +# OPENCLAW_GATEWAY_TOKEN — Unique bearer token per agent + +# Generate unique tokens: +for agent in main projects research operations; do + echo "OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)" +done ``` -Set a different `OPENCLAW_GATEWAY_TOKEN` in each `.env` file. +### Optional: Local Ollama -## 3. Deploy the Docker Swarm stack - -From repository root: +If you have an Ollama instance, add to any agent's `.env`: ```bash -docker stack deploy -c docker/openclaw-compose.yml jarvis +OLLAMA_BASE_URL=http://your-ollama-host:11434 +OLLAMA_MODEL=cogito # or any model you have pulled ``` -## 4. First-time auth (if needed) +The entrypoint script will automatically inject the Ollama provider at startup. -If an instance requires first-time login, exec into the running container and run: +### Optional: Override Default Model ```bash -openclaw auth +OPENCLAW_MODEL=anthropic/claude-sonnet-4-6 ``` -This uses OpenClaw's headless OAuth device-code flow. +## 2. Populate Config Volumes -## 5. Use Mosaic WebUI terminal for auth +Each agent needs its `.json.template` file in its config volume: -You can complete the device-code auth flow from the Mosaic WebUI terminal (xterm.js) attached to the service container. +```bash +# Create config directories and copy templates +for agent in main projects research operations; do + mkdir -p /var/lib/docker/volumes/mosaic-agents_mosaic-${agent}-config/_data/ + cp openclaw-instances/mosaic-${agent}.json.template \ + /var/lib/docker/volumes/mosaic-agents_mosaic-${agent}-config/_data/openclaw.json.template + cp openclaw-instances/entrypoint.sh \ + /var/lib/docker/volumes/mosaic-agents_mosaic-${agent}-config/_data/entrypoint.sh +done +``` + +## 3. Deploy + +```bash +docker stack deploy -c docker/openclaw-compose.yml mosaic-agents +docker stack services mosaic-agents +``` + +## 4. First-Time Auth (if needed) + +For providers requiring OAuth (e.g., Anthropic): + +```bash +docker exec -it $(docker ps -q -f name=mosaic-main) openclaw auth +``` + +Follow the device-code flow in your browser. Tokens persist in the state volume. + +You can also use the Mosaic WebUI terminal (xterm.js) for this. + +## 5. Verify + +```bash +# Check health +curl http://localhost:18789/health + +# Test chat completions endpoint +curl http://localhost:18789/v1/chat/completions \ + -H "Authorization: Bearer YOUR_GATEWAY_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"model":"openclaw:main","messages":[{"role":"user","content":"hello"}]}' +``` + +## Environment Variable Reference + +| Variable | Required | Description | +| ------------------------ | -------- | ------------------------------------------------- | +| `ZAI_API_KEY` | Yes\* | Z.ai API key (\*or other provider key) | +| `OPENCLAW_GATEWAY_TOKEN` | Yes | Bearer token for this agent (unique per instance) | +| `OPENCLAW_MODEL` | No | Override default model (default: `zai/glm-5`) | +| `OLLAMA_BASE_URL` | No | Ollama endpoint (e.g., `http://10.1.1.42:11434`) | +| `OLLAMA_MODEL` | No | Ollama model name (default: `cogito`) | diff --git a/docker/openclaw-instances/entrypoint.sh b/docker/openclaw-instances/entrypoint.sh new file mode 100755 index 0000000..7604ced --- /dev/null +++ b/docker/openclaw-instances/entrypoint.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Mosaic Agent Fleet — OpenClaw container entrypoint +# Renders config template from env vars, optionally adds Ollama provider, starts gateway +set -e + +TEMPLATE="/config/openclaw.json.template" +CONFIG="/tmp/openclaw.json" + +if [ ! -f "$TEMPLATE" ]; then + echo "ERROR: Config template not found at $TEMPLATE" + echo "Mount your config volume at /config with a .json.template file" + exit 1 +fi + +# Validate required env vars +: "${OPENCLAW_GATEWAY_TOKEN:?OPENCLAW_GATEWAY_TOKEN is required (generate: openssl rand -hex 32)}" + +# Render template with env var substitution +envsubst < "$TEMPLATE" > "$CONFIG" + +# If OLLAMA_BASE_URL is set, inject Ollama provider into config +if [ -n "$OLLAMA_BASE_URL" ]; then + # Use python3 if available, fall back to node + if command -v python3 >/dev/null 2>&1; then + python3 -c " +import json, sys +with open('$CONFIG') as f: cfg = json.load(f) +cfg.setdefault('models', {})['mode'] = 'merge' +cfg['models'].setdefault('providers', {})['ollama'] = { + 'baseUrl': '$OLLAMA_BASE_URL/v1', + 'api': 'openai-completions', + 'models': [{'id': '${OLLAMA_MODEL:-cogito}', 'name': '${OLLAMA_MODEL:-cogito} (Local)', 'reasoning': False, 'input': ['text'], 'cost': {'input':0,'output':0,'cacheRead':0,'cacheWrite':0}, 'contextWindow': 128000, 'maxTokens': 8192}] +} +with open('$CONFIG','w') as f: json.dump(cfg, f, indent=2) +" + echo "Ollama provider added: $OLLAMA_BASE_URL (model: ${OLLAMA_MODEL:-cogito})" + elif command -v node >/dev/null 2>&1; then + node -e " +const fs = require('fs'); +const cfg = JSON.parse(fs.readFileSync('$CONFIG','utf8')); +cfg.models = cfg.models || {}; cfg.models.mode = 'merge'; +cfg.models.providers = cfg.models.providers || {}; +cfg.models.providers.ollama = {baseUrl:'$OLLAMA_BASE_URL/v1',api:'openai-completions',models:[{id:'${OLLAMA_MODEL:-cogito}',name:'${OLLAMA_MODEL:-cogito} (Local)',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:128000,maxTokens:8192}]}; +fs.writeFileSync('$CONFIG', JSON.stringify(cfg, null, 2)); +" + echo "Ollama provider added: $OLLAMA_BASE_URL (model: ${OLLAMA_MODEL:-cogito})" + else + echo "WARNING: OLLAMA_BASE_URL set but no python3/node available to inject provider" + fi +fi + +export OPENCLAW_CONFIG_PATH="$CONFIG" +exec openclaw gateway run --bind lan --auth token "$@" diff --git a/docker/openclaw-instances/jarvis-main.env b/docker/openclaw-instances/jarvis-main.env deleted file mode 100644 index 719d4bc..0000000 --- a/docker/openclaw-instances/jarvis-main.env +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 5feeae9..0000000 --- a/docker/openclaw-instances/jarvis-main.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "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 deleted file mode 100644 index 719d4bc..0000000 --- a/docker/openclaw-instances/jarvis-operations.env +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 87ce1af..0000000 --- a/docker/openclaw-instances/jarvis-operations.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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 deleted file mode 100644 index 719d4bc..0000000 --- a/docker/openclaw-instances/jarvis-projects.env +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 71c8c00..0000000 --- a/docker/openclaw-instances/jarvis-projects.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "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 deleted file mode 100644 index 719d4bc..0000000 --- a/docker/openclaw-instances/jarvis-research.env +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 71c8c00..0000000 --- a/docker/openclaw-instances/jarvis-research.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "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/mosaic-main.env b/docker/openclaw-instances/mosaic-main.env new file mode 100644 index 0000000..81484ff --- /dev/null +++ b/docker/openclaw-instances/mosaic-main.env @@ -0,0 +1,14 @@ +# Mosaic Agent: main +# Fill in all values before deploying. + +# Required: LLM provider API key (Z.ai, OpenAI, etc.) +ZAI_API_KEY= + +# Required: unique bearer token for this agent instance (generate: openssl rand -hex 32) +OPENCLAW_GATEWAY_TOKEN= + +# Optional: override default model (default: zai/glm-5) +# OPENCLAW_MODEL=zai/glm-5 + +# Optional: Ollama endpoint for local inference (uncomment to enable) +# OLLAMA_BASE_URL= diff --git a/docker/openclaw-instances/mosaic-main.json.template b/docker/openclaw-instances/mosaic-main.json.template new file mode 100644 index 0000000..c6aaa57 --- /dev/null +++ b/docker/openclaw-instances/mosaic-main.json.template @@ -0,0 +1,19 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "${OPENCLAW_MODEL:-zai/glm-5}" } + } + } +} diff --git a/docker/openclaw-instances/mosaic-operations.env b/docker/openclaw-instances/mosaic-operations.env new file mode 100644 index 0000000..4167acb --- /dev/null +++ b/docker/openclaw-instances/mosaic-operations.env @@ -0,0 +1,14 @@ +# Mosaic Agent: operations +# Fill in all values before deploying. + +# Required: LLM provider API key (Z.ai, OpenAI, etc.) +ZAI_API_KEY= + +# Required: unique bearer token for this agent instance (generate: openssl rand -hex 32) +OPENCLAW_GATEWAY_TOKEN= + +# Optional: override default model (default: zai/glm-5) +# OPENCLAW_MODEL=zai/glm-5 + +# Optional: Ollama endpoint for local inference (uncomment to enable) +# OLLAMA_BASE_URL= diff --git a/docker/openclaw-instances/mosaic-operations.json.template b/docker/openclaw-instances/mosaic-operations.json.template new file mode 100644 index 0000000..c6aaa57 --- /dev/null +++ b/docker/openclaw-instances/mosaic-operations.json.template @@ -0,0 +1,19 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "${OPENCLAW_MODEL:-zai/glm-5}" } + } + } +} diff --git a/docker/openclaw-instances/mosaic-projects.env b/docker/openclaw-instances/mosaic-projects.env new file mode 100644 index 0000000..ecffe39 --- /dev/null +++ b/docker/openclaw-instances/mosaic-projects.env @@ -0,0 +1,14 @@ +# Mosaic Agent: projects +# Fill in all values before deploying. + +# Required: LLM provider API key (Z.ai, OpenAI, etc.) +ZAI_API_KEY= + +# Required: unique bearer token for this agent instance (generate: openssl rand -hex 32) +OPENCLAW_GATEWAY_TOKEN= + +# Optional: override default model (default: zai/glm-5) +# OPENCLAW_MODEL=zai/glm-5 + +# Optional: Ollama endpoint for local inference (uncomment to enable) +# OLLAMA_BASE_URL= diff --git a/docker/openclaw-instances/mosaic-projects.json.template b/docker/openclaw-instances/mosaic-projects.json.template new file mode 100644 index 0000000..c6aaa57 --- /dev/null +++ b/docker/openclaw-instances/mosaic-projects.json.template @@ -0,0 +1,19 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "${OPENCLAW_MODEL:-zai/glm-5}" } + } + } +} diff --git a/docker/openclaw-instances/mosaic-research.env b/docker/openclaw-instances/mosaic-research.env new file mode 100644 index 0000000..da2f9f9 --- /dev/null +++ b/docker/openclaw-instances/mosaic-research.env @@ -0,0 +1,14 @@ +# Mosaic Agent: research +# Fill in all values before deploying. + +# Required: LLM provider API key (Z.ai, OpenAI, etc.) +ZAI_API_KEY= + +# Required: unique bearer token for this agent instance (generate: openssl rand -hex 32) +OPENCLAW_GATEWAY_TOKEN= + +# Optional: override default model (default: zai/glm-5) +# OPENCLAW_MODEL=zai/glm-5 + +# Optional: Ollama endpoint for local inference (uncomment to enable) +# OLLAMA_BASE_URL= diff --git a/docker/openclaw-instances/mosaic-research.json.template b/docker/openclaw-instances/mosaic-research.json.template new file mode 100644 index 0000000..c6aaa57 --- /dev/null +++ b/docker/openclaw-instances/mosaic-research.json.template @@ -0,0 +1,19 @@ +{ + "gateway": { + "mode": "local", + "port": 18789, + "bind": "lan", + "auth": { "mode": "token" }, + "http": { + "endpoints": { + "chatCompletions": { "enabled": true } + } + } + }, + "agents": { + "defaults": { + "workspace": "/home/node/workspace", + "model": { "primary": "${OPENCLAW_MODEL:-zai/glm-5}" } + } + } +} -- 2.49.1 From 8d7a1be7f5e666aaa653e20323d6d55e4d7d7810 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 08:06:15 -0600 Subject: [PATCH 5/5] fix(docker): strip hardcoded model/provider assumptions from fleet doc Model choices and provider prereqs belong in onboarding/settings, not static documentation. --- docker/OPENCLAW-FLEET.md | 115 +++++++++++++-------------------------- 1 file changed, 37 insertions(+), 78 deletions(-) diff --git a/docker/OPENCLAW-FLEET.md b/docker/OPENCLAW-FLEET.md index ebc0564..78b643f 100644 --- a/docker/OPENCLAW-FLEET.md +++ b/docker/OPENCLAW-FLEET.md @@ -1,90 +1,49 @@ -# OpenClaw Agent Fleet +# Mosaic Agent Fleet -OpenClaw multi-agent deployment for Mosaic Stack using Docker Swarm and Portainer. +Multi-agent deployment for Mosaic Stack using OpenClaw containers on Docker Swarm. -## Agent Roster +## Architecture -| Agent | Service | Primary Model | Role | -| ----------------- | ------------------- | --------------- | ---------------------------------- | -| mosaic-main | `mosaic-main` | `zai/glm-5` | Orchestrator / user-facing gateway | -| mosaic-projects | `mosaic-projects` | `zai/glm-5` | Development and coding tasks | -| mosaic-research | `mosaic-research` | `zai/glm-5` | Research and web search | -| mosaic-operations | `mosaic-operations` | `ollama/cogito` | Monitoring, health checks, alerts | +Each agent runs as an isolated OpenClaw Gateway instance with its own: + +- **Workspace** — persistent volume for agent files and memory +- **State** — persistent volume for auth tokens and sessions +- **Config** — template rendered at startup from environment variables + +Agents communicate with the Mosaic API via the OpenAI-compatible +`/v1/chat/completions` endpoint. The Mosaic WebUI routes chat requests +to agents through the `OpenClawGatewayModule`. + +## Default Agent Roles + +| Agent | Role | Description | +| ----------------- | ------------ | ------------------------------------------- | +| mosaic-main | Orchestrator | User-facing gateway, routes to other agents | +| mosaic-projects | Developer | Implementation, coding, PRs | +| mosaic-research | Research | Web search, analysis, discovery | +| mosaic-operations | Operations | Monitoring, health checks, alerts | + +> **Models and providers are configured per-deployment** via environment +> variables and the Mosaic Settings UI — not hardcoded in these files. +> See the [Setup Guide](openclaw-instances/README.md) for env var reference. ## 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 +- Docker Swarm initialized on target host +- Mosaic Stack running (`mosaic-stack_internal` network available) +- At least one LLM provider API key (Z.ai, OpenAI, Anthropic, etc.) ## Quick Start -### 1. Configure each agent env file +1. **Configure** — Fill in `docker/openclaw-instances/*.env` files +2. **Deploy** — `docker stack deploy -c docker/openclaw-compose.yml mosaic-agents` +3. **Auth** — If needed, run `openclaw auth` inside a container (or via Mosaic terminal) +4. **Verify** — `docker stack services mosaic-agents` -Set values in: +See [openclaw-instances/README.md](openclaw-instances/README.md) for detailed setup. -- `docker/openclaw-instances/mosaic-main.env` -- `docker/openclaw-instances/mosaic-projects.env` -- `docker/openclaw-instances/mosaic-research.env` -- `docker/openclaw-instances/mosaic-operations.env` +## Future: Onboarding Wizard -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 mosaic -``` - -### 4. Verify service status - -```bash -docker stack services mosaic -docker service logs mosaic-mosaic-main --tail 100 -docker service logs mosaic-mosaic-projects --tail 100 -docker service logs mosaic-mosaic-research --tail 100 -docker service logs mosaic-mosaic-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=mosaic-mosaic-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 mosaic` | Deploy/update fleet | -| `docker stack services mosaic` | List services in stack | -| `docker service logs mosaic-` | View service logs | -| `docker service update --force mosaic-` | Restart rolling update | -| `docker service scale mosaic-=N` | Scale a service | -| `docker stack rm mosaic` | 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. +Model assignments, provider configuration, and agent customization will be +managed through the Mosaic WebUI onboarding wizard and Settings pages (MS22-P4). +Until then, use environment variables per the README. -- 2.49.1