- Go 99.7%
- Makefile 0.2%
|
All checks were successful
build-image / build (push) Successful in 19s
Обход supersedes существовал с начала (store.FactPredecessors, рекурсия по первичному ключу), но звал его только recall. Виды, перечисляющие факты одной сущности или одного эпизода, приходили с пустым predecessors — и клиент, показывающий факт, не мог сказать, у кого из них есть прошлое. Читателю оставалось открывать каждую строку или считать, что прошлого нет ни у кого. Один рекурсивный запрос на страницу, не по строке и не группировкой по ключу: у факта без объекта ключ схлопывается в (src, predicate), и группировка отчиталась бы обо всех атрибутах хоста как об одной длинной истории. Заодно шаринг: сервис ListShares/InviteToScope/AcceptShare/RevokeShare/ CancelInvite жил только в MCP, и GUI не мог ни показать, кому область открыта, ни открыть её. Доли — единственное место, где наружу уходит tenant, и намеренно: общее правило в том, что субъект не покидает сервер, но грант с вымаранной второй стороной не говорит владельцу ничего, на что можно действовать — отзыв принимает id получателя, а различить двух получателей на одной области и есть весь вопрос. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| cmd | ||
| configs | ||
| internal | ||
| pkg/types | ||
| .gitignore | ||
| .golangci.yml | ||
| .mcp.json | ||
| CLAUDE.md | ||
| complexity_test.go | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| Makefile | ||
| PLAN.md | ||
| README.md | ||
| rules_test.go | ||
mnemos
Autonomous memory for AI agents. Bi-temporal graph + episodic log + procedural skills. One Go binary, one Postgres. MCP-native. MIT.
mnemos is a memory server that any MCP-compatible client (Claude Desktop,
Claude Code, Cursor, Windsurf, custom agents) can connect to. It stops
agents from forgetting between sessions and gives them a real bi-temporal
knowledge graph instead of a flat vector store.
Why another memory system
| mnemos | Memory MCP | MemPalace | Mem0 | Graphiti | |
|---|---|---|---|---|---|
| Bi-temporal graph | ✅ | ❌ | ⚠️ flat | ❌ | ✅ |
| Procedural skills | ✅ | ❌ | ❌ | ❌ | ❌ |
| Multi-agent scoping | ✅ | ❌ | ❌ | ⚠️ | ⚠️ |
| Sleep consolidation | ✅ | ❌ | ❌ | ❌ | ❌ |
| Single binary | ✅ | ❌ | ❌ | ❌ | ❌ |
| Any LLM (OAI-compat) | ✅ | n/a | ❌ | ❌ | ❌ |
How it works
Writes are fast and dumb; understanding happens later — like sleep.
- observe appends an event to the episodic log (embed + insert, no LLM).
- The consolidate worker ticks in the background: extracts subject–
predicate–object facts from unconsolidated events via any OpenAI-compatible
LLM, and writes them into the graph bi-temporally — a repeated fact
reinforces the existing edge instead of duplicating it, a contradicting
fact supersedes the old one (which stays queryable as history), an
out-of-order historical fact backfills a closed interval. The reflect
step folds
message → tool_call*chains into procedural skills with a running success rate (acorrectionevent inside a chain counts as failure). - recall is hybrid: vector search over facts and episodes, plus 1-hop
graph expansion from query-matched entities, plus pins, plus skills —
filtered by scope and the bi-temporal pivot (
at). - Decay: importance of untouched memories halves every
decay_half_life; anything recall returns gets its clock reset. Old noise fades, things you keep coming back to stay strong.
Scopes are slash-separated namespaces (andy/raidflow) — materialized
paths, so both ancestor and descendant lookups are cheap. Recall has three
modes:
- empty scope → whole tenant. No scope given means "search everything I have" — the sensible default for an agent that hasn't mapped its memory.
- explicit scope → walk-up.
andy/raidflowsees itself + ancestors up to global, but not siblings or children. This is the isolation guarantee: project A's facts never leak into project B. descendants: true→ subtree. Widens an explicit scope to also include everything nested under it.
Call mnemos_scopes to discover which namespaces exist before targeting
one. Facts, events, skills and pins are scoped; entities (nodes) are global.
Two scopes may hold contradicting facts.
Quick start
docker compose up -d postgres ollama # pgvector + local LLM
docker compose exec ollama ollama pull nomic-embed-text
docker compose exec ollama ollama pull llama3.1:8b-instruct
go build -o mnemos .
./mnemos migrate up
./mnemos serve # StreamableHTTP on :8090
MCP endpoint: http://localhost:8090/mcp. Also served on the same port:
/healthz, /readyz, /metrics (Prometheus). For local stdio clients:
server.transport: stdio.
MCP tools
| Tool | Purpose | Key args |
|---|---|---|
mnemos_observe |
record an event into the episodic log | content*, kind (message/tool_call/observation/decision/correction/reflection), scope, parent_id |
mnemos_recall |
hybrid retrieval: vector + 1-hop graph + temporal | query*, scope (empty = whole tenant), descendants, at (RFC3339), since/until, limit |
mnemos_facts |
bi-temporal facts about an entity ("what was true at T") | node_id*, at |
mnemos_skill |
procedural patterns: "how did I do this before" | task*, scope, descendants, limit |
mnemos_search_nodes |
find entities by name (trigram) or meaning (vector) | query*, kind, limit |
mnemos_scopes |
list namespaces that hold memory (counts, last activity) | — |
mnemos_pin |
core memory, always returned by recall | text*, scope, ttl |
There is deliberately no forget/update/delete — contradictions are
handled by bi-temporal supersession in the consolidate worker.
Configuration
Defaults < YAML file < MNEMOS_* env vars. Full annotated example:
configs/mnemos.example.yaml.
./mnemos --config /etc/mnemos.yaml serve
# or pure env:
MNEMOS_DATABASE_DSN="postgres://mnemos:pw@host:5432/mnemos?sslmode=disable" \
MNEMOS_EMBEDDINGS_BASE_URL="http://ollama:11434/v1" ./mnemos serve
Embeddings take any OpenAI-compatible endpoint (Ollama,
OpenAI, DeepSeek, vLLM, proxies). Embedding dimension is fixed at 768 by
the schema — for OpenAI text-embedding-3-* set dimensions: 768 and
request_dimensions: true. The embedder is the only model mnemos calls:
fact extraction was removed once it had been switched off for good, and
the agent writing through MCP is the sole producer of the graph.
Postgres needs the vector, pg_trgm and btree_gist extensions.
vector is not trusted, so on a shared instance pre-create it as
superuser — migrations use IF NOT EXISTS.
Importing existing memory
./mnemos import claude ~/Downloads/claude-export/ # Claude.ai export.zip contents
./mnemos import markdown ~/notes --scope andy/notes # one event per .md file
Imports write plain events; the consolidate worker turns them into graph facts on its own schedule (backfilled timestamps land as historical facts).
Development
make test # unit tests; integration tests skip without a DB
make lint vet
Integration tests run against any pgvector Postgres:
docker run -d --rm -e POSTGRES_USER=mnemos -e POSTGRES_PASSWORD=mnemos \
-e POSTGRES_DB=mnemos_test -p 54329:5432 pgvector/pgvector:pg16
MNEMOS_TEST_DATABASE_URL="postgres://mnemos:mnemos@localhost:54329/mnemos_test?sslmode=disable" \
go test ./... -count=1
Architecture, schema and design decisions: PLAN.md. Conventions and invariants for contributors (and agents): CLAUDE.md.
Status
Sprints 0–7 complete: storage, memory service, MCP server, bi-temporal write path (supersession, dedup/reinforce), decay, importers, observability (Prometheus + OTel). Running against Postgres 16/17 + pgvector 0.8. Not yet: multi-tenant auth, web UI, replication (see PLAN.md §8).
License
MIT