rembric /ˈrem.brɪk/ — coined, from remember + fabric: the woven memory and lifecycle layer beneath your agents. One brain, one dashboard, one audit trail — shared across every MCP-capable tool: Claude Code, Codex CLI, Hermes Agent, opencode, Cursor, and beyond. The MCP memory surface is the core; sessions, judgments, consolidation, and the operator dashboard come along in the same single Node process.

Supported agents

Rembric works with any agent that speaks MCP or HTTP. First-class plugins handle session lifecycle + per-project path-scoping automatically, and redact <private>…</private> spans client-side before any transcript-derived text is uploaded (see docs/agents.md); everything else gets the same memory tools via a plain MCP URL. ChatGPT connects as a custom MCP connector over OAuth 2.1 (no static token) — see docs/agents.md.

Quickstart

One installer does everything. A single brand-styled menu prepares the server (writes docker-compose.yml + .env, generates your admin token) and installs / updates / uninstalls every agent plugin — detecting what you have and at which version. It is the recommended way to install, set up, upgrade, and remove Rembric.

curl -fsSL https://raw.githubusercontent.com/susomejias/rembric/main/install.sh | sh

Pick Server → install: it generates REMBRIC_ADMIN_TOKEN and, if Docker is present and you confirm, runs docker compose up -d for you. Then pick Plugins to install the plugin for your agent. Pin a release for reproducibility with --ref=<tag>.

Docker is the canonical distribution: the image bundles Node 22, the native modules (better-sqlite3, sqlite-vec, onnxruntime-node) and the embedding model, pre-built for linux/amd64 and linux/arm64 — the only requirement on your host is Docker and ~1 GB of RAM (2 GB recommended; the in-process embedder is why — see docs/embeddings.md).

MCP at http://<host>:8787/mcp, dashboard at http://<host>:8787/dashboard (replace <host> with 127.0.0.1 if running on the same host as your agent, or the LAN/Tailscale hostname of the box hosting Rembric otherwise).

curl -fsSL https://raw.githubusercontent.com/susomejias/rembric/main/install.sh -o rembric-install.sh
less rembric-install.sh        # skim it
sh rembric-install.sh

Set up the server by hand

Prefer to skip the installer? Fetch the compose file and env template and bring it up yourself:

mkdir rembric && cd rembric
curl -fsSLO https://raw.githubusercontent.com/susomejias/rembric/main/docker-compose.yml
curl -fsSL  https://raw.githubusercontent.com/susomejias/rembric/main/.env.example -o .env

# edit .env:  set REMBRIC_ADMIN_TOKEN (e.g. `openssl rand -hex 32`) — that's it

docker compose up -d
docker compose logs -f rembric

Running on a remote host (LXC, NAS, server) — the canonical case

The compose file publishes port 8787 on all interfaces of the host so your agent on another machine can reach it. Point the plugin at http://<host-ip>:8787 (LAN) or http://rembric.tailnet:8787 (Tailscale). Don't expose port 8787 directly to the public internet — front it with Tailscale, WireGuard, or your reverse proxy of choice. The bearer token is the real security boundary; every endpoint requires Authorization: Bearer <token>.

Running on the same host as your agent — loopback override

If you want to restrict the published port to loopback (stricter posture, same-host dev only), drop a docker-compose.override.yml next to the canonical compose:

services:
  rembric:
    ports: !override
      - '127.0.0.1:${REMBRIC_PORT:-8787}:8787'

Compose auto-merges the override on every up. Point your agent at http://127.0.0.1:8787. See docs/docker.md for the full topology guide.

Upgrading

Auto-updater from the UI. The dashboard checks for new releases (once a day, opt-out) and shows a badge + changelog modal when one is out. Mount the Docker socket and the same modal gains a one-click Update button that backs up the database, pulls the new image, swaps the container with health-check and rollback, and reloads the page on the new version — see docs/updates.md for setup and the security trade-off. Without the socket nothing breaks: you get the notification plus the manual flow below.

Docker manages versions for you. With REMBRIC_VERSION unset in .env, the compose file pulls :latest:

docker compose pull
docker compose up -d

Portainer / Arcane detect the new digest automatically and offer a "Recreate container" button — one click. For reproducible deploys, pin a specific version in .env (this also disables the one-click updater — the dashboard explains why):

REMBRIC_VERSION=0.21.9

Rolling back

Bump REMBRIC_VERSION to a previous tag in .env and re-run docker compose up -d. The bind-mounted ./data/ directory is untouched, so your memory stays intact across version flips.

See docs/docker.md for the full operator guide (private GHCR auth, named-volume vs bind-mount, troubleshooting).

Backups

# while the container is running (WAL-safe online backup):
docker compose exec rembric sqlite3 /data/data.db ".backup /data/backup-$(date +%Y%m%d).db"
mv ./data/backup-*.db ./backups/

# or stop + copy for a cold backup:
docker compose down
cp ./data/data.db ./backups/data-$(date +%Y%m%d).db
docker compose up -d

Do not bind-mount ./data/ onto NFS / SMB / network filesystems — SQLite's POSIX locking guarantees don't hold there, and you'll eventually corrupt the DB.

Configuration

All config via environment variables. With Docker, these live in .env and are loaded automatically by docker compose up. Required: REMBRIC_ADMIN_TOKEN (used to log into the dashboard and mint other tokens).

Variable Default Description
REMBRIC_HOST 127.0.0.1 (0.0.0.0 in Docker) Bind address. Pinned to 0.0.0.0 inside the container so the published port works; never override in Docker.
REMBRIC_PORT 8787 Bind port.
REMBRIC_DATA_DIR ~/.rembric (/data in Docker) Where the SQLite file lives. Pinned to /data inside the container; bind-mount ./data:/data in compose.
LOG_LEVEL info debug / info / warn / error.
REMBRIC_UPDATE_CHECK on off disables the daily release check (no badge, no modal, no GitHub API call). See docs/updates.md.
REMBRIC_PUBLIC_URL unset Set to the public https issuer (http://localhost allowed for local testing) to enable the OAuth 2.1 authorization server, so OAuth clients (Claude Code, ChatGPT) connect without a static token. Off when unset. See docs/agents.md.

Hardware requirements

Minimum 1 GB RAM; 2 GB recommended. The server embeds its semantic engine in-process — gte-multilingual-base (Apache 2.0, ONNX q8, 768 dims), loaded at boot (~1.1 s from the baked image; a broken model fails the boot instead of degrading silently), ~730 MB total process RSS measured under embedding load, ~14 ms per embedding on CPU. That requirement buys the entire trade: no external services, no API keys, no network calls — semantic candidate detection (including cross-language matching) works out of the box on an air-gapped box. Disk: the image carries the model (+~300 MB). The full pipeline is diagrammed in docs/embeddings.md.

The engine is code, not configuration: there is no model selector, no threshold knob, no off switch. The model class is pinned (≤350M params, ≤800 MB RSS); changing it is a breaking architectural change.

Consolidation sweep

Deterministic — decay + deadline orphaning, no LLM, no cron. Runs on session start (throttled to one run per scope per 6h) and on demand via the dashboard or POST /admin/consolidation/run. Pre-0.21/0.22 vars (CONSOLIDATION_*, OPENAI_*, LLM_PROVIDER, EMBEDDING_*, CANDIDATE_*_THRESHOLD) are ignored with a boot warning.

Variable Default Description
JUDGMENT_ORPHAN_AFTER_MS 86400000 Age (ms) past which pending judgments surface in memory.context for the agent to close. 24h.
JUDGMENT_ORPHAN_DEADLINE_MS 1209600000 Age (ms) past which unjudged pendings are orphaned by the sweep (journaled, undoable). 14 days.

Rate limiting

Per-token token-bucket limiter on /mcp requests. Disabled by default — single-user localhost deployments don't need it. Enable when exposing the server to multiple agents that might burst-call.

Variable Default Description
RATE_LIMIT_ENABLED false Master toggle. true activates the limiter; misbehaving tokens get 429 rate_limited with Retry-After.
RATE_LIMIT_RPS 10 Refill rate per token, in requests per second.
RATE_LIMIT_BURST 30 Burst capacity per token. The first N requests after a quiet period are free; beyond that, RPS applies.

Security hardening

Applied to every bearer-authenticated surface (/mcp, /api, /healthz, /admin, /dashboard/login). The failed-attempt lockout is always on (it only ever penalises repeated authentication failures from one network identity, and runs before the token-hash check so bogus bearers can't exhaust CPU).

Variable Default Description
AUTH_LOCKOUT_MAX_FAILURES 10 Failed auth attempts from one IP within the window before it is locked out (429).
AUTH_LOCKOUT_WINDOW_MS 60000 Window (ms) over which failures accumulate.
AUTH_LOCKOUT_MS 60000 Lockout duration (ms) once tripped. A successful auth clears the identity's counter.
MAX_BODY_BYTES 4194304 Max raw request body on /mcp and /api; larger is rejected with 413.
REMBRIC_MCP_ALLOWED_HOSTS unset Comma-separated Host allow-list (incl. port) for opt-in DNS-rebinding protection on the MCP transport. When set, requests with an unlisted Host are rejected.
REMBRIC_MCP_ALLOWED_ORIGINS unset Comma-separated Origin allow-list. When set, a request whose Origin is present and unlisted is rejected. Setting either allow-list enables the transport's rebinding check.

OAuth token confinement. An OAuth grant is bound to the project it was consented for (the connector's /mcp/<slug> path, carried as the RFC 8707 resource indicator): the minted token authorizes only that project (project:<id> / read:project:<id>), or global scope for a path-less /mcp grant. Deploying this version revokes all previously-issued OAuth tokens, so existing OAuth clients re-authorize once (static tokens are unaffected). The dashboard session cookie is marked Secure when REMBRIC_PUBLIC_URL is https.

Sessions

Variable Default Description
SESSION_ABANDON_AFTER_MS 86400000 At server startup, sessions with status='active' whose started_at is older than this are flipped to abandoned. Default 24h; floor 1min, ceiling 30 days.

Candidate detection (save-time judgment)

Controls how memory.save surfaces conflict candidates to the agent for fresh-context judgment.

Variable Default Description
CANDIDATES_PER_SAVE_MAX 5 Max number of similar memories surfaced per save. 0 disables surfacing (pending rows are still inserted and re-surface via memory.context).
CANDIDATE_VEC_THRESHOLD 0.85 Cosine-similarity floor on the embedding match. Range 0..1. Lower = more candidates, more noise.
CANDIDATE_FTS_THRESHOLD 0.4 BM25-derived score floor on the FTS5 match. Range 0..1.

Dashboard

Self-hosted operator surface for every memory, session, prompt, judgment, and consolidation op. SSR HTML + HTMX, brutalist on purpose, no JS framework, no telemetry. Auth is one admin token — no onboarding flow.

Day-to-day operator work lives in the dashboard at http://127.0.0.1:8787/dashboard (port 8788 in the dev stack). Mint and revoke API tokens at /dashboard/tokens, create and archive projects at /dashboard/projects, soft-delete agent sessions at /dashboard/sessions, browse and soft-delete curated user prompts at /dashboard/prompts (FTS5 search over content + tags; refined predecessors show a REFINED badge), trigger consolidation at /dashboard/consolidation, and run the operator-only purges from /dashboard/maintenance. Programmatic agents talk to the same data through the MCP project.* / memory.* tools or, for admin-only operations, through the HTTP endpoints under /admin/* (admin bearer token required — see docs/agents.md).

Maintenance (manual purges)

The dashboard exposes /dashboard/maintenance for three irreversible, operator-triggered physical purges. All are gated to dashboard sessions whose underlying token has scope * (admin):

  • Purge empty sessions — removes ended / abandoned session rows that have no summary, no manual title, no referencing memories / non-deleted prompts / confirmations, are not operator-soft-deleted, and ended over 1 hour ago. Soft-deleted prompts no longer block purge.
  • Purge disconnected archived memories — removes archived memory rows whose ids are referenced by NO other row in the graph (memory.replaces, consolidation_ops.affected_ids / created_id, memory_relations.{source,target}_id, confirmations.memory_id). The matching memory_vec and memory_fts shadow rows are dropped in the same transaction.
  • Purge deleted prompts — removes prompts rows whose deleted_at IS NOT NULL. Covers both operator soft-deletes (from /dashboard/prompts) and refine supersedes (from memory.save_prompt({ replaces })). The matching prompts_fts shadow row is dropped in the same transaction.

Each click shows a count and a confirmation modal. The deletion is journaled in consolidation_ops (op_type = 'session_purge', 'archived_memory_purge', or 'prompt_purge') so the operator can audit what was removed even after the rows are gone. Consolidation undo on operations whose rows have been purged returns a structured error — the dashboard surfaces it inline naming the missing ids.

To reclaim file-level disk after a large purge, run VACUUM against the SQLite file. The maintenance page surfaces the freelist size so you know how much would be reclaimed.

Architecture

Single Node process, single SQLite file, packaged as a multi-arch Docker image (linux/amd64, linux/arm64). Agents reach it over HTTP(S) + a bearer token at /mcp/<slug> (or /mcp + project.use); the operator dashboard is served from the same process.

Four load-bearing invariants:

  • Append-only: rows are never deleted; content never updated. Lifecycle is status flips + replaces links. Every consolidation op is reversible. An agent MAY retire a memory at the user's explicit request via memory.archive (a reversible, journaled active → archived flip, scoped to the connection — no successor link, distinct from a topic_key/memory.judge supersede); physical purge stays operator-only in /dashboard/maintenance.
  • Project scoping by construction: every memory is global or attached to one project_id. Consolidation and relations never cross scope.
  • Convergent topics via topic_key: on memory.save, the previously-active row in the same (scope, project_id, topic_key) is auto-superseded atomically.
  • Fresh-context judgment: candidate conflicts surface at save time (candidates[]); the agent that produced the conflict judges it. Aged pendings re-surface in memory.context until an agent closes them. The deterministic sweep (no LLM, no cron — runs on session start) only handles decay + deadline orphaning.

Memory also exposes a derived review state alongside decay: a needs_review flag on memory.search / memory.get rows (and a needsReview[] list in memory.context) for active memories whose per-type shelf life elapsed without re-affirmation. It is computed at read time — no column, no sweep — and is cleared by re-affirming with memory.confirm (not by merely reading). See docs/relations.md for the relation taxonomy and the review axis.

                  ┌─────────────────────────────────────────────────┐
                  │              Agents (MCP clients)               │
                  │     Claude Code · Codex CLI · Cursor · …        │
                  └────────────────────────┬────────────────────────┘
                                           │
                                           │  HTTP(S) + Bearer token
                                           │  URL path: /mcp/<slug>  (or /mcp + project.use)
                                           ▼
   ┌───────────────────────────────────────────────────────────────────────┐
   │                       rembric  (single Node process)                  │
   │                                                                       │
   │   ┌────────────────────────────┐   ┌───────────────────────────────┐  │
   │   │  /mcp       /mcp/<slug>    │   │  /dashboard                   │  │
   │   │  Streamable HTTP transport │   │  SSR HTML + HTMX              │  │
   │   │  + initialize.instructions │   │                               │  │
   │   │  memory.{save,search,…}    │   │  /memories /sessions          │  │
   │   │  memory.session_*          │   │  /prompts /judgments          │  │
   │   │  memory.*_prompt(s)        │   │  /consolidation /projects     │  │
   │   │  memory.judge / compare    │   │  /tokens /maintenance         │  │
   │   │  project.{use,list,current}│   │                               │  │
   │   └─────────────┬──────────────┘   └─────────────┬─────────────────┘  │
   │                 ▼                                ▼                    │
   │   ┌───────────────────────────────────────────────────────────────┐   │
   │   │  Service layer                                                │   │
   │   │   MemoryService · PromptsService · RelationsService           │   │
   │   │   ProjectsService · TokensService · AgentSessionsService      │   │
   │   │   SessionRouter                                               │   │
   │   └───────────────────────────────┬───────────────────────────────┘   │
   │                                   ▼                                   │
   │   ┌───────────────────────────────────────────────────────────────┐   │
   │   │  SQLite (Drizzle, append-only + tombstones)                   │   │
   │   │   memory · projects · tokens · sessions · prompts             │   │
   │   │   memory_relations · consolidation_{runs,ops}                 │   │
   │   │   + memory_fts · prompts_fts (FTS5)  + memory_vec (sqlite-vec)│   │
   │   └───────────────────────────────▲───────────────────────────────┘   │
   │   ┌───────────────────────────────┴───────────────────────────────┐   │
   │   │  In-process background work (no external services)            │   │
   │   │   embedder: gte-multilingual-base, ONNX q8, loaded at boot    │   │
   │   │   drain worker (every 30s) fills memory_vec                   │   │
   │   │   deterministic sweep: decay + deadline orphaning             │   │
   │   └───────────────────────────────────────────────────────────────┘   │
   └───────────────────────────────────────────────────────────────────────┘

Two staleness axes, deliberately distinct: decay runs in the background sweep above (access + confidence, keyed on last_seen_at → archives); review is derived in the request path on memory.search / memory.get / memory.context (affirmation, keyed on max(created_at, last confirmation) + per-type TTL → flags needs_review). Review adds no table, no tool, and no background work — it is computed at read time and cleared by memory.confirm.

Development

pnpm install
pnpm run dev          # tsc --watch
pnpm test             # full vitest suite + Hermes Python tests
pnpm run typecheck    # tsc --noEmit
pnpm run lint

For a clean Docker build from source:

docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

CodeGraph (code intelligence)

This repo ships a committed CodeGraph setup so agents can query the codebase as a knowledge graph. The MCP server is registered in .mcp.json (Claude Code) and opencode.jsonc (opencode); both point at a local codegraph binary.

CodeGraph is opt-in per developer — install the binary to enable it:

# install the codegraph CLI, then from the repo root:
codegraph index        # builds .codegraph/codegraph.db (git-ignored, ~11 MB)

If you do not install the binary, nothing breaks: the codegraph MCP entry just shows as failed/unavailable in /mcp and every other workflow is unaffected. The index, daemon socket, pid, and logs under .codegraph/ are git-ignored (only .codegraph/.gitignore is committed) — they are local to each machine and never belong in a commit.

More docs

  • docs/docker.md — Docker operator guide (topologies, GHCR auth, upgrade/rollback, troubleshooting).
  • docs/agents.md — per-client MCP config (Codex, Cursor, Windsurf, VS Code, Gemini, …).
  • docs/backup.md — backup strategies and restore recipes for the SQLite data file.
  • docs/updates.md — update notifications and the opt-in one-click auto-updater from the dashboard.
  • docs/troubleshooting.md — common errors and recovery.

Project status

Rembric is open-source under the MIT License — as-is, no warranty. The maintainers do not accept responsibility for operator data loss, deployment downtime, or any other operational consequence of running the software.

  • Security: vulnerability reports go through SECURITY.md. Preferred channel is GitHub Security Advisories.
  • Backups: the entire server state lives in one SQLite file. Operators are responsible for backing it up; see docs/backup.md for sqlite3 .backup cron, manual snapshots, and litestream.
  • Versioning: SemVer, driven by release-please. Pre-1.0 means the wire and HTTP contracts may shift across minors; check release notes before upgrading.

Contributing

See CONTRIBUTING.md. Commits follow Conventional Commits; pre-commit lints and typechecks; pre-push runs the full test suite.

Sponsor

If Rembric saves you time, you can support its maintenance through GitHub Sponsors, Ko-fi, or Buy Me a Coffee.

License

MIT — see LICENSE.