Architecture

Cortex is a knowledge-graph memory that agents propose to and humans approve. This page explains the storage model, the ingestion pipeline, and how the pieces are packaged.

Storage model

Approved facts — assertions — are the source of truth. They live in an Apache Jena TDB2 store: in memory by default, or on disk when cortex.persistent=true. Everything else is a derived cache:

Both are rebuilt from the assertions on every startup, regardless of persistence — so there is no separate index to manage, and a restored or reloaded store is immediately consistent.

Why the split matters. Because inference and search are always derived, the only durable state is the assertions dataset. That is what backup snapshots and what restore reloads — the closure and index simply rebuild over it.

Ontology grounding

You bring three classpath resources, each a list merged in order:

ResourcePropertyRole
ontology.ttlcortex.ontologiesThe OWL vocabulary — the classes and properties assertions may use.
ontology.shapescortex.shapesSHACL shapes ingested data must structurally conform to.
ontology.rulescortex.rulesJena rules that derive inferred statements.

Linting is a closed-world vocabulary check: every class and property used must be defined in the ontology, and only rdf:type, rdfs:label, and rdfs:comment are allowed beyond it. SHACL validation is the structural check, run over the union of incoming and already-approved data so a new statement may rely on approved ones to conform.

The ingestion pipeline

Nothing an agent or an /import submits reaches the approved graph directly. Every write is proposed, staged, and gated on human approval:

Cortex ingestion pipeline: Turtle in, lint, SHACL validate and novelty diff, stage on a branch, human review, then approve (merge with provenance, extend inference and search) or reject.
Ingest → review → approve. Steps 3–4 and step 6 are each a single TDB2 write transaction.
Single-writer safety. TDB2 serialises writers, which is exactly what makes ingest and approve safe as single transactions — concurrent approvals cannot double-merge.

Provenance

Cortex records lineage in PROV-O. Staging a branch creates a prov:Activity with a start time; approving it sets the end time and, for every merged statement, writes a reification linked with prov:wasGeneratedBy into the reserved cortex://provenance graph. That graph is excluded from inference and queried separately — it is how the Describe view attaches a "known since" timestamp to each fact.

Reserved namespace. cortex:// is Cortex's own — the provenance graph and branch graphs live there. Your ontology uses its own namespace (the example uses example://ontology#). The provenance graph is not a branch and is never rendered as one.

A Jena GenericRuleReasoner materialises derived statements from your ontology.rules, bound to the ontology as schema. The full closure is recomputed once at startup; each approval then extends it incrementally with only that approval's newly added statements — and the Lucene index is patched with just those, so indexing stays cheap. Query, Ask, Describe, and Search all read the inference dataset, so results always include entailed statements.

How search ranks

Query text is tokenised by the same analyser that indexed the literals. That equality is the whole game: Lucene resolves fuzzy terms without tokenising, so a query split by hand on whitespace is looked up whole against an index the tokeniser already split differently, and note-pad silently matches nothing while note pad works. Labels and comments are separate fields, searched as separate branches so a name outranks a description, and every term is required, so adding a word narrows rather than widens.

Textual relevance is then weighted by how often each resource is deliberately opened — opening it, never merely appearing in a result list, which would boost whatever already ranked highly and is self-fulfilling. Counts live in cortex://usage, a reserved named graph beside provenance, so nothing here reaches the approved assertions. Views buffer in memory and flush in batches: TDB2 admits a single writer, so a write per view would serialise reads against ingestion.

The weight saturates and is bounded at 2×, and each view's contribution halves every cortex.search.viewHalfLife. Both limits exist for the same reason: popularity that compounds without bound becomes a rich-get-richer loop, since boosted results are seen more and therefore opened more, and nothing new can break in. Bounding the weight keeps popularity a tie-breaker between comparable results rather than a substitute for relevance; decaying it means the ranking follows what people consult now rather than whatever was popular once.

It is a ranking prior, not associative learning. Each resource carries one decayed score of its own. Nothing strengthens a connection between two resources, so despite the surface resemblance this is not Hebbian — there is no co-activation and no edge whose weight changes. Storing a co-view score per pair would be.

Modules & delivery mechanisms

The auto-configuration is grouped by how the graph is delivered — to the process, to humans, to agents, to durable storage, and seeded back from it — not by domain. That grouping is why each mechanism is a single on/off switch.

The Cortex graph at the centre, delivered to humans via the web UI, to agents via MCP, to durable storage via backup, and seeded back via restore; backup and restore share one S3 client. Module stack: api, core, autoconfigure, starter.
One graph, five delivery mechanisms, each a single switch. Backup and restore independently share the S3 client.

The S3 client is deliberately separate from backup and restore: both require it, independently of each other, so cortex.s3.enabled is a real switch rather than a flag that must always track a consumer. See Configuration for the details of backup and restore.

The web UI

The starter serves a small server-rendered UI for exploring the graph and reviewing what agents have staged.

Cortex home page showing knowledge graph statistics.
The home page — live statistics over the graph.
Cortex branches page listing pending branches awaiting review.
Pending branches awaiting review, each with its provenance activity as badges.
Cortex branch review page showing staged assertions grouped by subject with approve and reject actions.
Reviewing a branch — edit, approve, or reject staged assertions before they enter the graph.

See the full route table in the getting-started guide.