Kenning Continuity
Enterprise knowledge that accumulates instead of degrading.
An MCP server that lets LLM agents accumulate organizational knowledge across sessions, scoped by domain, backed by Neo4j. Every structural invariant is enforced by a tool rather than asked for in an instruction, and no raw Cypher write is exposed — so the graph cannot corrupt itself no matter what the model decides to do.
It is in production at an enterprise scale: 37 domains, 376 sessions, and 3,000+ knowledge entities accumulated over four months of daily use.
Why this does not degrade as it fills
Most accumulating memory systems fail in one of two ways. They confabulate from the first day, because nothing distinguishes a thing that was observed from a thing that was inferred by a model trying to be helpful. Or they survive the demo and then degrade under mass, because every write is an unconstrained append and the store slowly fills with near-duplicates, silent overwrites, and claims nobody can trace to an author.
Four commitments, all enforced structurally:
Nothing is overwritten. There is no update_knowledge and no
retype_knowledge. Changing what is known about an entity creates a new node
linked to its predecessor by EVOLVED_FROM. The prior understanding is still
there, still readable, still attributable. Being wrong earlier is a fact about
the record, not something to be erased from it.
Every claim reaches its author in two hops. A knowledge entity is
discovered by a Session; a Session is directed by a Person. Both edges
are written by tools that refuse to run without a real director. So "who said
this, and when, and toward what purpose" is always answerable — not by
convention, but because there is no code path that creates knowledge without
it.
Identity is never inferred from a string. Seventeen nodes named
"Alice Example" across eleven domains are seventeen acts of reference,
correctly distinct. Asserting that two of them are one person is a claim someone makes,
in a session, recorded as a REFERENT_CLAIM that can carry the value
distinct — because a system with nowhere to record the answer no will
re-ask the same question forever. String-matching identity is where most
knowledge graphs quietly begin lying.
Structure carries facts; properties do not duplicate them. A knowledge
entity holds no domain property. It is placed in a domain by the session that
discovered it and reached from there by a walk. The property that preceded this
had drifted, which is what denormalizations do: 21 entities carried a domain
string naming a real domain that had no path to them at all. A fact held as
text beside a structure, rather than by it, is a fact that will disagree with
the structure eventually.
There is no confidence field, deliberately. Someone's confidence on an
occasion is a fact about their experience and does not transmit; a later reader
derives their own from the description against what they know. What confidence
scores reach for is carried instead by edges any reader can adjudicate:
VALIDATION, INVALIDATING, and a Challenge with no paired Solution.
If you have run an accumulating memory system at scale and hit these walls yourself, we want to hear from you.
Architecture
Process layer (immutable) — Person → Session, within Domain → Session.
NEXT_SESSION always points forward in time, enforced by the tool. Membership
in this layer is the capacity to hold possible futures and measure them toward
actualization; nothing here is a referent.
Knowledge layer (append-only) — 11 ontological types and 7 sub-labels,
connected by 13 connection types, every node marked :Knowledge. Description
changes create a new chain node linked by EVOLVED_FROM.
See HOWTO.xml for the operator-facing teaching to give an
LLM as invariant scaffolding, and docs/design/ for the
reasoning behind each commitment. HOWTO.xml is installed alongside the
package, so a pip install reaches it at
kenning_continuity/HOWTO.xml inside site-packages.
Prerequisites
- Python 3.10+
- Neo4j 5.x instance (local or remote)
- uv (for development; not needed to install)
Quick start
pip install kenning-continuity
kenning-continuity --db-url bolt://localhost:7687
Or from source:
uv sync
uv run kenning-continuity --db-url bolt://localhost:7687
Configuration
CLI flags take precedence over environment variables, which take precedence over defaults.
| CLI flag | Env var | Default | Description |
|---|---|---|---|
--db-url |
NEO4J_URI or NEO4J_URL |
bolt://localhost:7687 |
Neo4j connection URL |
--username |
NEO4J_USERNAME |
neo4j |
Neo4j username |
--password |
NEO4J_PASSWORD |
password |
Neo4j password |
--database |
NEO4J_DATABASE |
neo4j |
Neo4j database name |
--transport |
NEO4J_TRANSPORT |
stdio |
stdio, sse, or streamable-http |
--namespace |
NEO4J_NAMESPACE |
(none) | Tool name prefix (e.g. myapp → myapp-begin_session) |
--read-timeout |
NEO4J_READ_TIMEOUT |
30 |
Read query timeout, seconds |
--server-host |
NEO4J_MCP_SERVER_HOST |
127.0.0.1 |
HTTP host (non-stdio transports) |
--server-port |
NEO4J_MCP_SERVER_PORT |
8000 |
HTTP port (non-stdio transports) |
--server-path |
NEO4J_MCP_SERVER_PATH |
/mcp/ |
HTTP path (non-stdio transports) |
--allow-origins |
NEO4J_MCP_SERVER_ALLOW_ORIGINS |
(none) | Comma-separated CORS origins |
--allowed-hosts |
NEO4J_MCP_SERVER_ALLOWED_HOSTS |
(none) | Comma-separated hosts, DNS-rebinding protection |
MCP client configuration
Claude Desktop / Claude Code
{
"mcpServers": {
"temporal-knowledge": {
"command": "kenning-continuity",
"args": ["--db-url", "bolt://localhost:7687"]
}
}
}
HTTP transport
kenning-continuity \
--db-url bolt://localhost:7687 \
--transport streamable-http \
--server-host 0.0.0.0 \
--server-port 8000 \
--allow-origins "http://localhost:3000" \
--allowed-hosts "localhost,127.0.0.1"
Tool surface (23 tools)
A session runs: create_domain (once) → begin_session →
create / evolve / confirm / connect knowledge → end_session.
The server is stateless. begin_session returns a session_id that the client
passes to every subsequent knowledge tool. A session never closed stays
active — by design.
Process (4)
| Tool | Description |
|---|---|
list_domains |
All domains with session counts and last activity |
create_domain |
Create a knowledge domain (idempotent) |
begin_session |
Start a session; returns session_id. domain, purpose, director and director_key are all required — the director's Person node is created here and nowhere else |
end_session |
Close a session with a summary of what was learned |
begin_session returns a count by type, not the domain state. Returning
the state inline was 600,820 characters on one production domain — roughly
150k tokens spent before the session had asked a question. Call
get_domain_state with types and limit for the part you actually need.
Knowledge mutation (6)
| Tool | Description |
|---|---|
create_knowledge |
Create entities. Refuses on collision, refuses process types, and refuses unknown fields rather than dropping them |
evolve_knowledge |
The only way to change a description. Creates a new chain node preserving the prior one via EVOLVED_FROM; keeps the ontological type, replaces the sub-label set |
confirm_knowledge |
Record that entities were reviewed and found unchanged |
merge_knowledge |
Compact an EVOLVED_FROM chain into one canonical node. Destructive — cannot be undone |
create_connections |
Link entities. Takes a session_id and records it on every edge — an edge is an assertion and reaches its author |
claim_referent |
Claim two referents are the same thing, with a required resolution: proposed, confirmed, or distinct |
Query (5)
| Tool | Description |
|---|---|
search_knowledge |
Fulltext across names and descriptions, head-of-chain only |
get_domain_state |
Current entities for a domain; optional types, limit, descriptions |
get_session_history |
Who worked on what, when, and toward what purpose |
get_chain_history |
Walk EVOLVED_FROM backward — what was previously understood |
read_cypher |
Read-only Cypher escape hatch; writes are rejected |
Taxonomy (2)
| Tool | Description |
|---|---|
list_knowledge_types |
11 ontological types and 7 sub-labels, with the lattice |
list_connection_types |
13 knowledge and 5 process edge types |
Graph analytics (6)
| Tool | Description |
|---|---|
gds_create_projection |
Create a GDS graph projection |
gds_drop_projection |
Drop a projection |
gds_pagerank |
PageRank centrality |
gds_betweenness |
Betweenness centrality — bridge nodes |
gds_louvain |
Louvain community detection |
gds_wcc |
Weakly connected components |
Type system
Every knowledge node carries :Knowledge, exactly one ontological label, an
ont_type property naming that label deterministically, and zero or more
sub-labels via Neo4j multi-labeling.
| Ontological type | What it is |
|---|---|
Actor |
That which acts; has agency |
Structure |
Arrangement without agency; persistent shape |
Artifact |
Produced thing that persists and can be referenced |
Place |
A site, campus, region or extent — always a referent, never a referrer |
Event |
Temporally-located happening |
Insight |
A realization, discovery, breakthrough |
Pattern |
Recurring structure recognized across instances |
Challenge |
Present-tense obstacle |
Solution |
Resolution that worked; pairs with Challenge |
Lesson |
Rule derived from experience |
Rationale |
The reason behind something |
Sub-labels are a lattice, not a flat map
PersonReferent → Actor System → Structure
Organization → Actor Process → Structure
Team → Organization → Actor Configuration → Structure
Service → Structure
create_knowledge accepts either an ontological type (Actor) or a sub-label
(Team), and applies the whole chain: :Knowledge:Actor:Organization:Team.
A query for organizations therefore finds teams. A flat map would emit
:Actor:Team, silently dropping the middle level, and nothing would report it.
Connection types (13)
EVOLVED_FROM, ENABLING, REQUIRING, INFORMING, CAUSING, COMPOSING,
EXTENDING, RECOGNITION, VALIDATION, INVALIDATING, ASSOCIATED_WITH,
SITUATED_AT, REFERENT_CLAIM.
Process edges — HAS_SESSION, NEXT_SESSION, DIRECTED, DISCOVERED,
CONFIRMED — are written only by process tools and cannot be created through
create_connections.
What a description should say
A description says what the entity is, not what happened to or around it.
Temporal observations belong on Event nodes reached by edges.
- Correct, for an
Actor:PersonReferent: "Founder of Example Corp. Largest individual shareholder (8.4%). Filed under Schedule 13D — not passive." - Wrong: "SCHEDULE 13D/A Amendment No. 10 filed Oct 8, 2025. FOUNDER GOES PUBLIC…"
The filing is an Event. Alice Example is the founder. The filing
revealing something about them is an INFORMING edge. Their description should
survive any number of future filings unchanged, unless their identity itself
changes.
Development
uv sync --group dev
./.venv/bin/pytest tests/unit # mocked substrate, no database
./.venv/bin/pytest tests/integration # real Neo4j via testcontainers; needs Docker
./.venv/bin/pyright
A defect that lives in a Cypher query is invisible to the unit tests — they
mock the substrate, and an edge-fabrication bug once passed every one of them
identically before and after the fix. Anything touching a query belongs in
tests/integration/.
License and attribution
Licensed under the Apache License, Version 2.0. Use it, modify it, run it in production, commercially or otherwise.
The work here is the ontology — the type lattice, the resolution-by-walk, the
placement of epistemic standing in edges, the principle that every invariant
lives in a tool. That is given away freely, and Apache-2.0 asks only that
attribution travel with it: keep LICENSE and NOTICE, and mark what you
changed.
The Kenning AI name is not part of that grant (Apache-2.0 §6). See
TRADEMARKS.md for what you may do without asking — which is
nearly everything, including saying what your software is built on.
Contact
contact@kenningai.com — no form, no funnel, it reaches us directly.
We are especially interested in hearing from anyone who has run an accumulating memory system at scale and formed a view on why they fail. That failure mode is the reason for nearly every decision in this repository, and disagreement from someone who has hit it themselves is worth more to us than agreement from anyone who hasn't.
Kenning AI · kenningai.com
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kenning_continuity-0.1.2.tar.gz.
File metadata
- Download URL: kenning_continuity-0.1.2.tar.gz
- Upload date:
- Size: 96.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e02f3074e719cf3836ca15b4c3b6cf426a7e6bd07c6c4059035f15a25c4b57e
|
|
| MD5 |
ca6076f3c330ad3f055022153b018d46
|
|
| BLAKE2b-256 |
66a3fa8044d19ddd5f5dc2e529514e6e0be56f00731db1f6ec932c07fb602936
|
File details
Details for the file kenning_continuity-0.1.2-py3-none-any.whl.
File metadata
- Download URL: kenning_continuity-0.1.2-py3-none-any.whl
- Upload date:
- Size: 72.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5768c0d9fcab4129d37281846641113349d159422137195bcfc5cba8ff09aaa4
|
|
| MD5 |
daf1dc7be8b6743bee1d8d6a972c1bb4
|
|
| BLAKE2b-256 |
057d727b581fcb7487309bb18f269e47412ea571ceaca9c5c8439e7c055b91a4
|