Metadata-Version: 2.4
Name: canongraph
Version: 0.1.1
Summary: An embeddable, event-sourced context graph with an MCP interface. Zero external dependencies by default (SQLite + Kùzu); scales to Postgres + Neo4j. Define your domain in one topology YAML.
Author: jvattimo1
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/jvattimo1/canongraph
Project-URL: Repository, https://github.com/jvattimo1/canongraph
Project-URL: Issues, https://github.com/jvattimo1/canongraph/issues
Keywords: context-graph,knowledge-graph,mcp,model-context-protocol,event-sourcing,graph-database,embeddings,rag,llm,kuzu,neo4j
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database :: Database Engines/Servers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.6
Requires-Dist: pyyaml>=6.0
Requires-Dist: mcp>=1.2
Requires-Dist: uvicorn>=0.30
Requires-Dist: model2vec>=0.3
Requires-Dist: kuzu>=0.11
Provides-Extra: server
Requires-Dist: psycopg[binary]>=3.1; extra == "server"
Requires-Dist: psycopg-pool>=3.2; extra == "server"
Requires-Dist: neo4j>=5.20; extra == "server"
Provides-Extra: st
Requires-Dist: sentence-transformers>=2.2; extra == "st"
Provides-Extra: pdf
Requires-Dist: pypdf>=4.0; extra == "pdf"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Requires-Dist: canongraph[server,st]; extra == "dev"
Dynamic: license-file

<!-- LAUNCH STEP: these logo paths are RELATIVE so they render on GitHub while the repo is private.
     At go-public, switch both to absolute (https://raw.githubusercontent.com/jvattimo1/canongraph/main/assets/…)
     so the logo also renders on the PyPI page. Absolute raw URLs only resolve once the repo is public. -->
<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="assets/canongraph-logo-dark.svg">
    <img src="assets/canongraph-logo.svg" alt="CanonGraph" width="420">
  </picture>
</p>

<p align="center"><b>One brain for every AI you use — private, portable, and yours.</b></p>

<p align="center">
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="Apache 2.0"></a>
  <a href="https://github.com/jvattimo1/canongraph/actions/workflows/ci.yml"><img src="https://github.com/jvattimo1/canongraph/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+">
  <img src="https://img.shields.io/badge/runs-100%25_local-14b8a6.svg" alt="Runs locally">
</p>

---

## What is this?

Every time you close a chat, your AI forgets everything. You re-explain your projects, your team, your decisions — over and over. And your knowledge ends up trapped inside whatever model you happened to use.

**CanonGraph gives your AI a persistent, private memory that *you* control** — a knowledge graph it builds *as you work*. Tell it something once and it remembers: your projects, the people involved, what was decided, the documents that matter. Next week, it already knows.

The key idea: **the memory belongs to you, not to any one AI.** It lives on your machine, in your own database, and any assistant can plug into it — Claude today, a different model tomorrow. Switch providers and you lose nothing, because your knowledge was never locked inside the model.

Think of it as **a second brain your AI agents share** — portable, private, and yours.

## Who is it for?

Anyone tired of re-explaining their world to an AI every single session:

- 🧑‍💻 **Developers** — so your AI remembers your codebase, architecture decisions, and conventions.
- 🔬 **Researchers & writers** — a memory of papers, ideas, sources, and how they connect.
- 🏢 **Teams** — a shared, structured memory of clients, deals, or projects (scales from your laptop to a shared server).
- 🧠 **Anyone** who wants an AI that actually knows their world.

**Works with the agents you already use.** Claude Code and Claude Desktop get the smoothest setup (a one-command plugin and a guided wizard), and any [MCP](https://modelcontextprotocol.io)-capable client works out of the box — Cursor, Cline, and others. Anything that *isn't* MCP — a custom script, another model, your own app — connects over a plain REST API. **One memory, many front ends.**

No account, no cloud, no subscription. It runs locally, your data stays yours, and it's open source (Apache-2.0) — so you can build on it freely.

---

## Try it in 60 seconds

See it before you set up anything. One command seeds a small, self-contained **sample** memory and opens it as a live, interactive graph:

```bash
uv tool install canongraph     # or:  pipx install canongraph  /  pip install canongraph
canongraph demo                # seeds a sample memory, shows recall + its source, opens the viewer
```

You'll get an instant answer *with its source* right in the terminal —

```
You ask:  "Why are we pursuing the Aurora deal?"
◆ CanonGraph recalls:
    Acme Robotics is the category leader in warehouse automation… gross margins
    have expanded from 48% to 61%… net revenue retention is 132%.
    ◈ source: aurora-ic-memo.pdf
```

— then the graph opens in your browser to explore. **It's honest sample data** in a dedicated, isolated `demo` profile: it never touches your real memory, and you can remove it any time (`rm -rf ~/.canongraph/demo`). When you're ready, point it at *your* world with `canongraph init` (below).

> Prefer to stay in the terminal? `canongraph demo --no-open` seeds and prints the recall moment without launching the viewer.

---

## See it in action (about 2 minutes)

```bash
# 1. Install (one command)
uv tool install canongraph          # or:  pipx install canongraph  /  pip install canongraph

# 2. Create your memory (starts from a sample you can shape to your own world)
canongraph init

# 3. Connect it to Claude, and check it's healthy
canongraph config --client claude-desktop   # prints the snippet to paste into Claude
canongraph doctor                            # → status: OK
```

> 💡 **Want Claude to design the memory *around your world* for you?** That's the `/context-init`
> skill in the [Claude Code plugin](#see-it-in-action-about-2-minutes) below — Claude interviews
> you ("what kinds of things do you want me to remember?") and sets it all up. `canongraph init`
> on its own just gives you a ready-to-use starter you can edit.

Now just **talk to Claude normally.** As you work, it remembers the important things. Later:

- *"What do we know about the Acme deal?"* → Claude recalls it from your graph.
- `canongraph view` → open a private local page and **explore everything it remembers** as a live, interactive graph — search it, click any node for its sources ("why do you believe this?"), scrub through time, watch new memory glow in as it's captured, and **customize the HUD** (theme, motion, node size, labels, freeze) to taste.
- `canongraph redact <doc>` → tell it to **forget** something, for real.

> **Install the Claude Code plugin** to skip the manual client config and get a `/context-init`
> setup wizard:
> ```
> uv tool install canongraph          # the plugin needs this binary on PATH to run
> /plugin marketplace add jvattimo1/canongraph
> /plugin install canongraph@canongraph
> /context-init
> ```
> The plugin **registers** the MCP server config for you — but it still needs the `canongraph`
> binary installed (e.g. `uv tool install canongraph`) for that server to actually run. The
> `/context-init` skill checks for it first and tells you if it's missing.

### It keeps itself up to date

You don't hand-feed CanonGraph. As you work, your assistant captures the durable facts —
decisions, people, projects, how they connect — into the memory, on **your own model** (the
engine never calls an LLM). When it's unsure, it records the fact with **low confidence** and the
pruning policy quietly **demotes** it out of trusted recall, so guesses never pollute what you get
back. At the end of a session the whole conversation is preserved, searchable. Your memory stays
current without you thinking about it.

Controls (all optional):

| Env var | Effect |
|---|---|
| `CANONGRAPH_NO_AUTOCAPTURE=1` | Turn off all automatic capture at session end. |
| `CANONGRAPH_NO_AUTODEMOTE=1` | Keep session-end capture but skip the unsure-fact demote. |
| `CANONGRAPH_DEMOTE_FLOOR=0.35` | The confidence below which a fact is treated as unsure. |
| `CANONGRAPH_STRUCTURED_AUTOCAPTURE=1` | *(Claude Code)* also run a structured background pass over the transcript. Off by default — it uses your model's tokens. |

---

## Why it's different

Most "AI memory" either lives in someone else's cloud or dumps your chats into a pile of text. CanonGraph leads with what *you* get — the engineering is just the proof it's real:

| What you get | What it means · and why it's true |
|---|---|
| **You own it** | Runs 100% on your machine — your files, open source, no cloud, no account, no subscription. *Proof: local database + local embeddings; nothing leaves your machine.* |
| **A real graph, not a note pile** | Things and how they connect — and "Acme / Acme Corp / ACME" fold into **one**, instead of piling up. *Proof: typed entities + relationships; it surfaces the existing entity so it's reused, not duplicated.* |
| **You can trust it** | Ask *"why do you believe this?"* and see the source. Tell it to forget, and it's gone for real. *Proof: an event-sourced log is the source of truth; redact/retract physically delete + scrub.* |
| **Any model, forever** | Claude today, a different model tomorrow — over MCP *or* plain REST. Switch providers, keep your memory. *Proof: MCP-native + a REST API over the same store; no lock-in.* |
| **It never quietly corrupts** | Your memory is always reconstructable — nothing silently rots. *Proof: the graph is a rebuildable projection of an append-only log.* |

---

## What you can build with it

- **A personal second brain** — an AI that remembers your projects, contacts, reading, and decisions.
- **Deal / CRM intelligence** — a graph of companies, people, and interactions your AI can reason over.
- **A research assistant** — papers, findings, and their connections, semantically searchable.
- **Codebase context** — architecture, conventions, and history your AI keeps straight.

Because it's **Apache-2.0 licensed and headless** (no locked-in UI), you can wrap it, embed it, or extend it however you like.

---

## One brain, many agents

Run CanonGraph once as a shared service and point *multiple* agents at the same memory — e.g. Claude Code and another assistant working side by side on one knowledge base:

- **Everyone reads at once.** Lookups run concurrently — no waiting.
- **Writes are safe.** Saves are taken one at a time by a shared lock, so two agents writing at the same instant can never corrupt the memory or overwrite each other. Nothing is lost — a write just waits its (millisecond) turn. It's [event-sourced](#under-the-hood-for-the-curious), so the memory is always reconstructable.
- **Facts merge instead of colliding.** Because CanonGraph recognizes when two agents mean the *same thing*, if both add facts about "Acme" they fold into one clean record — not duplicates. Genuine collaboration, not a mess.

For a single user on one machine, the default **embedded** mode is all you need. For multiple agents (or a team) sharing one memory, flip to **server** mode — `CG_TRANSPORT=http canongraph serve`, backed by Postgres + Neo4j — and every agent connects over MCP or REST to the same brain.

> **The trade-off, honestly:** writes are serialized (they take turns) rather than running truly in parallel. For a memory layer that's the right call — correctness first — and reads, the common case, are fully concurrent.

### Many memories, one AI (opt-in multi-profile)

You can keep **separate memories** — e.g. `work` and `personal` — each its own [profile](#commands) with its own topology and data (`--profile NAME`, or `canongraph profile use NAME`). The MCP server serves **one active profile**, and **isolation is the default**: with no extra argument every tool reads and writes the active profile *only*, so your personal memory never bleeds into work.

When you *want* your AI to look **across** memories, it's **opt-in and read-only**:

- **`list_profiles`** lets the AI discover *what memories you have* — each profile's name, topology, entity/relationship/document counts, and which is active.
- The **log-store-answerable read tools** — `get_topology`, `resolve`, `describe_entity`, and `search` — take an optional **`profile`** argument. Given it, they answer from *that* profile, **read-only**, opened the same collision-safe way as the [viewer](#commands) (the durable log, never the other profile's graph — so it can't lock-collide with a running server and can never write to it).
- **Writes stay single-profile.** `ingest` / `ingest_document` / `merge` / `alias` / `retract` / `redact` operate on the **active profile only** — there is no cross-profile write. To write into another memory, switch to it (`canongraph profile use NAME`). This is a safety boundary, not an oversight.
- **Graph-only tools stay active-only.** `query` (named Cypher) and `benchmark` run against the active profile's live graph projection; asked to target another profile they return a clear message (switch profiles, or use `search`/`resolve`/`describe_entity`, which *do* cross).

**One honest caveat about cross-profile `search`.** Your **active** profile's `search` is full **hybrid** (vector + keyword). **Cross-profile** `search` is **keyword-only** (full-text) — by design: a non-active profile's vector index lives in its graph, which we deliberately don't open (collision-safe coexistence), so only its log store's full-text index is used. Expect comparable exact-term recall but weaker paraphrase recall than the active hybrid path; results are attributed by `profile`.

---

## Working with a team

Two ways for multiple people to share memory — pick by whether you want **one shared brain** or **your own brains that sync**.

**One shared brain (server mode).** Run *one* CanonGraph backed by [Postgres + Neo4j](#install) and point everyone's agent at it (MCP or REST, behind a shared token). Reads run concurrently; writes take safe turns via a cross-instance lock (event-sourced, so nothing's lost — see [One brain, many agents](#one-brain-many-agents)). Every write records **who made it** (`CG_ACTOR`), and entity resolution folds same-named facts into **one** entity — so the team collaborates on a single, deduplicated, attributed knowledge base. Best when you want one source of truth.

**Personal brains that sync (signed replication).** Each person keeps their **own** local memory and shares by exchanging signed log segments — `canongraph sync export` → teammate `canongraph sync import` (see [Team sync](#team-sync-signed-log-replication)). Segments are **HMAC-signed** with a shared team key, carry each event's **actor** + an idempotency key (re-importing is a no-op), and merge **additively** — non-conflicting facts converge across the team, and a **retraction survives re-sync**. Best when you want data ownership + selective sharing (like git for memory). You can combine the two: personal brains that also push segments to a shared one.

**Honest limits (today) — these are *trusted-team* features, not enterprise multi-tenant:**
- **No per-user access control within one memory.** Shared server mode uses **one token** — no per-user login, roles, or row-level permissions yet; everyone with the token sees and writes everything.
- **Server-backed profiles share one database.** Isolation between memories is an *embedded* (per-directory) property today; per-tenant isolation on the server backend is future work.
- **Sync uses a shared secret and is manual.** One HMAC team key (not per-user asymmetric keys), via export/import (or a script), not real-time.
- **Additive merge, not a CRDT.** Concurrent edits to the *same field* resolve last-writer-by-import.

These and the other current v1 boundaries are documented plainly in **[LIMITATIONS.md](LIMITATIONS.md)**; where they're headed is on the **[roadmap](ROADMAP.md)**.

---

## How it works

You never manage the memory by hand — Claude does it for you:

```
   You talk to Claude  ─────────►  Claude notices what's worth remembering
                                              │
                    ┌─────────────────────────┴─────────────────────────┐
                    ▼                                                     ▼
        Structured facts                                        The raw content
   (Acme is a company in the                              (the note / doc, kept whole
    cloud sector, advised by…)                             and semantically searchable)
                    │                                                     │
                    └──────────────►  your private memory graph  ◄────────┘
                                              │
   Later:  "what do you know about X?"  ──────┘   →  Claude recalls, with sources
```

Two things make this trustworthy:

1. **You define the *shape* once — but you don't have to up front.** A **topology is not required to start**: every memory ships with a generic starter shape, so you can capture from minute one. When you *want* structure, the `/context-init` skill (in Claude Code) interviews you — "what kinds of things do you want me to remember?" — and sets it up; prefer the terminal? `canongraph init` starts from an editable sample, or `canongraph init --from-spec your.yaml` uses your own. As your needs grow, `canongraph extend` adds new kinds of things safely.
2. **You stay in control.** Everything Claude remembers carries its **source** ("why do you believe this?"), you can **browse it** (`canongraph view`), and you can **redact a document** (`canongraph redact`) — its text, chunks, and embeddings are removed from the active store, and freed pages are best-effort scrubbed. This is strong hardening, not a cryptographic erase guarantee: at-rest confidentiality still relies on full-disk encryption, and redaction removes the *document* — not any structured facts already extracted from it (see [SECURITY.md](SECURITY.md) for the exact boundary).

### Two layers, one memory

- **Structured layer** — the tidy graph of *things and relationships* you query precisely.
- **Document layer** — anything else you throw at it (notes, files, transcripts) kept whole and **searchable by meaning**.

Nothing is lost just because it didn't fit a neat category — and the things you care about stay clean and queryable.

---

## Under the hood (for the curious)

CanonGraph is **event-sourced**: everything you tell it is appended to an immutable log — the single source of truth. The queryable graph and the search index are *projections* rebuilt from that log, so your memory is always reconstructable and never silently corrupted.

```
  your topology (what to remember)
            │
            ▼
    ┌───────────────┐     ┌──────────────┐     ┌───────────────────┐
    │  event log    │ ──► │  projector   │ ──► │  knowledge graph  │ ──► MCP tools
    │ (source of    │     │ (rebuildable │     │  + hybrid search  │     (Claude works
    │   truth)      │     │  projections)│     │                   │      through these)
    └───────────────┘     └──────────────┘     └───────────────────┘
```

### How `search` works (hybrid, graph-aware)

`search` is **hybrid**: it runs two retrievers over your document chunks and fuses them, so it
handles *both* fuzzy paraphrase questions and precise exact-term lookups (codenames, ticket
numbers, identifiers) — the cases pure vector search ranks poorly.

- **Vector retriever** — semantic kNN over local chunk embeddings (meaning-based recall).
- **Keyword retriever** — lexical full-text search over the same chunks (SQLite **FTS5**
  embedded / Postgres **tsvector + GIN** on the server), with stemming, so a rare exact token
  reliably surfaces its document.
- **Reciprocal Rank Fusion (RRF)** — the two *rankings* are merged rank-by-rank (not by raw
  score), sidestepping their incompatible scales; a chunk both retrievers agree on rises to the
  top.

Each returned passage also carries `mentions`: the graph entities that chunk is linked to
(`{id, name, type}`) — graph context, not just text.

**What `score` means:** the **RRF fused score** (higher = more relevant). It combines the two
*rankings*, so it is **not** a cosine similarity or a probability — treat it as a relative
ordering signal *within one response*, not an absolute threshold. The keyword index is a
rebuildable projection kept in lockstep with the chunk set on every ingest and rebuild.

*Optional precision upgrade:* with the `[st]` extra installed, pass `rerank=true` to reorder
the fused top passages with a cross-encoder. It's skipped gracefully when `[st]` isn't present
— the lightweight default path needs no torch.

### Time & recency (temporal reads over the log)

Because the log is append-only and every event is timestamped, "what did I know / what was
true, and when?" is answered by **reading the log** — no history table, no schema migration.

- **`observed_at` (valid time).** Any `ingest` event or `ingest_document` call may carry an
  optional `observed_at` (ISO timestamp) recording *when the fact was true in the world*, as
  distinct from its append/transaction time (`ts`). It rides the existing provenance channel
  (`_prov`), so legacy events stay byte-identical; when absent it defaults to `ts` on read.
- **History view (`describe_entity`).** `describe_entity` returns a `history`: the ordered
  value-by-value timeline of an entity, each value carrying its `observed_at` and a **derived**
  `superseded_at` (when a later event replaced it; `null` for the current value). Derived
  read-only from the log — no new storage.
- **As-of queries.** `describe_entity(id_or_name, as_of=<ISO timestamp>)` returns the entity's
  state **as of that time** — an *ephemeral* in-memory replay of the events appended by then that
  **never** touches the materialized tables, the graph projection, or any watermark. It applies the
  merge/alias markers up to that time exactly as a real replay does (a merged-away entity resolves
  into its winner — no phantom). v1 uses **transaction time** (`ts`, "what the log knew by then");
  if `as_of` predates the entity's first event the response reports it did not exist yet.
  **Retracted entities are physically purged from the log, so an as-of query for a time before a
  retraction cannot reconstruct them** (they report absent) — an unavoidable v1 limitation.
- **Search time-decay (recency, opt-in).** Set `CG_SEARCH_DECAY_HALFLIFE_DAYS` (or pass
  `decay_halflife_days` to `search`) to a positive number to rank recent documents higher: each
  fused RRF score is multiplied by an exponential decay `0.5**(age_days / halflife)` on the
  document's age (its `observed_at`, else append `ts`). **Off by default** — equal-age corpora
  are unaffected (the retrieval eval is unchanged with decay on), so it only helps when your
  documents actually span time.

- **Runs anywhere, zero setup by default.** The default backend is fully embedded — [SQLite](https://sqlite.org) + [Kùzu](https://kuzudb.com) — so `pip install` and go. No Docker, no database to run. Embeddings are computed **locally** (no API key, no data leaving your machine).
- **Scales to a team** by flipping one setting to the **Postgres + Neo4j** backend — same code, same tools.
- **Describe your world in one YAML file** (a "topology"). Swap it to repurpose the same engine for any domain; the engine code never changes.
- **Speaks [MCP](https://modelcontextprotocol.io)** (the Model Context Protocol), so any MCP-capable assistant can use it — no custom integration.

### Commands

| Command | What it does |
|---|---|
| `canongraph init` | Set up a new memory (from a sample, or your own topology) |
| `/context-init` *(plugin skill)* | Have Claude interview you and design the memory around your world |
| `/recall <question>` *(plugin skill)* | Answer a question from your memory with **grounded, provenance-checked** results — resolve the entities, retrieve + traverse, verify each fact's **source**, and cite them (flags gaps as "not in memory"; read-only) |
| `canongraph config` | Print the snippet to connect it to your AI client |
| `canongraph doctor` | Health check — is everything wired up? |
| `canongraph view` | Open a private local page to **explore** your memory — an interactive graph you can search, drill into for sources, and scrub through time (live-updating, read-only, loopback-only) |
| `canongraph list` / `show <thing>` | Inspect what's remembered (with sources) |
| `canongraph redact <doc>` | **Forget** a document — permanently |
| `canongraph retract entity <type> <key>` / `retract relationship <rel> <from> <to>` | **Forget** a wrong structured fact — an entity (its relationships cascade) or one relationship. Retracting a merge winner / alias target is refused (v1 boundary — retract before merging/aliasing) |
| `canongraph extend` | Add new *kinds* of things to your memory, safely |
| `canongraph sync export` / `sync import <segment>` | **Share memory with a teammate** via a signed, deduplicated log segment (see [Team sync](#team-sync-signed-log-replication)) |
| `canongraph ingest-transcript <path>` | **Record a Claude Code session** as searchable memory — extractive, idempotent (see [Automatic capture](#automatic-capture)) |
| `canongraph serve` | Run the memory server (what your AI connects to) |

Run `canongraph -h` for the full command + flag reference. Every command takes `--profile NAME` to target a specific memory.

### Team sync (signed log replication)

Two teammates can **share** their memory by exchanging **signed, deduplicated log segments**. Because the event log is the source of truth, replication is just: export a signed slice of the log → your teammate verifies and imports it → their projection rebuilds.

```bash
export CG_SYNC_KEY="a-shared-team-secret"   # both teammates use the SAME key (never commit it)
export CG_ACTOR="alice"                      # your identity — every fact records who created it

# Alice exports everything (or everything since a cursor) to a file:
canongraph sync export --out alice-segment.json
canongraph sync export --since 42 --out alice-delta.json   # only events past event_id 42

# Bob verifies + imports it into HIS memory, then his graph rebuilds:
canongraph sync import alice-segment.json
```

What's guaranteed:

- **Attribution** — every event records its real author (`CG_ACTOR`, or the profile's `actor`, else your OS login). Imported facts keep the *originating* teammate's actor.
- **Signed + fail-closed** — each segment carries an HMAC-SHA256 signature over its contents, keyed by the shared `CG_SYNC_KEY`. A tampered segment, one signed with a **different** key, or one built for an **incompatible topology** is rejected — importing **nothing**.
- **Idempotent** — every event has a stable content-hash key, so re-importing the same segment (or overlapping segments) adds **0** the second time.

Merge semantics — an **honest v1 boundary**. Imported facts are *appended* to your local log and replay in local arrival order. **Non-conflicting facts converge**: teammates adding facts about different entities (or additive facts) all reach the same union after a full exchange, and same-named entities fold together via entity resolution. But **concurrent edits to the *same* field of the *same* entity are *not* auto-reconciled** — the result is last-by-local-replay-order, not a principled merge. This is *signed additive replication, not a CRDT*. See [SECURITY.md](SECURITY.md#team-sync-signed-log-replication-fable-17).

### Automatic capture

So you never have to remember to say "remember this," CanonGraph can **automatically record each Claude Code session as searchable memory** when the session ends. A `SessionEnd` hook (shipped in the plugin) runs `canongraph ingest-transcript` on the session transcript, which pulls out the **substantive user and assistant turns** — dropping tool calls, tool output, thinking, and system reminders — and stores them as one searchable session document. Later you can ask "what did we work on last Tuesday?" and `search` will surface it.

**Be honest about what this is.** This is **extractive**: it *records/captures the session as searchable memory* — it does **not** AI-summarize it. CanonGraph's engine has no LLM. The proactive [`capture`](skills/capture/SKILL.md) skill is the thing that extracts **structured facts** (entities, relationships, decisions with provenance) during a session; this hook is the automatic **safety-net** that preserves the session itself so nothing is lost.

It's designed to be **non-intrusive**: `SessionEnd` is non-blocking, and the hook swallows every error and always exits 0 — a capture failure can never wedge Claude Code. It no-ops cleanly if the `canongraph` binary isn't installed. Re-capturing the same session **updates** its one document (a stable per-session `doc_id`) instead of duplicating it.

Run it by hand any time:

```bash
canongraph ingest-transcript ~/.claude/projects/<project>/<session-id>.jsonl
canongraph ingest-transcript <path> --dry-run     # show what it WOULD capture, write nothing
```

**Enable / disable.** The hook is active whenever the plugin is installed. To turn it **off**, either set `CANONGRAPH_NO_AUTOCAPTURE=1` in your environment, or remove/rename `hooks/hooks.json` in the plugin. (Respects `CG_READ_ONLY=1`, which refuses the write entirely.)

### The tools your AI uses

**Read:** `get_topology` · `resolve` (find the canonical thing) · `describe_entity` (one entity — its history + provenance) · `query` · `benchmark` · `search` (hybrid: vector + keyword) · `list_profiles` (discover your separate memories).

**Write:** `ingest` (remember a fact) · `ingest_document` (remember a document) · `merge` / `alias` (unify duplicates) · `retract` (forget a fact) · `redact` (forget a document).

Isolation is the default — every tool reads/writes the **active** profile only. The read tools (`list_profiles`, `get_topology`, `resolve`, `describe_entity`, `search`) accept an opt-in `profile` for a **read-only** cross-profile look; writes stay single-profile. See [Many memories, one AI](#many-memories-one-ai-opt-in-multi-profile).

### Getting data in

There are exactly **two ways** data gets into CanonGraph — plus one automatic path — and **none of them needs a framework. A connector is anything that calls the API; there is no plugin system to learn.**

**1. LLM-driven (the common case) — the LLM *is* the connector, no code.**
Your assistant reads a source with **its own** MCP tools (Gmail, Slack, GitHub, a local file, a web page) and then calls CanonGraph's `ingest` / `ingest_document` tools to remember what matters. Nothing to install between them — that's why there is no connector SDK. For example, you just say:

> *"Read the last few messages in our #launch Slack channel and remember who owns each launch task."*

The LLM pulls the messages with its Slack tool and writes the people, tasks, and ownership as facts via `ingest`.

**2. Programmatic (automated / scheduled / bulk) — a script POSTs to the REST API.**
Any script, in any language, that can make an HTTP request is a connector. POST facts to `/api/v1/ingest` or a document to `/api/v1/ingest_document` with `Authorization: Bearer $CG_AUTH_TOKEN`. **That script is your connector — the REST/MCP surface *is* the connector interface.** A one-liner that remembers a document:

```bash
curl -H "Authorization: Bearer $CG_AUTH_TOKEN" -H "Content-Type: application/json" \
     -d '{"doc_id":"notes/kickoff.md","text":"Kickoff: Ada owns the launch.","source":"kickoff.md"}' \
     http://127.0.0.1:8000/api/v1/ingest_document
```

Two copyable, **dependency-free** reference connectors live in [`examples/`](examples/): [`ingest_folder.py`](examples/ingest_folder.py) (walk a folder → POST each `.md`/`.txt` as a document) and [`ingest_facts.py`](examples/ingest_facts.py) (POST structured facts). They're starting points to copy, not a framework — see [`examples/README.md`](examples/README.md).

**3. Automatic (no action at all).** With the plugin installed, each Claude Code session is captured as searchable memory when it ends — see [Automatic capture](#automatic-capture) above. So you never have to remember to say "remember this."

The full REST/MCP surface — the interface every connector targets — is below.

### Use it from any model — MCP *or* REST

**Your memory, any model, you own it.** CanonGraph isn't locked to one assistant:

- **MCP** — works out of the box with any MCP-capable client (Claude Code, Claude Desktop, Cursor, Cline). No custom integration.
- **REST** — a thin JSON/HTTP API over the *same* memory, so **any** app or model — a raw OpenAI/Gemini script, LangChain, a shell one-liner — can read and write it over HTTP. No MCP required.

Run the server over HTTP (`CG_TRANSPORT=http canongraph serve`) and it serves **both** the MCP endpoint *and* the REST API on the same port, behind the **same** bearer token (`CG_AUTH_TOKEN`):

```bash
curl -H "Authorization: Bearer $CG_AUTH_TOKEN" \
     -d '{"query":"what drove revenue growth"}' \
     http://127.0.0.1:8000/api/v1/search
```

REST routes (all under `/api/v1`, all requiring `Authorization: Bearer $CG_AUTH_TOKEN`):

| Route | Method | Tool |
|---|---|---|
| `/api/v1/topology` | GET | discover entities, queries, metrics |
| `/api/v1/search` | POST | hybrid search (vector + keyword, RRF) `{query, top_k?}` |
| `/api/v1/query` | POST | named query `{query_id, params?, limit?, offset?}` |
| `/api/v1/resolve` | POST | canonical lookup `{entity_type, key\|resolution_keys}` |
| `/api/v1/benchmark` | POST | metric stats `{metric, limit?}` |
| `/api/v1/entity/{id_or_name}` | GET | browse one entity |
| `/api/v1/ingest` | POST | remember facts `{events}` *(write)* |
| `/api/v1/ingest_document` | POST | remember a document `{doc_id, text, source?, links?}` *(write)* |
| `/api/v1/redact` | POST | forget a document `{doc_id}` *(destructive)* |

Auth is fail-closed (401 without/with a wrong token; the server refuses to start unauthenticated unless `CG_ALLOW_ANONYMOUS=1`), `/health` and `/ready` stay open for probes, and `CG_READ_ONLY=1` turns the three write/destructive routes into `403` — the same controls as the MCP transport.

---

## Install

```bash
uv tool install canongraph        # recommended — a stable, always-available command
pipx install canongraph           # alternative
pip install canongraph            # or a plain pip install

pip install "canongraph[st]"      # optional: higher-quality embeddings (larger download)
pip install "canongraph[server]"  # optional: the Postgres + Neo4j team backend
```

**Choosing an embedder.** By default CanonGraph uses a small **local** model (model2vec — no torch, no API). Set `CG_EMBEDDER` to switch: `st` for the higher-quality sentence-transformers path (needs `[st]`), or a **`pkg.module:attr` import spec** to plug in your own — a class, a zero-arg factory, or a ready instance — so a deployment with existing embedding infrastructure can reuse it instead of running a second pipeline:

```bash
CG_EMBEDDER="mycompany.embeddings:ProductionEmbedder" canongraph serve
```

A custom spec **loads and runs the Python you point it at** — an operator-controlled extension point (you're loading your own code), so only set it to code you trust.

**From source** (to hack on it, or to try it before it's on PyPI):

```bash
git clone https://github.com/jvattimo1/canongraph.git
cd canongraph
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest                            # run the test suite
canongraph init && canongraph doctor
```

## Your data & privacy

Your memory lives in plain files under `~/.canongraph/` on your own machine (created private — `0700`/`0600`). Nothing is sent anywhere. `canongraph redact` physically removes content — text, chunks, *and* embeddings — so "forget" means forget. The one network call is a one-time download of the local embedding model from the HuggingFace Hub on first use (pinned to an exact commit, then cached). Details in [SECURITY.md](SECURITY.md).

## Build on it

CanonGraph is **Apache-2.0** — use it, embed it, fork it, ship it, commercially or not. It's headless and MCP-native on purpose, so it's easy to build on. Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).

## License

[Apache License 2.0](LICENSE). All demo data is synthetic.
