{
  // 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}",
        },
      },
      // agap-mcp (kb#64) -- the SAME shared agap-mcp instance Claude Code uses
      // (network_mode: host, :3100, unauthenticated on localhost). Grants Adolf
      // the same access as Claude: vault (vw_* for credential fetching) plus
      // gitea/ha/zabbix/radicale. Reached via host.docker.internal like
      // kanboard/marketplace above.
      agap: {
        type: "http",
        url: "http://host.docker.internal:3100/mcp",
      },
    },
  },

  // Memory plugins (P8). Activation entry is required for the gateway to
  // load a plugin at startup (discovery alone is not enough).
  plugins: {
    entries: {
      // 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,
      },
    },
  },
}
