{ // Adolf P6 — OpenClaw gateway config for the "adolf" container. // Lives in the adolf-state VOLUME (mounted at /home/node/.openclaw), not // in the openai/ git repo. Secrets referenced below (${VAR}) are resolved // from this container's process env, itself sourced from openai/.env // (gitignored) via docker-compose.yml — never inlined here. gateway: { mode: "local", auth: { // Compose already binds "lan" (0.0.0.0) and publishes 18789/18790 to // the host, so this is a non-loopback bind and auth is mandatory. // Shared-secret token auth also gives the openclaw-tools bridge // (which calls POST /tools/invoke with the same token) full // trusted-operator scope, which is what lets gateway.tools.allow // below actually unlock cron/nodes for it. mode: "token", token: "${OPENCLAW_GATEWAY_TOKEN}", }, tools: { // cron and nodes are owner-only and hard-denied on the HTTP // /tools/invoke surface by default. The openclaw-tools MCP bridge // (P5) calls that surface for cron_create/cron_list/nodes_invoke, so // without this allow-list those tools 404 even with a valid token. allow: ["cron", "nodes"], }, }, // Model provider: adolf-llm (P2/P4), the Kimi-CLI OpenAI-compatible // wrapper on :8010. Its HTTP server (openai/adolf-llm/server.js) performs // NO api-key/Authorization validation at all -- ADOLF_KEY's value is // functionally irrelevant to adolf-llm itself. It's still wired through // env (not hardcoded) because OpenClaw's custom-provider schema requires // a non-empty apiKey field and ${VAR} substitution fails closed on an // empty/missing var. models: { mode: "merge", providers: { "adolf-llm": { baseUrl: "http://adolf-llm:8010/v1", apiKey: "${ADOLF_KEY}", api: "openai-completions", // Margin above the server.js SSE heartbeat cadence (empty-content // keepalive delta every ~25s once idle) so the idle watchdog never // fires on long thinking/tool/MCP phases even if a heartbeat tick // is delayed (kb #71). Raised to 10min: long agentic turns (Cognee // tool-loops / recalls up to 150s each) were producing no *content* // progress for >300s, tripping "no response from model" and surfacing // an error before the agent finished. The wrapper now also kills the // kimi child on disconnect, so an over-timeout turn no longer orphans. timeoutSeconds: 600, models: [ { id: "adolf", name: "Adolf", input: ["text", "image"] }, ], }, }, }, agents: { defaults: { model: "adolf-llm/adolf", }, }, ui: { assistant: { name: "Adolf" }, }, // Matrix channel (P6). Deliberately no accessToken/userId/password here: // MATRIX_HOMESERVER / MATRIX_USER_ID / MATRIX_PASSWORD / MATRIX_DEVICE_NAME // are config-key-backed env vars OpenClaw reads automatically when the // matching config key is unset, so real creds never touch this file or // git. Password auth (not the raw access token) mints Adolf its own fresh // Matrix device instead of reusing the existing matrixbot (Python/nio) // device -- see the P6 report for why that separation matters. // // Conservative defaults on purpose: dm "pairing" (owner must approve // unknown senders) and groupPolicy "disabled" (no room handling yet). // Revisit once the legacy matrixbot adapter is retired. channels: { matrix: { enabled: true, encryption: true, dm: { policy: "allowlist", allowFrom: ["@admin:mtx.alogins.net", "@elizaveta:mtx.alogins.net"] }, groupPolicy: "disabled", autoJoin: "always", // mtx.alogins.net is deliberately mapped to the host-gateway (private) // IP via extra_hosts in docker-compose.yml to dodge a hairpin-NAT dead // end on the public route -- not an actual SSRF exposure, so opt out // of the private-network block for this trusted, self-owned homeserver. network: { dangerouslyAllowPrivateNetwork: true }, }, }, // MCP registry (P6) -- same servers as openai/shared-mcp.json, // expressed in OpenClaw's own mcp.servers schema. `type: "http"` is // OpenClaw's documented CLI-native alias for transport: "streamable-http". mcp: { servers: { hindsight: { type: "http", url: "http://hindsight:8888/mcp/adolf/", }, "openclaw-tools": { type: "http", url: "http://openclaw-tools:8020/mcp", }, // kanboard-mcp-adolf (kb task #58) -- standalone kanboard-mcp image // (source /home/alvis/kanboard/mcp), second instance on :3104, // authenticated as the Kanboard "adolf" user via its own personal API // access token (KANBOARD_AUTH_USER=adolf in that instance's // .env.adolf) -- a genuinely distinct credential from the "claude" // instance on :3103 (app-wide jsonrpc token). Not part of the openai // compose network, so reached via host.docker.internal (already // extra_hosts-mapped for this container) rather than a service name. kanboard: { type: "http", url: "http://host.docker.internal:3104/mcp", }, // marketplace-mcp (kb task #61) -- the SAME shared marketplace-mcp // instance Claude Code and OpenWebUI use (single service, port 3101, // network_mode: host on the Agap host), not a second copy. It can // place real orders on live marketplace accounts, so it's gated by a // shared bearer token (MARKETPLACE_MCP_TOKEN in Vaultwarden / this // container's env, injected via openai/.env -> docker-compose.yml). // Reached via host.docker.internal, same reasoning as kanboard above. marketplace: { type: "http", url: "http://host.docker.internal:3101/mcp", headers: { Authorization: "Bearer ${MARKETPLACE_MCP_TOKEN}", }, }, }, }, // Memory plugins (P8). Activation entry is required for the gateway to // load a plugin at startup (discovery alone is not enough). plugins: { entries: { // Cognee memory plugin — DISABLED as of kb #75 (H3): superseded by // hindsight-memory below. Left `enabled: false` rather than removed — // full teardown (plugin dir, cognee/cognee-mcp/cognee-llm containers) // is kb #75's H4, a separate step so the Hindsight path can be proven // out first. Kept disabled (not both active) to avoid double // prependContext injection and double persisted turns while both // backends exist side by side. "cognee-memory": { enabled: false, // External (non-bundled) plugins must opt in to conversation + prompt-injection // hook access explicitly. before_prompt_build => allowPromptInjection; // agent_end => allowConversationAccess. hooks: { allowConversationAccess: true, allowPromptInjection: true }, // Throttle background cognify hard to stop it draining the Kimi quota: // one cognify per dataset per hour (was 5 min default), and a longer // persist timeout so /add doesn't fail-and-retry when cognee is busy. config: { sweepIntervalMs: 3600000, // 1h (default 300000 = 5min) persistTimeoutMs: 20000, // 20s (default 8000) }, }, // Hindsight memory plugin (kb #75, H3) — installed external plugin // under .openclaw/extensions/hindsight-memory, bind-mounted read-only // from openai/hindsight-openclaw-plugin (see that project's // docker-compose.yml adolf.volumes). Structural successor to // cognee-memory above: LLM-free recall inject (before_prompt_build) + // async retain (agent_end) against the hindsight service, bank // "adolf" (same bank the mcp.servers.hindsight tool surface above // uses, so hook-based and tool-based memory stay one consistent // store). No cognify/sweep config here — Hindsight's retain does // extraction/consolidation server-side, so that whole class of // config (sweepIntervalMs etc. above) doesn't apply. "hindsight-memory": { enabled: true, // Same opt-in requirement as cognee-memory above: before_prompt_build // => allowPromptInjection; agent_end => allowConversationAccess. hooks: { allowConversationAccess: true, allowPromptInjection: true }, }, // Kimi quota readout (kb #62) — installed external plugin, bind-mounted // read-only from openai/quota-command-openclaw-plugin (see that // project's docker-compose.yml adolf.volumes) onto // .openclaw/extensions/quota-command. Registers a `/quota` native // command; no hooks, so no allowConversationAccess/allowPromptInjection // opt-in needed. "quota-command": { enabled: true, }, }, }, }