Skip to main content
Glama
README.md
# Ebb — an agent-first knowledge graph

Long-term memory for AI agents, built as a graph. Relevance is **recency-weighted
connection strength**: reusing knowledge keeps it alive, unused knowledge decays
and is archived, and nothing is deleted until a human signs off. Runs embedded
(zero infra) or on Neo4j (production).

The two mechanisms it's built around — connection-weighted relevance and
time-decay — are native graph operations, and both have deep prior art
(PageRank/centrality; ACT-R base-level activation and spreading activation from
cognitive science; spaced-repetition forgetting curves). This is a small, honest
implementation of that lineage aimed specifically at agent memory.

---

## Why it's built this way

**1. The engine and the interface are separate.** The graph store sits behind a
small interface (`GraphStore`). Agents and the scoring logic never touch a
specific database, so you can run the exact same graph on an embedded engine
today and swap to Neo4j later with one env var.

**2. Relevance is recency-weighted, not raw connection count.** "More
connections = more relevant" rewards old, heavily-referenced data forever — the
exact stale-data problem the system is meant to kill. Here, every edge's
contribution to relevance is multiplied by a time-decay factor keyed to *when the
connection was last reinforced*. An edge reinforced yesterday counts near-full;
one last touched six months ago counts for almost nothing. Reusing a connection
(`recall`/`reinforce`) resets its clock — so relevance tracks what's actually
live, and stale knowledge sinks on its own.

Proof, from the demo seed graph (`python -m ebb.demo`):

```
node                             raw#   activation
decision:outcome-pricing            3        6.116   <- fresh, few links, ranks #1
decision:seat-pricing              11        3.077   <- MOST links, ranks #3
...
note:analysis-* (x10)               1        0.051   <- decayed -> archived (tier 4)
```

The superseded per-seat decision has the **highest raw connection count in the
graph** and still ranks third, behind a fresh decision with a third as many
links. Raw count lost; recency won.

---

## What's in it

- **Graph model** — every note, decision, meeting, person, client, fact is a
  node; every reference is a *timestamped, typed, weighted* edge.
- **Scoring engine** (`scoring.py`) — recency-weighted activation, exponential
  decay (configurable half-life), one hop of spreading activation (a portable
  stand-in for PageRank), and tier assignment. Pure functions, fully unit-tested.
- **Four archive tiers** — 1 hot (default recall) · 2 warm (deeper recall) ·
  3 cold (archived, on-demand only) · 4 frozen (**pending human sign-off before
  deletion**). Pinned nodes never auto-archive.
- **MCP server** (`mcp_server.py`) — the agent interface: `remember`, `recall`,
  `connect`, `reinforce`, `forget`, `neighbors`, `pin`, `maintain`,
  `review_queue`, `stats`.
- **Two backends** — `KuzuStore` (embedded, default) and `Neo4jStore`
  (production), same interface, same Cypher shapes.

---

## Quickstart (embedded — zero infra)

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

python -m ebb.demo      # narrated end-to-end walkthrough
pytest -q                 # 11 tests, all green
```

No Docker, no server, no ports. Kùzu is an in-process graph database, so the
Ebb is just a folder (`./ebb_db`).

## Plug it into an MCP client (e.g. Claude Desktop)

1. Copy the `ebb` block from `claude_desktop_config.example.json` into your
   client's MCP config, fixing the absolute paths.
2. Restart the client. The ebb tools appear in the tools menu.
3. The agent can now `remember` things across sessions, `recall` what's relevant,
   and `reinforce` what it keeps using — with decay and archival handled for it.

## Production mode (Neo4j)

```bash
docker compose up -d      # Neo4j + Graph Data Science + APOC
EBB_BACKEND=neo4j NEO4J_PASSWORD=brainbrain python -m ebb.demo
```

Same code, same behavior. On Neo4j you additionally get the GDS library, so the
spreading-activation pass in `scoring.py` can graduate to real PageRank /
centrality / community detection when scale demands it. (The Neo4j backend's
Cypher mirrors the fully-tested Kùzu backend; run `pytest` against a live
instance before trusting it in prod.)

---

## The model, briefly

Activation of a node =
`Σ (edge.weight × decay(age_since_last_reinforced)) + read-recency-bonus`,
plus one damped hop of the same from its neighbours. Decay is a half-life
(default 30 days, tunable). Tiers are cut on the activation normalised against
the most-active non-pinned node. `recall` blends this activation with query
text-match and returns *why* each result surfaced. Everything is tunable in one
place — `ebb/scoring.py::Config`.

## Writing an ingestion adapter

Ebb is source-agnostic: anything that calls `remember`/`connect` can feed
it. A source (a notes folder, a wiki, an issue tracker) becomes a graph by
mapping documents to nodes, links/mentions to edges, and an edit timestamp to
the recency clock. Keep adapters and their data out of the repo.

## Layout

```
src/ebb/
  model.py        # Node, Edge, tiers
  scoring.py      # decay, activation, spreading, tiering  <- the core
  store.py        # GraphStore interface
  kuzu_store.py   # embedded backend (default)
  neo4j_store.py  # production backend
  engine.py       # Brain: remember/recall/connect/reinforce/maintain/...
  mcp_server.py   # agent-facing MCP tools
  seed.py         # fictional demo graph
  demo.py         # narrated walkthrough
tests/            # 11 tests: scoring + end-to-end
docker-compose.yml
```

## License

MIT — see `LICENSE`.

TDQS

A3.5/5.0

Scored across 10 tools

Disambiguation5/5

Each tool serves a distinct operation: node creation/update, edge creation, edge reinforcement, query, neighbor listing, deletion, pinning, maintenance pass, review queue access, and statistics. No two tools overlap in purpose.

Naming Consistency4/5

All tool names are single lowercase words, with most being verbs (remember, connect, reinforce, recall, forget, pin, maintain) and a few nouns (neighbors, review_queue, stats). The style is consistent but mixes verb and noun forms, a minor deviation from a pure verb-noun pattern.

Tool Count5/5

With 10 tools, the server is well-scoped for a knowledge graph with memory decay and archival. Each tool earns its place, covering creation, linking, querying, deletion, and maintenance without unnecessary bloat.

Completeness4/5

The surface covers the full node lifecycle (remember/forget), edge operations (connect/reinforce), querying (recall/neighbors), and archival (pin/maintain/review_queue). Minor gaps exist—no explicit edge deletion and no direct node fetch by ID—but recall and forget adequately work around these.

Maintenance

ActivityMaintained
ResponsivenessNo issues