Files
wehub-resource-sync f9447f8e5f
CI / typecheck · build · test · bundle-size (push) Failing after 1s
CI / clean-room dependency closure smoke (push) Failing after 1s
CI / server-runtime e2e (docker · pg + valkey) (push) Failing after 2s
Deploy Install Scripts / deploy (push) Failing after 2s
Windows / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:07:03 +08:00

163 lines
6.2 KiB
Markdown

# Claude-Mem Server (Beta)
Claude-Mem Server is the beta server runtime for Claude-Mem 13. It is a
Postgres-backed, BullMQ-driven, API-key-authenticated runtime that replaces
the legacy `claude-mem worker` for deployable use cases.
## Architecture
```
+-------------------+
| Hooks / SDK / MCP|
| (clients) |
+---------+---------+
| HTTPS / Bearer API key
v
+-----------------+ +----+---------+ +-------------------+
| Postgres |<-+ claude-mem- +-->+ Valkey |
| (canonical | | server | | (BullMQ queue, |
| storage: | | --daemon | | noeviction, |
| events, | | HTTP only, | | appendonly yes) |
| observations, | | no generation | +---------+---------+
| jobs, sessions,| +-------+-------+ ^
| api_keys) | | enqueue | poll
+--------^--------+ | |
| v |
| +-----------------+ |
+----------+ claude-mem- +-------------+
read | worker (Nx) | consume jobs
write | server worker | call provider
| start |
+-----------------+
```
The HTTP service and the BullMQ generation worker run from the **same image
and same codebase**, but are split into separate processes / containers so
that:
1. Long-running provider calls cannot block HTTP responsiveness.
2. Generation can scale horizontally (`docker compose up --scale claude-mem-worker=N`).
3. Restarting the HTTP server does not lose enqueued generation work — jobs
live in Valkey, persisted by AOF.
The legacy `claude-mem worker` runtime is **not** spawned in Docker. The
container entrypoint runs `bun server-service.cjs --daemon` (or
`worker start`) and never `bun worker-service.cjs`.
## Required environment variables
`validateServerBetaEnv()` runs at startup and refuses to boot when any of
the following are missing or invalid in Docker:
| Variable | Required | Notes |
|-----------------------------------|----------|--------------------------------------------------------------|
| `CLAUDE_MEM_RUNTIME` | Docker | Must be `server-beta` in Docker (warned otherwise). |
| `CLAUDE_MEM_QUEUE_ENGINE` | Docker | Must be `bullmq`. In-process queues are rejected in Docker. |
| `CLAUDE_MEM_SERVER_DATABASE_URL` | Always | Postgres connection string. Fails fast at startup. |
| `CLAUDE_MEM_REDIS_URL` | bullmq | Required when queue engine is `bullmq`. |
| `CLAUDE_MEM_AUTH_MODE` | Always | Must NOT be `local-dev` in Docker. |
| `CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS` | Docker | Must NOT be `1`/`true` in Docker. |
| `CLAUDE_MEM_GENERATION_DISABLED` | Optional | Set to `true` on the HTTP service when running a separate worker. |
| `CLAUDE_MEM_SERVER_PROVIDER` | Worker | One of `claude`, `gemini`, `openrouter`. Worker only. |
| `ANTHROPIC_API_KEY` (or alt) | Worker | Required by the chosen provider. |
Local development can still use SQLite + `local-dev` auth bypass **outside
Docker only**. Deployable mode must use the table above.
## Generation worker mode (`claude-mem server worker start`)
The same image runs the generation worker via:
```sh
claude-mem server worker start
```
This starts a process that:
* Connects to Postgres and Valkey using the same configuration as the HTTP
service.
* Attaches BullMQ Workers to the `event` and `summary` queues.
* Never opens an HTTP listener.
* Blocks in the foreground (good for `docker run`, `kubectl run`, systemd).
* Forces generation enabled even if `CLAUDE_MEM_GENERATION_DISABLED=true`
is inherited from the shared compose file. The worker IS the generation
process.
In Compose this is the `claude-mem-worker` service. Scale it horizontally:
```sh
docker compose up -d --scale claude-mem-worker=4
```
BullMQ guarantees only one worker processes a given job at a time; the
provider call inside `ProviderObservationGenerator.process` is idempotent
on the `job.id` (`evt_<sha256>` / `sum_<sha256>`) so retries cannot
duplicate observations.
## Auth in production
```sh
CLAUDE_MEM_AUTH_MODE=api-key
```
API keys are created with:
```sh
claude-mem server api-key create \
--name "ci" \
--scope memories:read,memories:write
```
The raw key is shown **once**; only a SHA-256 hash is stored in Postgres
(`api_keys.key_hash`). Revoke with:
```sh
claude-mem server api-key revoke <id>
```
Revocation is enforced on every request because `requirePostgresServerAuth`
reloads the row by hash on each call. There is no in-memory cache to
poison.
> **Do not enable `CLAUDE_MEM_AUTH_MODE=local-dev` in Docker.** The
> loopback bypass relies on the request originating from `127.0.0.1` on
> the HTTP listener, which is not a meaningful boundary inside a
> container. The startup validator refuses to boot with this combination
> and returns a non-zero exit code.
## Compose stack
`docker-compose.yml` ships four services:
* `postgres` — canonical storage. Schema is bootstrapped at startup by
`bootstrapServerBetaPostgresSchema()`.
* `valkey` — BullMQ queue, configured with `appendonly yes`,
`appendfsync everysec`, `maxmemory-policy noeviction`.
* `claude-mem-server` — HTTP runtime.
`CLAUDE_MEM_GENERATION_DISABLED=true` so the BullMQ Worker is **not**
attached here.
* `claude-mem-worker` — generation worker. Scale horizontally.
Bring it up:
```sh
docker compose up -d --build
```
Tear it down (and wipe data):
```sh
docker compose down -v
```
## End-to-end test
`scripts/e2e-server-docker.sh` brings up the full stack and verifies:
* `POST /v1/events?wait=true` returns a `generationJob` descriptor.
* Restart of `claude-mem-server` and `claude-mem-worker` mid-stream does
not lose data.
* Revoking an API key denies subsequent reads and writes (401/403).
* No `worker-service.cjs` process runs in any container.
* `CLAUDE_MEM_AUTH_MODE=local-dev` is rejected inside Docker.