AI代理本地记忆栈

Hindsight是一个为AI代理提供长期记忆的本地运行栈,包含API和机器学习模型,使用PostgreSQL和pgvector进行数据存储。

  • by abix5
  • Makefile
  • 本地部署
  • 更新于
在 GitHub 查看 abix5/hindsight-setup

使用说明

Hindsight — local stack for macOS

Long-term memory for AI agents, running locally: the API and the ML models run in a native Python process, docker-compose carries PostgreSQL + pgvector and nothing else.

Русская версия


Quick start

git clone <this-repo> && cd hindsight

cp .env.example .env
# edit three lines in the REQUIRED block: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL

make start                    # bootstraps everything, ~3 min on first run
make bank BANK=myproject      # create and configure a memory bank

That's it. make start creates the Python venv, installs the pinned version, starts PostgreSQL, runs DB migrations and brings up the API and the UI.

API http://localhost:8888
UI http://localhost:9999
MCP endpoint http://localhost:8888/mcp/{bank_id}/

Check it works:

make health                   # {"status":"healthy","database":"connected",...}
make banks                    # list banks with fact counts

First run downloads ~2.5 GB of embedding and reranker models into ./huggingface. Subsequent starts take a few seconds.


Requirements

Verified on the versions below. Older ones usually work, but these are what the stack is actually tested against.

Tool Version Needed for
macOS 26.x, Apple Silicon MPS acceleration for local models
Docker Desktop 29.x PostgreSQL only
uv 0.9.x Python env, installed automatically by make
Node.js 22.x LTS UI via npx — optional
Python 3.11 pinned by the Makefile, uv installs it for you

Install the two you need:

brew install --cask docker
brew install uv node

You also need an OpenAI-compatible LLM endpoint — OpenAI itself, a local LM Studio / Ollama, or any proxy in front of them.


Why not run everything in Docker

Docker Desktop on macOS gives a container no usable access to the GPU. The embedding model (bge-m3) and the cross-encoder reranker would fall back to CPU and become several times slower.

So the split is: models and API on the host where they reach Metal/MPS, and the database — which does not care — in a container.

┌──────────────────────────────────────────┐
│ macOS host                               │
│  - hindsight-api (Python 3.11 venv)      │
│  - embeddings + reranker (MPS)           │
│  - control-plane UI via npx (optional)   │
└───────────────────────┬──────────────────┘
                        │ localhost:5432
┌───────────────────────▼──────────────────┐
│ docker-compose                           │
│  - postgres 16 + pgvector                │
└──────────────────────────────────────────┘

Commands

Command What it does
make start Postgres + API + UI (bootstraps on first run)
make stop stop API and UI, leave Postgres running
make stop-all stop everything including Postgres
make restart stop + start
make status what is currently running
make health API health check
make logs tail API and UI logs together
make bank BANK=x create/configure bank x from bank-template.json
make banks list banks with fact counts
make sync-version align installed packages with VERSION in .env

Setting up a memory bank

A bank is an isolated memory space — normally one per project.

make bank BANK=myproject

This applies bank-template.json to the bank, creating it if missing. The file in the repo is a sane engineering default: it tells the extractor to keep decisions, constraints, verified procedures and dead-ends, and to ignore session chatter.

Edit bank-template.json to taste and re-run the command — it is idempotent.

Two rules worth knowing:

  • Missions must be written in English. The server-side validator rejects other languages. This is independent of the language your facts are stored in.
  • The language of stored facts is HINDSIGHT_API_LLM_OUTPUT_LANGUAGE in .env.

The full field list is served by the API itself:

curl http://localhost:8888/v1/bank-template-schema | jq .

Configuration

Everything lives in .env, which the API reads directly at startup (load_dotenv(override=True)) — no shell exports needed. .env is gitignored; only .env.example is committed.

.env.example has two parts: an active block with working defaults, and a large commented block documenting the optional settings with their current defaults.

A few settings deserve a mention because their failure mode is silent:

HINDSIGHT_API_LLM_TIMEOUT — if a Cloudflare-fronted proxy sits in front of your endpoint, it cuts connections at 120 s. Keep the client timeout below that, or every failing attempt burns the full two minutes.

HINDSIGHT_API_LLM_CACHE_AFFINITY — the default auto is an allowlist covering only x.ai and openai.com. Any custom proxy silently resolves to none. Set it explicitly if your endpoint understands prompt_cache_key. It only pays off for multi-call operations, since the key is one per operation run.

A misspelled variable name is silently ignored. There is no validation. To check your file against the installed version:

grep -oE '"HINDSIGHT_API_[A-Z0-9_]+"' \
  .venv-hs/lib/python3.11/site-packages/hindsight_api/config.py \
  | tr -d '"' | sort -u > /tmp/valid.txt
grep -oE '^HINDSIGHT_API_[A-Z0-9_]+' .env | sort -u | comm -23 - /tmp/valid.txt

Anything printed does not exist and is doing nothing.


Search language

Two independent settings, easy to confuse:

Setting Controls
HINDSIGHT_API_LLM_OUTPUT_LANGUAGE the language the LLM writes new facts in
HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE how text is stemmed for lexical search

PostgreSQL's built-in russian configuration is already bilingual: Cyrillic tokens go through russian_stem, ASCII tokens (phpstan, GraphQL, file paths) through english_stem, and stopwords are dropped on both sides. There is no need to build a custom hybrid configuration — verify with \dF+ russian.

Changing the search language on a populated database requires a backfill. memory_units.search_vector is a plain column written at insert time, not a GENERATED one, so existing rows keep their old configuration:

UPDATE memory_units SET search_vector = to_tsvector('<lang>'::regconfig,
  COALESCE(text,'') || ' ' || COALESCE(context,'') || ' ' || COALESCE(text_signals,''));
REINDEX INDEX idx_memory_units_text_search;

This is pure SQL — no LLM calls, no re-embedding, no document re-ingest. Measured at ~27 seconds for 3,800 rows.


Upgrading

# 1. back up first
docker compose exec -T postgres pg_dump -U hindsight -Fc hindsight_db \
  > backups/hindsight_pre_<version>.dump

# 2. pin the new version
#    edit VERSION in .env, e.g. VERSION=v0.9.1

# 3. apply
make restart

make restart reinstalls the Python package and the UI at the pinned version, and Alembic migrations run automatically on API start. Verify:

curl -s http://localhost:8888/version
grep -iE 'alembic|error' logs/api.log | tail

Rollback is the reverse: restore VERSION, make restart, and pg_restore the dump if a migration changed the schema.


Troubleshooting

API will not start — make logs, then make health. Most often .env is missing or the LLM endpoint is unreachable.

Database connection refused — docker compose ps to confirm Postgres is up, docker compose up -d postgres to bring it back. The password in HINDSIGHT_API_DATABASE_URL must match POSTGRES_PASSWORD in docker-compose.yaml.

UI does not come up — install Node.js for npx, or just ignore it; the API works standalone.

Operations stuck in processing — a worker died without releasing its tasks (a restart will do it). Release them:

.venv-hs/bin/hindsight-admin decommission-workers -y

Checking what the background workers are doing:

SELECT operation_type, status, count(*)
FROM async_operations
WHERE created_at > now() - interval '7 days'
GROUP BY 1,2 ORDER BY 3 DESC;

Contributing / Security