ai: migrate LLM backbone from Kimi CLI to Codex CLI
Retires the Moonshot/Kimi subscription in favour of the already-paid ChatGPT plan. Both CLI wrappers now run `codex exec`; the kimi-agent container is gone. adolf-llm + hindsight-llm: - runKimi -> runCodex (`codex exec --json --skip-git-repo-check`), resume via `codex exec resume <thread_id>`. - MCP moves from a per-session .mcp.json (a workaround for Kimi having no --mcp-config-file flag) to a $CODEX_HOME/config.toml generated once at startup from shared-mcp.json. Field translation is load-bearing: bearerTokenEnvVar -> bearer_token_env_var, enabledTools -> enabled_tools. - approval_policy="never" + sandbox_mode required, or unattended turns block on an approval prompt nobody can answer. kimi-agent removed. It was the ONLY large-tier deployment behind LiteLLM, so deleting it outright would have silently degraded every large-tier request to the local 4B model via the existing fallbacks. tier-large, the auto_router complex-reasoning route and their fallbacks now point at the codex-backed adolf-llm wrapper (model_name: codex-agent). Three environment blockers fixed along the way: - OpenAI geo-blocks this host (403 unsupported_country_region_territory). Both containers now egress via the host xray proxy, with NO_PROXY keeping MCP and *.alogins.net traffic off the tunnel. - node:22-slim ships no system CA store; the Rust codex binary validates TLS against it, so every HTTPS call failed with a generic transport error while Node's own fetch worked. ca-certificates added to both images. - `codex exec resume` rejects -C/--cd (plain `codex exec` accepts it), which broke follow-up turns while first turns succeeded. Known regression: Kimi's managed-usage API has no Codex equivalent, so the /usage route returns 501 and there is no quota probe for the codex model. The two quota plugins degrade quietly to no output. Also: stop tracking cognee.env (live LLM + JWT secrets) and gitignore it. The secrets remain in earlier history and should be rotated. Verified live: plain turn, SSE streaming, session resume, MCP tool call, bearer-token MCP call, and completions through both LiteLLM routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Y5QPagv4iun1ghpwM96Ff
This commit is contained in:
730
ai/docker-compose.yml
Normal file
730
ai/docker-compose.yml
Normal file
@@ -0,0 +1,730 @@
|
||||
# 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 <token>` 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/<service>). 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
|
||||
# kimi-quota-footer plugin (kb #85) — same pattern. Appends the Kimi
|
||||
# usage line to every outgoing reply via reply_payload_sending, reusing
|
||||
# quota-command's adolf-llm:8010/usage route. Activated via
|
||||
# plugins.entries.kimi-quota-footer in openclaw.json.
|
||||
- ./kimi-quota-footer-plugin:/home/node/.openclaw/extensions/kimi-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 <think>-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 <think>-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:
|
||||
Reference in New Issue
Block a user