openai: compose healthchecks + dependency ordering, registries, LiteLLM routing

docker-compose.yml gains healthchecks and depends_on/condition chains for the
litellm/langfuse/postgres tier so dependants wait for a genuinely ready
service instead of a started container. Also plumbs AGAP_MCP_TOKEN into the
adolf and adolf-llm containers, sourced from openai/.env (gitignored), for the
kb#180 bearer auth on the agap MCP server; shared-mcp.json consumes it via
bearerTokenEnvVar so the Kimi backbone authenticates too.

agent-registry.yaml / agent_registry.py: the version-controlled source of
truth for agent identities and trust classes -- the same ids the agap-mcp
token map resolves to (`adolf`, `claude-coder`; note `claude-code-cli` is the
runtime entry, not an agent identity).

model-registry.yaml, litellm-config.yaml, auto-router-routes.json and
provision_litellm_keys.py: model tiering, virtual-key provisioning and
auto-router routes. tei-reranker/ is the local reranker service backing
Hindsight recall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 04:41:31 +00:00
parent a5c625b9b6
commit b27d31b3ca
12 changed files with 732 additions and 40 deletions

View File

@@ -9,6 +9,13 @@ services:
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
@@ -28,9 +35,19 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- litellm-db
- langfuse
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:
build: ./kimi-agent
@@ -50,6 +67,13 @@ services:
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
langfuse:
image: ghcr.io/langfuse/langfuse:2
@@ -63,8 +87,20 @@ services:
- SALT=7927b3b0092afe4542274940b557becea6418a5fed79f7acd25c3a789349fdc9
- AUTH_DISABLE_SIGNUP=true
depends_on:
- langfuse-db
langfuse-db:
condition: service_healthy
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
qdrant:
image: qdrant/qdrant
@@ -76,19 +112,22 @@ services:
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
runtime: nvidia
ports:
- "8880:8000"
environment:
- WHISPER__MODEL=deepdml/faster-whisper-large-v3-turbo-ct2
- WHISPER__INFERENCE_DEVICE=cuda
- WHISPER__INFERENCE_DEVICE=cpu
- WHISPER__COMPUTE_TYPE=int8
- WHISPER__LANGUAGE=ru
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
volumes:
- /mnt/ssd/ai/faster-whisper:/root/.cache/huggingface
restart: always
@@ -152,12 +191,33 @@ services:
- 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 openai/.env
# (gitignored); never inlined here.
- AGAP_MCP_TOKEN=${AGAP_MCP_TOKEN:-}
- TZ=Europe/Riga
volumes:
# Runtime state only (Matrix crypto/devices, credentials, sessions,
@@ -189,6 +249,19 @@ services:
# 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 read-only-bind-over-volume
# pattern as quota-command/hindsight-memory above. 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 read-only-bind-
# over-volume pattern as quota-command/hindsight-memory/kimi-quota-
# footer above. 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
extra_hosts:
- "host.docker.internal:host-gateway"
# mtx.alogins.net's public A record can't hairpin-NAT back through the
@@ -230,6 +303,48 @@ services:
volumes:
- hindsight-llm-home:/root/.kimi-code
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 Kimi-CLI wrapper (:8010), the model backend for
# the Adolf OpenClaw gateway (P2). Real streaming (SSE), chat_id session-keying
@@ -239,6 +354,23 @@ services:
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"` (Kimi CLI's own field
# for a static bearer token sourced from the environment, confirmed by
# decompiling @moonshot-ai/kimi-code's dist/main.mjs help text). Kimi
# 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
# openai/.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 Kimi backbone's agap tools all
# fail with HTTP 401 once agap-mcp restarts with auth on.
- AGAP_MCP_TOKEN=${AGAP_MCP_TOKEN:-}
ports:
- "8010:8010"
volumes:
@@ -257,6 +389,14 @@ services:
- "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 Kimi 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
@@ -339,14 +479,15 @@ services:
# in-flight async retain/consolidation tasks under the old hostname
# (startup log warns about exactly this).
- HINDSIGHT_API_WORKER_ID=hindsight
# Reranker -> multilingual (kb#84 follow-up). The TEMPR rerank stage
# defaulted to English cross-encoder/ms-marco-MiniLM, which ranks
# Russian/multilingual candidates poorly. jina v2 multilingual fixes
# that. Runs on CPU in this image (no CUDA torch) but only over the
# small recall candidate set. trust_remote_code: jina ships custom code.
- HINDSIGHT_API_RERANKER_PROVIDER=local
- HINDSIGHT_API_RERANKER_LOCAL_MODEL=jinaai/jina-reranker-v2-base-multilingual
- HINDSIGHT_API_RERANKER_LOCAL_TRUST_REMOTE_CODE=true
# 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
@@ -362,7 +503,29 @@ services:
# file.
- "host.docker.internal:host-gateway"
depends_on:
- hindsight-llm
# 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