How it works
Boring, inspectable infrastructure: a Laravel MCP server over Streamable-HTTP, native vector search in Postgres, and embeddings that never leave the box.
MCP over Streamable-HTTP
A single endpoint, POST /api/mcp, speaks JSON-RPC 2.0. A transport-agnostic server dispatches
initialize, tools/list, tools/call and ping against a tool registry; the HTTP
controller handles auth and the response envelope. Every request carries a per-agent agt_… bearer key, which the
controller resolves to an Agent and sets as the current context. The REST API reuses that exact auth seam.
MCP client ──JSON-RPC 2.0 (Bearer agt_…)──▶ Laravel (syntheticmem.com)
POST /api/mcp · McpServer::dispatch()
tool → MemoryService
│
local embeddings ◀── OllamaEmbed (nomic-embed-text, 768d)
▼
Postgres 16 + pgvector
vector(768) + HNSW cosine (<=>)
Vectors: Postgres + pgvector
The whole app runs on Postgres 16 with the pgvector extension. Each memory stores a native
vector(768) column indexed with HNSW over cosine distance. Recall is a SQL pre-filter by
agent_id / namespace / status, then an ANN top-k ordered fact →
hypothesis → retired and nearest-first — real vector search from day one, scoped fail-closed to the
acting agent. A fail-closed global scope means a forgotten where returns the current agent’s rows, never everybody’s.
Local ollama embeddings
Embeddings are generated locally by nomic-embed-text (768-dim) via POST http://localhost:11434/api/embed —
no cloud, no key, and the content never leaves the box to be embedded. The same model name is stamped on each row
(embedding_model) so a future model change is auditable.
The prune scheduler
Ephemeral memories (the anonymous-24h rule and any ttl_hours) carry an expires_at. They are hidden from
recall/list once expired, and the memory:prune command hard-deletes expired rows on an hourly schedule
(withoutOverlapping()). See Self-host & ops for the cron wiring.
Data model
| Table | Holds |
|---|---|
agents | One principal per MCP client: name, namespace_default, optional user_id (linked ⇒ permanent memories). |
api_keys | Per-agent agt_… keys, stored as a SHA-256 hash (plaintext shown once), with revoked_at. |
memories | Content + embedding vector(768) + full provenance + status + expires_at; btree (agent_id, namespace, status) and an HNSW index on the embedding. |
memory_consolidate (doubt-preserving episodic→semantic summarization), a WebSocket transport, and hybrid lexical+vector reranking are planned, not shipped. The six tools, provenance, and ephemeral durability are live today.