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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user