# kb#220: dir renamed openai/ -> ai/ (nothing in it is OpenAI). Pin the # compose project name explicitly so container/network/volume names # (e.g. openai_adolf-state) stay stable across the rename -- otherwise # Compose derives the project name from the directory basename and the # rename would orphan the existing volume/network. name: openai services: litellm-db: image: postgres:16-alpine container_name: litellm-db environment: - POSTGRES_DB=litellm - POSTGRES_USER=litellm - POSTGRES_PASSWORD=litellm volumes: - /mnt/ssd/dbs/litellm/postgres:/var/lib/postgresql/data restart: always # kb#190: cheap connectivity probe, no query load. healthcheck: test: ["CMD-SHELL", "pg_isready -U litellm -d litellm"] interval: 10s timeout: 5s retries: 5 start_period: 10s litellm: image: ghcr.io/berriai/litellm:main-latest container_name: litellm ports: - "4000:4000" volumes: - ./litellm-config.yaml:/app/config.yaml environment: - DATABASE_URL=postgresql://litellm:litellm@litellm-db:5432/litellm - LITELLM_MASTER_KEY=sk-fjQC1BxAiGFSMs - LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY:-changeme} - LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY:-changeme} - LANGFUSE_HOST=http://langfuse:3000 - OPENROUTER_API_KEY=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 command: ["--config", "/app/config.yaml", "--port", "4000"] extra_hosts: - "host.docker.internal:host-gateway" depends_on: litellm-db: condition: service_healthy langfuse: condition: service_healthy restart: always # kb#190: /health/liveliness is litellm's cheap liveness probe (no # provider/model call), unlike /health which pings every configured model. healthcheck: test: ["CMD-SHELL", "node -e \"fetch('http://localhost:4000/health/liveliness').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""] interval: 15s timeout: 10s retries: 5 start_period: 20s # kimi-agent — REMOVED 2026-08-01 (Kimi purge). Was the only large-tier # deployment behind LiteLLM; `tier-large`, the auto_router complex route and # their fallbacks now point at the codex-backed adolf-llm wrapper instead # (litellm-config.yaml model_name: codex-agent). The kimi-agent-home volume # and /home/alvis/kimi-workspace are left on disk deliberately — drop them # once the Codex path has proven itself. langfuse-db: image: postgres:16-alpine container_name: langfuse-db environment: - POSTGRES_DB=langfuse - POSTGRES_USER=langfuse - POSTGRES_PASSWORD=langfuse volumes: - /mnt/ssd/dbs/langfuse/postgres:/var/lib/postgresql/data restart: always # kb#190: cheap connectivity probe, no query load. healthcheck: test: ["CMD-SHELL", "pg_isready -U langfuse -d langfuse"] interval: 10s timeout: 5s retries: 5 start_period: 10s # kb#148 (A2A-16): Langfuse v3 split the monolith into langfuse-web + # langfuse-worker, and added ClickHouse (event/analytics store), Redis # (queue) and S3-compatible blob storage (MinIO here) as hard # dependencies -- Postgres alone is no longer sufficient, unlike v2. # NOT YET ACTIVATED: v2's existing trace history (3175+ traces per # DESIGN-a2a-agents.md §7, confirmed live 2026-07-26) lives only in the # langfuse-db Postgres volume in v2's schema. Langfuse's official v2->v3 # upgrade path requires running the migration entrypoint once against # this data (langfuse/langfuse:3's container runs pending Postgres # migrations automatically on boot, but the ClickHouse backfill of # historical trace data is a separate, explicit step -- see Langfuse's # "Upgrade from v2 to v3" guide) BEFORE cutting traffic over, or the old # traces are stranded. That migration is a live-data operation with real # downtime and rollback risk, so it is out of scope for an unattended # edit -- see kb#148's report for the exact handoff commands. New # volumes (clickhouse/minio/redis below) also need their host dirs # created + chowned first (root-gated, same pattern as kb#87's # hindsight-cache dir). langfuse-worker: image: docker.io/langfuse/langfuse-worker:3 container_name: langfuse-worker depends_on: &langfuse-depends-on langfuse-db: condition: service_healthy langfuse-minio: condition: service_healthy langfuse-redis: condition: service_healthy langfuse-clickhouse: condition: service_healthy environment: &langfuse-worker-env NEXTAUTH_URL: https://lf.alogins.net DATABASE_URL: postgresql://langfuse:langfuse@langfuse-db:5432/langfuse SALT: 7927b3b0092afe4542274940b557becea6418a5fed79f7acd25c3a789349fdc9 ENCRYPTION_KEY: 12056e4e3cf5b9d936fedca267d4bd877a4b79fb9ff0ff32859a623d5e96c814 CLICKHOUSE_MIGRATION_URL: clickhouse://langfuse-clickhouse:9000 CLICKHOUSE_URL: http://langfuse-clickhouse:8123 CLICKHOUSE_USER: clickhouse CLICKHOUSE_PASSWORD: f1d3bd6dc01c9741b99c633b2e167d1d LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse LANGFUSE_S3_EVENT_UPLOAD_REGION: auto LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: minio LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: 078dd39aada907ab40c6a4d581033cfe LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://langfuse-minio:9000 LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true" LANGFUSE_S3_EVENT_UPLOAD_PREFIX: events/ LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: langfuse LANGFUSE_S3_MEDIA_UPLOAD_REGION: auto LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: minio LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: 078dd39aada907ab40c6a4d581033cfe LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://langfuse-minio:9000 LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: "true" LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: media/ REDIS_HOST: langfuse-redis REDIS_PORT: "6379" REDIS_AUTH: 36471006ce5b95ed4f7fb769788fe91c restart: always langfuse: image: docker.io/langfuse/langfuse:3 container_name: langfuse depends_on: *langfuse-depends-on ports: - "3200:3000" environment: <<: *langfuse-worker-env NEXTAUTH_SECRET: 532a746b24ac40afa39f9d317031cab94d4d6881107ea3b1209b28020f1a9761 AUTH_DISABLE_SIGNUP: "true" LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: https://lf.alogins.net restart: always # kb#190: langfuse's Next.js server binds the container's bridge IP, # NOT 127.0.0.1/localhost (confirmed via `ss -tlnp` inside the # container: 127.0.0.1 connection is refused) -- so the probe must # address it by its own compose DNS name, which resolves to that same # bridge IP from inside the container. healthcheck: test: ["CMD-SHELL", "wget -q -O- http://langfuse:3000/api/public/health || exit 1"] interval: 15s timeout: 10s retries: 5 start_period: 30s langfuse-clickhouse: image: docker.io/clickhouse/clickhouse-server:25.12 container_name: langfuse-clickhouse user: "101:101" environment: - CLICKHOUSE_DB=default - CLICKHOUSE_USER=clickhouse - CLICKHOUSE_PASSWORD=f1d3bd6dc01c9741b99c633b2e167d1d volumes: - /mnt/ssd/dbs/langfuse/clickhouse-data:/var/lib/clickhouse - /mnt/ssd/dbs/langfuse/clickhouse-logs:/var/log/clickhouse-server restart: always healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 interval: 5s timeout: 5s retries: 10 start_period: 1s langfuse-minio: image: cgr.dev/chainguard/minio container_name: langfuse-minio entrypoint: sh # create the 'langfuse' bucket before starting the service command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' environment: - MINIO_ROOT_USER=minio - MINIO_ROOT_PASSWORD=078dd39aada907ab40c6a4d581033cfe volumes: - /mnt/ssd/dbs/langfuse/minio:/data restart: always healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 1s timeout: 5s retries: 5 start_period: 1s langfuse-redis: image: docker.io/redis:7 container_name: langfuse-redis command: > --requirepass 36471006ce5b95ed4f7fb769788fe91c --maxmemory-policy noeviction volumes: - /mnt/ssd/dbs/langfuse/redis:/data restart: always healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 3s timeout: 10s retries: 10 qdrant: image: qdrant/qdrant container_name: qdrant ports: - "6333:6333" - "6334:6334" restart: always volumes: - /mnt/ssd/dbs/qdrant:/qdrant/storage:z # GPU residency decision (kb#191, 2026-07-26, DESIGN-a2a-agents.md sec 3b): # the 8GB GTX 1070 only has ~1.7GB free with the never-evict set (bge-m3 + # tei-reranker) resident alongside gemma3:4b -- no room for a 4th GPU # tenant without risking evicting the reranker (silent Hindsight recall # breakage). Runs CPU-only until the card gets more headroom. Never # started yet -- kb#175 (Adolf STT) was parked waiting on this call. faster-whisper: image: fedirz/faster-whisper-server:latest-cuda container_name: faster-whisper ports: - "8880:8000" environment: - WHISPER__MODEL=deepdml/faster-whisper-large-v3-turbo-ct2 - WHISPER__INFERENCE_DEVICE=cpu - WHISPER__COMPUTE_TYPE=int8 - WHISPER__LANGUAGE=ru volumes: - /mnt/ssd/ai/faster-whisper:/root/.cache/huggingface restart: always silero-tts: build: ./silero-tts container_name: silero-tts ports: - "8881:8881" volumes: - /mnt/ssd/ai/silero-tts:/cache/torch restart: always pipecat: build: ./pipecat container_name: pipecat ports: - "8882:8882" environment: - LIVEKIT_URL=ws://host.docker.internal:7880 - LIVEKIT_PUBLIC_URL=wss://lk.alogins.net - LIVEKIT_API_KEY=devkey - LIVEKIT_SECRET=ef3ef4b903ca8469b09b2dd7ab6af529c4d2f3c95668f53832fc351cf67777a9 - ADOLF_URL=http://host.docker.internal:8000/v1 - STT_URL=http://host.docker.internal:8880/v1 - TTS_URL=http://host.docker.internal:8881/v1 - STT_MODEL=deepdml/faster-whisper-large-v3-turbo-ct2 - TTS_VOICE=onyx extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped # Adolf — OpenClaw fork (Matrix-first personal assistant). The OpenClaw # gateway config (Matrix channel + allow-list, model provider -> # adolf-llm:8010, MCP registry, gateway.tools.allow for cron/nodes) is # version-controlled at agap_git/adolf/openclaw.json (repo root, alongside # this ai/ project, not nested inside it) and bind-mounted read-only # over the adolf-state volume (see volumes below), so git is the single # source of truth — not a hand-edited volume file. The volume still # holds runtime state only (Matrix crypto/devices, credentials, sessions, # workspace/SOUL.md, logs). Matrix creds and ADOLF_KEY come from # ai/.env (gitignored, never committed). Source tree: /home/alvis/adolf. # To change config: edit ../adolf/openclaw.json + restart adolf. adolf: build: context: ../../adolf # Matrix is opt-in at build time (see adolf/Dockerfile); without this, # the gateway logs "no-channel-owner" and channels.matrix is inert. args: OPENCLAW_EXTENSIONS: matrix image: adolf:local container_name: adolf environment: - HOME=/home/node - OPENCLAW_HOME=/home/node - OPENCLAW_STATE_DIR=/home/node/.openclaw - OPENCLAW_CONFIG_PATH=/home/node/.openclaw/openclaw.json - OPENCLAW_CONFIG_DIR=/home/node/.openclaw - OPENCLAW_WORKSPACE_DIR=/home/node/.openclaw/workspace - OPENCLAW_GATEWAY_TOKEN=${ADOLF_GATEWAY_TOKEN:-} - ADOLF_KEY=${ADOLF_KEY:-} - MATRIX_HOMESERVER=${MATRIX_HOMESERVER:-} - MATRIX_USER_ID=${MATRIX_USER_ID:-} # kb#67: stable token + device_id pin, so restarts reuse the existing # Matrix device (matrix-sdk/OpenClaw's own credential cache in the # adolf-state volume already does this across restarts -- see # extensions/matrix/src/matrix/client/config.ts resolveMatrixAuth -- # but that cache lives in the volume, so a lost/rebuilt volume would # fall through to MATRIX_PASSWORD and mint a brand-new device with no # cross-signing. Setting the token here removes that dependency). # MATRIX_PASSWORD stays configured as a manual-recovery fallback only: # it is never used while MATRIX_ACCESS_TOKEN resolves to a valid token. - MATRIX_ACCESS_TOKEN=${MATRIX_ACCESS_TOKEN:-} - MATRIX_DEVICE_ID=${MATRIX_DEVICE_ID:-} - MATRIX_PASSWORD=${MATRIX_PASSWORD:-} - MATRIX_DEVICE_NAME=${MATRIX_DEVICE_NAME:-Adolf OpenClaw Gateway} # marketplace-mcp bearer token (kb task #61) -- referenced by # openclaw.json's mcp.servers.marketplace.headers.Authorization via # ${MARKETPLACE_MCP_TOKEN} substitution; never inlined into that file. - MARKETPLACE_MCP_TOKEN=${MARKETPLACE_MCP_TOKEN:-} # agap-mcp bearer token (kb#180) -- agap-mcp's :3100 listener requires # `Authorization: Bearer ` on every route now (DESIGN §4: no # unauthenticated JSON-RPC listener; :3100 is host-networked and the # LAN carries VPN-terminated peers). Referenced by openclaw.json's # mcp.servers.agap.headers.Authorization via ${AGAP_MCP_TOKEN} # substitution, and read directly by the todoist-capture plugin's # /capture-idea POST. The token must map to agent id `adolf` in # agap-mcp's AGAP_MCP_AGENT_TOKENS. Sourced from ai/.env # (gitignored); never inlined here. - AGAP_MCP_TOKEN=${AGAP_MCP_TOKEN:-} - TZ=Europe/Riga volumes: # kb#219: permanent host mount, replacing the named Docker volume # (openai_adolf-state) so Adolf's state is inspectable/backup-able on # the host like every other Agap service (hindsight, litellm, qdrant, # langfuse, ... all live under /mnt/ssd/dbs/). Runtime state # only (Matrix crypto/devices, credentials, sessions, workspace, # logs). The gateway config file and personas are overlaid below. # Migration: agap_git/ai/migrate-adolf-state.sh (copies the old # openai_adolf-state volume here; run + verified before this bind # mount is activated — see kb#219). # NOT YET ACTIVE (2026-07-31): migrate-adolf-state.sh has not been run, # so /mnt/ssd/dbs/adolf/ is empty and binding it starts Adolf with a # clobbered config. Reverted to the named volume until kb#219's root # setup + migration is done; re-swap these six lines then. - adolf-state:/home/node/.openclaw # kb#219: config binds folded from two scattered locations # (agap_git/adolf/openclaw.json + four agap_git/ai/*-plugin dirs) # into one coherent home, /mnt/ssd/dbs/adolf/config/. Git remains the # single source of truth for content — these are symlinks back to the # tracked agap_git paths (created by the root setup block in kb#219's # report), not copies, so "edit the tracked file + restart" still # applies unchanged. Only the mount *path* changed from five spread # locations to one directory tree. # # Version-controlled OpenClaw gateway config, mounted read-only on top # of the state mount so it is the single source of truth. The gateway # reads this JSONC file and snapshots its own .last-good/.rejected # copies into the state dir (writable) — it never rewrites this file, # so read-only is safe. - ../adolf/openclaw.json:/home/node/.openclaw/openclaw.json:ro # quota-command plugin (kb #62) — same read-only-bind pattern as # openclaw.json above, applied to a single external plugin dir. # Activated via plugins.entries.quota-command in openclaw.json. - ./quota-command-openclaw-plugin:/home/node/.openclaw/extensions/quota-command:ro # hindsight-memory plugin (kb #75, H3) — same pattern. Forced hooks # (before_prompt_build recall / agent_end retain) against the # hindsight service (see that service's block below), replacing # Cognee as Adolf's memory backend. Activated via # plugins.entries.hindsight-memory in openclaw.json. - ./hindsight-openclaw-plugin:/home/node/.openclaw/extensions/hindsight-memory:ro # codex-quota-footer plugin (kb #85) — same pattern. Appends the Codex # usage line to every outgoing reply via reply_payload_sending, reusing # quota-command's adolf-llm:8010/usage route. Activated via # plugins.entries.codex-quota-footer in openclaw.json (the mount path, # the plugin's own id and that entry key must all agree or the plugin # silently does not load). Renamed from kimi-quota-footer 2026-08-01. - ./codex-quota-footer-plugin:/home/node/.openclaw/extensions/codex-quota-footer:ro # todoist-capture plugin (kb#170 component 1) — same pattern. # Registers /idea (native command, zero Kimi calls); POSTs to # agap-mcp's /capture-idea (see agap-mcp/src/server.js + capture.js) # which does the actual bge-m3 classify + Todoist create. Activated # via plugins.entries.todoist-capture in openclaw.json. - ./todoist-capture-plugin:/home/node/.openclaw/extensions/todoist-capture:ro # kb#219 / kb#156: personas deploy read-only onto the mount from the # alvis/agent-personas gitea repo (commit 936f655 at time of writing) # via that repo's deploy/deploy-persona.sh, landing at # /mnt/ssd/dbs/adolf/personas/adolf/*.md. Overlaid individually onto # the corresponding workspace/*.md files so they stay read-only from # Adolf's side and are written only by a git deploy — "who changed # Adolf's soul" is answerable by `git log` in that repo. USER.md is # deliberately NOT deployed (alvis, 2026-07-30): Hindsight's per-human # bank (#153) is the single source of user facts now, USER.md was the # stale unused template. # (persona overlays deliberately not mounted until kb#219 lands — the # personas currently live inside the adolf-state volume's workspace/) extra_hosts: - "host.docker.internal:host-gateway" # mtx.alogins.net's public A record can't hairpin-NAT back through the # router from inside a container; route it to the host gateway instead, # matching matrix/docker-compose.yml's lk-jwt-service (same problem, # same fix). Caddy on the host terminates TLS on :443 and proxies to # synapse:8008. - "mtx.alogins.net:host-gateway" # Local *.alogins.net web services (family wiki / OtterWiki, РодоВики) — # same hairpin-NAT dodge: the public A record can't loop back through the # router from inside a container, so route the hostname to the host # gateway where Caddy terminates TLS on :443 and proxies to the service. # Lets Adolf's OpenClaw browser reach them with the real URL + the # Vaultwarden creds. Add more *.alogins.net hosts here as needed. - "family.alogins.net:host-gateway" - "wiki.alogins.net:host-gateway" cap_drop: - NET_RAW - NET_ADMIN security_opt: - no-new-privileges:true init: true ports: - "18789:18789" - "18790:18790" command: ["node", "dist/index.js", "gateway", "--bind", "lan", "--port", "18789"] restart: unless-stopped # hindsight-llm — standalone clone of cognee-llm (kb#76, H4 option B): the # dedicated Codex-CLI wrapper that is now Hindsight's LLM, so the whole cognee # stack (incl. cognee-llm) can be decommissioned. Own port (:8012) + own # codex volume; needs a one-time `codex login` seeded into # hindsight-llm-codex-home. Migrated off Kimi CLI 2026-07-31 for cost. hindsight-llm: build: ./hindsight-llm container_name: hindsight-llm environment: # Same OpenAI geo-block workaround as adolf-llm above — see the comment # there. This wrapper makes no MCP calls, but NO_PROXY still keeps # container-to-container traffic off the tunnel. - HTTPS_PROXY=http://host.docker.internal:56928 - HTTP_PROXY=http://host.docker.internal:56928 - NO_PROXY=localhost,127.0.0.1,::1,host.docker.internal,.alogins.net,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16 extra_hosts: # Needed to reach the host's xray proxy (:56928) for OpenAI egress. - "host.docker.internal:host-gateway" ports: - "8012:8012" volumes: - hindsight-llm-codex-home:/root/.codex restart: unless-stopped # kb#190: GET /v1/models is a static, no-inference route (see # hindsight-llm/server.js) -- cheap liveness probe. healthcheck: test: ["CMD-SHELL", "node -e \"fetch('http://localhost:8012/v1/models').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""] interval: 15s timeout: 10s retries: 5 start_period: 20s # tei-reranker — GPU cross-encoder rerank sidecar for Hindsight (kb#87). # Hindsight's recall reranker ran the multilingual jina-reranker-v2 on the # image's CPU-only torch; over the grown adolf bank (269 facts, ~81 rerank # candidates) a single recall pinned ~8 cores for ~183s, so the memory # plugin's 4s timeout skipped injection every time. The stock HF TEI GPU # image needs CUDA sm_75+; this box is a GTX 1070 (Pascal sm_61), so we serve # the SAME jina model via plain CUDA torch (Pascal-compatible) behind the # TEI-compatible /info + /rerank API that Hindsight's `tei` provider speaks. # Shares the GPU with ollama (~1GB fp16 here, ~5.6GB ollama peak, 8GB card). # Reuses the already-downloaded model from hindsight's HF cache (no re-DL). tei-reranker: build: ./tei-reranker container_name: tei-reranker runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=compute,utility - RERANKER_MODEL=jinaai/jina-reranker-v2-base-multilingual - RERANKER_DEVICE=cuda - HF_HOME=/root/.cache/huggingface volumes: - /mnt/ssd/dbs/hindsight-cache/huggingface:/root/.cache/huggingface ports: - "8014:80" restart: unless-stopped # kb#190: /info is TEI's own lightweight metadata endpoint (model name, # no rerank/inference call). Container has python3 only (no curl/wget). healthcheck: test: ["CMD-SHELL", "python3 -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:80/info',timeout=3).status==200 else 1)\""] interval: 15s timeout: 10s retries: 5 start_period: 30s # adolf-llm — conversational Codex-CLI wrapper (:8010), the model backend for # the Adolf OpenClaw gateway (P2). Real streaming (SSE), chat_id session-keying # + 1:1 `codex exec resume`, media, shared MCP via a generated # $CODEX_HOME/config.toml sourced from the shared-mcp.json contract # (cognee-mcp P4, openclaw-tools P5). Needs `codex login` in # adolf-llm-codex-home. # # Migrated off Kimi CLI 2026-07-31 for cost (Moonshot subscription retired in # favour of the existing ChatGPT plan). adolf-llm: build: ./adolf-llm container_name: adolf-llm environment: # marketplace-mcp bearer token (kb#61) -- shared-mcp.json's # "marketplace" entry references this by name via # `bearerTokenEnvVar: "MARKETPLACE_MCP_TOKEN"`, which adolf-llm's config # writer translates to Codex's own `bearer_token_env_var` key. Codex # reads process.env at request time, so the raw secret never sits in # the git-tracked shared-mcp.json -- same secret, same env-var pattern # already used for the `adolf` service's openclaw.json Layer-1 config # above (${MARKETPLACE_MCP_TOKEN} substitution), sourced from # ai/.env (gitignored, never committed). - MARKETPLACE_MCP_TOKEN=${MARKETPLACE_MCP_TOKEN:-} # agap-mcp bearer token (kb#180) -- same env-var pattern, referenced by # shared-mcp.json's "agap" entry via `bearerTokenEnvVar: # "AGAP_MCP_TOKEN"`. Without it the Codex backbone's agap tools all # fail with HTTP 401 once agap-mcp restarts with auth on. - AGAP_MCP_TOKEN=${AGAP_MCP_TOKEN:-} # OpenAI egress proxy (Codex migration, 2026-07-31). OpenAI geo-blocks # this host outright: a direct call returns HTTP 403 # `unsupported_country_region_territory`, so `codex login` and every model # call fail without this. Routed through the same xray proxy on the host # that Claude Code itself uses (:56928, listening on all interfaces); # reached from the container via the host-gateway alias below. # Codex is Rust/reqwest, which honours these vars natively. - HTTPS_PROXY=http://host.docker.internal:56928 - HTTP_PROXY=http://host.docker.internal:56928 # NO_PROXY is load-bearing, not cosmetic: without it ALL egress — # including MCP calls to hindsight/openclaw-tools/agap-mcp and the local # *.alogins.net services — would be tunnelled through xray, which is both # slow and likely to fail. Only OpenAI should take the tunnel. - NO_PROXY=localhost,127.0.0.1,::1,host.docker.internal,hindsight,openclaw-tools,.alogins.net,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16 ports: - "8010:8010" volumes: - adolf-llm-workspace:/workspace - adolf-llm-codex-home:/root/.codex - ./shared-mcp.json:/shared-mcp.json:ro extra_hosts: # Needed to reach kanboard-mcp-adolf (:3104, network_mode: host, outside # this compose project's network) via shared-mcp.json's "kanboard" # entry — same host-gateway trick used by adolf/cognee/pipecat above. - "host.docker.internal:host-gateway" # Local *.alogins.net web services: the Codex CLI's own web-fetch tool # runs IN THIS container, so it needs the same hairpin-NAT dodge as the # adolf gateway (the public A record can't loop back through the router). # Route to the host gateway where Caddy terminates TLS on :443. - "family.alogins.net:host-gateway" - "wiki.alogins.net:host-gateway" restart: unless-stopped # kb#190: GET /v1/models is a static, no-inference route (see # adolf-llm/server.js) -- cheap liveness probe, no Codex call/quota use. healthcheck: test: ["CMD-SHELL", "node -e \"fetch('http://localhost:8010/v1/models').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""] interval: 15s timeout: 10s retries: 5 start_period: 20s # hindsight — Adolf memory backend, replacing cognee/cognee-mcp/cognee-llm # (kb#73, migration doc agap_git/adolf/HINDSIGHT-MIGRATION.md, H1). One # container: REST API :8888 (also serves the built-in MCP at /mcp/{bank}/), # UI :9999, built-in Postgres (pg0) bind-mounted to # /mnt/ssd/dbs/hindsight/ (host dir created + chowned 1000:1000 to match # the image's non-root `hindsight` user, confirmed via # `docker run --entrypoint id`). # # LLM + embeddings reconfigured 2026-07-15 (kb#84) to fix two wrong H1 # choices for a Russian/multilingual use case: # # LLM -> hindsight-llm:8012 (dedicated Kimi-CLI wrapper cloned from the shim cognee # uses — see cognee/cognee.env's LLM section for the full precedent, # including why LLM_INSTRUCTOR_MODE=json_mode isn't needed here since # Hindsight's own client doesn't go through `instructor`). Replaces the # H1 choice of LiteLLM + ollama/gemma3:4b (a tiny local model): validated # 2026-07-15 that cognee-llm returns clean, JSON-parseable structured # extraction for Russian input (see kb#84 probe B) — gemma3:4b's fluency # on Russian was never actually verified, it was picked only to dodge # qwen3:8b's -token empty-content bug. Kimi is also the flat-rate # subscription already paid for, so this isn't a new cost. # # Embeddings -> ollama's bge-m3 on the GPU (host.docker.internal:11436, # separate compose project, same extra_hosts trick as cognee/adolf-llm # below), via ollama's OpenAI-compatible /v1/embeddings endpoint # (confirmed 200 + 1024-dim vector 2026-07-15, kb#84 probe A). Replaces # the H1 choice of Hindsight's built-in `local` provider # (BAAI/bge-small-en-v1.5, English-only, 384-d, CPU-bound in-process # SentenceTransformers). The hindsight image itself is CPU-only (torch # +cpu build, no onnxruntime GPU provider — confirmed 2026-07-15), so its # in-process local/onnx embedders can never reach the GPU; routing # through ollama's `openai` embeddings provider (HTTP, not the bespoke # cognee-style `ollama` provider Hindsight doesn't have) is how GPU # serving happens here. Dimensions var matches cognee.env's own bge-m3 # swap (kb#60): 1024. # # Runs ALONGSIDE cognee/cognee-mcp/cognee-llm during the migration; those # are untouched here and only decommissioned in H4, after H2/H3/H5 prove # this service out. Not yet wired into openclaw.json/shared-mcp.json # (that's H2, kb#74) — this block only stands the service up and proves # retain/recall against a throwaway bank. hindsight: image: ghcr.io/vectorize-io/hindsight:latest container_name: hindsight restart: unless-stopped environment: # ALL stages on the local model (alvis, 2026-07-26): retain/extraction # moved OFF Kimi (hindsight-llm:8012) onto ollama/gemma3:4b via LiteLLM, # joining consolidation + reflect which were already local. Kimi is no # longer in the Hindsight path at all, so the memory backend costs zero # quota and Adolf's 5h window is left entirely for conversation. # # ⚠️ Accepted tradeoff: the kb#88/kb#84 rationale for keeping retain on # Kimi was fact QUALITY — gemma3:4b's Russian fluency was never verified # (it was originally picked only to dodge qwen3:8b's -token bug), # and this bank's content is largely Russian. Watch extraction quality on # the next retains; if facts degrade, this is the first thing to revert. - HINDSIGHT_API_LLM_PROVIDER=openai - HINDSIGHT_API_LLM_BASE_URL=http://litellm:4000/v1 - HINDSIGHT_API_LLM_MODEL=ollama/gemma3:4b - HINDSIGHT_API_LLM_API_KEY=sk-fjQC1BxAiGFSMs - HINDSIGHT_API_CONSOLIDATION_LLM_PROVIDER=openai - HINDSIGHT_API_CONSOLIDATION_LLM_BASE_URL=http://litellm:4000/v1 - HINDSIGHT_API_CONSOLIDATION_LLM_MODEL=ollama/gemma3:4b - HINDSIGHT_API_CONSOLIDATION_LLM_API_KEY=sk-fjQC1BxAiGFSMs - HINDSIGHT_API_REFLECT_LLM_PROVIDER=openai - HINDSIGHT_API_REFLECT_LLM_BASE_URL=http://litellm:4000/v1 - HINDSIGHT_API_REFLECT_LLM_MODEL=ollama/gemma3:4b - HINDSIGHT_API_REFLECT_LLM_API_KEY=sk-fjQC1BxAiGFSMs - HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai - HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL=http://host.docker.internal:11436/v1 - HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=bge-m3 - HINDSIGHT_API_EMBEDDINGS_OPENAI_DIMENSIONS=1024 # ollama doesn't check this value at all (no auth), but the openai # embeddings client requires a non-empty key to construct. - HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=ollama # Stable worker id (compose service name), not the container hostname # default -- without this, recreating the container orphans any # in-flight async retain/consolidation tasks under the old hostname # (startup log warns about exactly this). - HINDSIGHT_API_WORKER_ID=hindsight # Reranker -> TEI GPU sidecar (kb#87). Was `local` = the same # multilingual jina-reranker-v2, but on this image's CPU-only torch it # pinned ~8 cores for ~183s over the grown adolf bank (269 facts / ~81 # rerank candidates), so the memory plugin's 4s recall timeout skipped # injection every time. Now the identical jina model is served on the # GPU by the tei-reranker sidecar behind the TEI /rerank API. - HINDSIGHT_API_RERANKER_PROVIDER=tei - HINDSIGHT_API_RERANKER_TEI_URL=http://tei-reranker:80 - HINDSIGHT_API_RERANKER_TEI_HTTP_TIMEOUT=60 volumes: - /mnt/ssd/dbs/hindsight:/home/hindsight/.pg0 # Persist HuggingFace/sentence-transformers model cache so the jina # reranker (~1GB) doesn't re-download on every container recreate. - /mnt/ssd/dbs/hindsight-cache:/home/hindsight/.cache ports: - "8888:8888" - "9999:9999" extra_hosts: # Needed to resolve host.docker.internal from inside the container # for the ollama embeddings call above — ollama lives in a separate # compose project, same trick as cognee/adolf-llm elsewhere in this # file. - "host.docker.internal:host-gateway" depends_on: # kb#217: litellm is now on the critical path for all three LLM stages # (HINDSIGHT_API_*_LLM_BASE_URL above all point at litellm:4000) since # the 2026-07-26 gemma3:4b re-route (59af13f); gate on its healthcheck # (added by kb#190) so a cold boot doesn't race hindsight up before it. litellm: condition: service_healthy # hindsight-llm dropped (kb#217): it was the Kimi-CLI wrapper that used # to serve retain before the re-route above; nothing in this service's # config points at hindsight-llm:8012 any more (grep confirms only # model-registry.yaml still lists it, unrelated to this container's # startup). The hindsight-llm service/volume are left in place — that's # a separate decommission decision, not this task's scope. tei-reranker: condition: service_healthy # kb#190: /health is hindsight's own liveness+DB-connectivity endpoint # (returns {"status":"healthy","database":"connected"}), confirmed cheap # (curl is present in this image). healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:8888/health || exit 1"] interval: 15s timeout: 10s retries: 5 start_period: 30s # openclaw-tools — MCP bridge (P5) exposing a minimal slice of the Adolf # OpenClaw gateway's agent tools (message/cron/nodes/browser) over MCP # Streamable HTTP, so Kimi CLI sessions (adolf-llm) can call them instead of # bypassing OpenClaw entirely. Proxies each MCP tool call to the gateway's # `POST /tools/invoke` HTTP surface (http://adolf:18789). NOTE: `cron` and # `nodes` are hard-denied on that surface by default until P6 adds them to # `gateway.tools.allow` in the adolf openclaw.json — see openclaw-tools/ # server.js for the full gate writeup. Not useful until `adolf` (P6) is # configured and running; safe to build/run standalone before that. openclaw-tools: build: ./openclaw-tools container_name: openclaw-tools environment: - OPENCLAW_GATEWAY_URL=http://adolf:18789 - OPENCLAW_GATEWAY_TOKEN=${ADOLF_GATEWAY_TOKEN:-} ports: - "8020:8020" restart: unless-stopped volumes: # No longer mounted by any service (kimi-agent removed 2026-08-01). Left # declared so the volume survives as a rollback source; `docker volume rm` # is a human decision after a soak period. kimi-agent-home: # kb#219: no longer mounted by the adolf service (replaced by the # /mnt/ssd/dbs/adolf/state bind mount above). Left declared, not removed, # so the volume itself survives as a rollback source until a human # explicitly `docker volume rm adolf-state` after a soak period — see the # rollback procedure in the kb#219 report. Removing this declaration is # a later cleanup step, not part of this migration. adolf-state: # Replaces hindsight-llm-home (/root/.kimi-code) at the Codex migration; the # old volume still holds the Kimi login until this is verified. hindsight-llm-codex-home: adolf-llm-workspace: # Replaces adolf-llm-home (/root/.kimi-code) at the Codex migration. The old # volume still exists and holds the Kimi OAuth login; drop it once the Codex # backend is verified working. adolf-llm-codex-home: