Hindsight auf Coolify betreiben
Mein selbst gehostetes Setup, die Probleme beim Deployment und die Änderung, die Recall schneller gemacht hat.
Ich habe darüber geschrieben, warum ich das Gedächtnis meiner Agents in Hindsight ablege. Das hier ist die andere Hälfte: wie es tatsächlich läuft. Selbst gehostet, auf Coolify, hinter Caddy, mit einer Bank pro Repo.
Die Compose-Datei ist der einfache Teil. Alles darunter ist das, was ich zuerst falsch gemacht habe.
Was du brauchst
Eine Coolify-Instanz mit einem Worker, auf den du deployen darfst. Eine kleine Kiste: 2 vCPU und 4 GB reichen, mit einer Bedingung, auf die ich noch zurückkomme. Eine Domain, die du darauf zeigen lassen kannst. Und einen LLM-Key, denn Hindsight extrahiert Fakten mit einem Modell; der Extraktionsschritt braucht ein konfiguriertes Modell.
Die Compose-Datei
Leg in Coolify einen Service aus einer eigenen Compose-Datei an. Zwei Container, ein Volume.
services:
db:
image: 'pgvector/pgvector:pg18'
restart: unless-stopped
shm_size: 1gb
environment:
- POSTGRES_USER=${HINDSIGHT_DB_USER}
- POSTGRES_PASSWORD=${HINDSIGHT_DB_PASSWORD}
- POSTGRES_DB=${HINDSIGHT_DB_NAME}
volumes:
- 'pg-data:/var/lib/postgresql/18/docker'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
interval: 5s
timeout: 20s
retries: 20
hindsight:
image: 'ghcr.io/vectorize-io/hindsight:latest'
restart: unless-stopped
expose:
- 8888
- 9999
environment:
- SERVICE_FQDN_HINDSIGHT_8888
- SERVICE_FQDN_HINDSIGHT_9999
- HINDSIGHT_API_DATABASE_URL=postgresql://${HINDSIGHT_DB_USER}:${HINDSIGHT_DB_PASSWORD}@db:5432/${HINDSIGHT_DB_NAME}
- HINDSIGHT_API_WORKER_ID=${HINDSIGHT_API_WORKER_ID}
- HINDSIGHT_API_LLM_PROVIDER=${HINDSIGHT_API_LLM_PROVIDER}
- HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_API_LLM_MODEL}
- HINDSIGHT_API_TENANT_EXTENSION=${HINDSIGHT_API_TENANT_EXTENSION}
- HINDSIGHT_API_TENANT_API_KEY=${HINDSIGHT_API_TENANT_API_KEY}
- HINDSIGHT_CP_ACCESS_KEY=${HINDSIGHT_CP_ACCESS_KEY}
- HINDSIGHT_CP_DATAPLANE_API_KEY=${HINDSIGHT_CP_DATAPLANE_API_KEY}
depends_on:
db:
condition: service_healthy
volumes:
pg-data:
Zwei Ports, weil Hindsight zwei Dinge ist: die API auf 8888 und eine Control-Plane-Oberfläche auf 9999.
shm_size: 1gb ist keine Deko. Postgres bekommt in Docker standardmäßig 64 MB Shared Memory, und der Endpoint für den Dokumenttransfer stirbt daran, sobald echte Mengen durchgehen. Das habe ich herausgefunden, indem ich viertausend Dokumente importiert und zugesehen habe, wie es irgendwo in der Mitte abbricht.
Auth
Drei Variablen, und die dritte ist die, die gern übersehen wird.
HINDSIGHT_API_TENANT_EXTENSION auf die API-Key-Tenant-Extension gesetzt, dazu HINDSIGHT_API_TENANT_API_KEY, und du hast eine API, die ohne Key 401 zurückgibt. /health bleibt offen, und genau das willst du für einen Healthcheck.
HINDSIGHT_CP_ACCESS_KEY schützt die Oberfläche. Und HINDSIGHT_CP_DATAPLANE_API_KEY muss auf denselben Wert wie der Tenant-Key gesetzt sein. Sonst kann die Control Plane nicht mit ihrer eigenen API reden, und du bekommst eine Oberfläche, die lädt und dann nichts anzeigt.
Vier Coolify-spezifische Fallen
Schreib keine eigenen caddy_*-Labels. Coolify erzeugt die Proxy-Labels aus dem Feld fqdn neu und wirft deine weg. Schlimmer: Bei mir ist das Deployment danach still gescheitert, die Container waren einfach weg, nirgends ein Fehler. Deklarier die Ports als nackte SERVICE_FQDN_<SERVICE>_<PORT>-Einträge und lass Coolify caddy_0 und caddy_1 erzeugen.
Service-FQDNs lassen sich nicht über die API setzen. Applications akzeptieren beim PATCH ein Feld domains. Services nicht, auch in 4.3.5 noch nicht. Du setzt service_applications.fqdn in Coolifys eigener Postgres, Format https://a.example:8888,https://b.example:9999, und deployst dann neu.
Env-Änderungen erreichen den Server nur bei einem vollen Deployment. Ein Restart lässt die alte .env in /data/coolify/services/<uuid>/ unangetastet, und du debuggst einen Wert, der gar nicht da ist.
Wenn Coolify schweigt, frag Docker. cd /data/coolify/services/<uuid> && docker compose up -d auf der Kiste gibt den echten Fehler aus. Außerdem: Nach Stop und Start zieht Coolify :latest neu, und das dauert Minuten. Wer zu früh nachschaut, sieht „no containers“, und das sieht genau aus wie ein Absturz.
Erst DNS, dann Domains
Setz zuerst den A-Record, dann trag die Domain in Coolify ein. Nicht andersherum.
Ist der Hostname konfiguriert, bevor DNS auflöst, fängt Caddy an, bei Let’s Encrypt ein Zertifikat anzufragen, das es nicht validieren kann. Fünf fehlgeschlagene Autorisierungen pro Hostname pro Stunde, und du bist rate-limited und schaust einem Service zu, der einwandfrei läuft und kein TLS ausliefert. Ich habe dieses Limit an einem Nachmittag für mehrere Hostnamen verbrannt. Steht DNS zuerst, kommt das Zertifikat beim ersten Versuch.
Das Modell muss langweilig sein
In meinem Setup hat gpt-5-mini nicht funktioniert. Reasoning-Modelle akzeptieren nur temperature=1, Hindsight schickt 0.1, und du bekommst einen BadRequestError, der nichts über den Grund sagt. Ein normales Chat-Modell geht problemlos. Ich nutze gpt-5.4-mini über litellm gegen Azure.
Du kannst retain, reflect und consolidation auch mit HINDSIGHT_API_{RETAIN,REFLECT,CONSOLIDATION}_LLM_MODEL auf unterschiedliche Modelle zeigen lassen. Gebraucht habe ich das bisher nicht.
Recall messen und den Reranker umziehen
Diesen Teil würde ich an den Anfang stellen, wenn ich das für mich selbst von vor einer Woche schreiben würde.
Ab Werk rerankt Hindsight mit einem neuronalen Cross-Encoder. Auf einer kleinen CPU-Kiste heißt das: Recall dauert neun Sekunden, im Leerlauf, jedes Mal. Deswegen hätte ich das Ganze fast weggeworfen.
Gerettet hat es trace: true bei einem Recall-Aufruf und ein Blick auf die Aufschlüsselung nach Phasen. Gesamt 9,82 s. Reranking: 8,87 s davon. Eine einzige Phase, die 300 Kandidaten mit je etwa 30 ms bewertet, auf einer CPU, die dafür nicht gemacht ist.
Den Reranker auf einen gehosteten Endpoint umzuziehen, brachte es auf 1,1–1,7 Sekunden gesamt, 0,24 s fürs Reranking, bei gleicher Kandidatenzahl. Sonst hat sich nichts geändert.
- HINDSIGHT_API_RERANKER_PROVIDER=${HINDSIGHT_API_RERANKER_PROVIDER}
- HINDSIGHT_API_RERANKER_COHERE_BASE_URL=${HINDSIGHT_API_RERANKER_COHERE_BASE_URL}
- HINDSIGHT_API_RERANKER_COHERE_MODEL=${HINDSIGHT_API_RERANKER_COHERE_MODEL}
- HINDSIGHT_API_RERANKER_COHERE_API_KEY=${HINDSIGHT_API_RERANKER_COHERE_API_KEY}
Ich nutze einen Cohere-kompatiblen Rerank-Endpoint, gehostet auf Azure.
Also: erst messen, dann urteilen. Und die 2 vCPU von oben reichen nur, weil der Reranker remote läuft. Wenn du ihn lokal betreiben willst, miss seinen Ressourcenbedarf, bevor du den Server auswählst.
Die Clients anbinden
Das Claude-Code-Plugin liest ~/.hindsight/claude-code.json:
{
"hindsightApiUrl": "https://memory.example.com",
"hindsightApiToken": "…",
"dynamicBankId": true,
"dynamicBankGranularity": ["project"],
"autoRecall": true,
"autoRetain": true
}
dynamicBankGranularity ist absichtlich ["project"], ohne agent. Nimmst du agent dazu, schreiben Claude Code und Codex für dasselbe Repo in getrennte Banks, und damit ist der Sinn dahin. Git-Worktrees werden von selbst auf den Namen des Haupt-Repos aufgelöst.
Für alles, was nicht Claude Code ist, also Grok oder Cursor, bekommst du mit einem Fünfzeilen-Wrapper denselben MCP-Server, der dieselbe Config liest:
#!/usr/bin/env bash
set -e
export CLAUDE_PLUGIN_ROOT="$HOME/.claude/plugins/cache/hindsight/hindsight-memory/<version>"
export CLAUDE_PLUGIN_DATA="$HOME/.claude/plugins/data/hindsight-memory"
CFG="$HOME/.hindsight/claude-code.json"
export HINDSIGHT_API_URL="$(jq -r .hindsightApiUrl "$CFG")"
export HINDSIGHT_API_TOKEN="$(jq -r .hindsightApiToken "$CFG")"
exec "$CLAUDE_PLUGIN_ROOT/scripts/run_mcp.sh" "$@"
Eine Config-Datei, ein Server, jeder Agent. Änderst du die URL einmal, ziehen alle mit.
Prüfen, ob es läuft
Die API-Pfade haben ein Präfix und sind nicht zu erraten. /banks ist ein 404. Was du willst, ist /v1/default/banks und /v1/default/banks/<bank>/memories/recall. /openapi.json wird ausgeliefert und ist der schnellste Weg, den Rest zu finden.
Der echte Test ist nicht /health. Sondern ob autoRetain in echten Sessions schreibt. Frag die Datenbank direkt ab, statt der Oberfläche zu trauen:
select bank_id, count(*), max(created_at)
from documents
where created_at >= '<date>'
group by 1 order by 2 desc;
Wenn die Zahlen über ein paar Tage in mehreren Repos steigen, funktioniert es. Bei mir waren es in den ersten zwei Tagen vierundvierzig neue Dokumente in sieben Banks, und da habe ich aufgehört, mir Sorgen zu machen.
Hat es sich gelohnt?
Das Setup hat einen Abend gedauert, und das meiste davon war der Umweg über den Reranker. Seitdem läuft es, mit etwa 1,2 GB RAM, auf einer eigenen kleinen Kiste, getrennt von allem, worauf ich deploye. Denn das Einzige, was ich auf keinen Fall will, ist, dass das Gedächtnis mit der Maschine stirbt, die ich gerade debugge.