The knowledge graph
How Ouroboros turns mined and remembered claims into typed, entity-scoped, provenance-carrying rows an agent can filter and join instead of re-reading source text.
Verified 2026-07-08 @ 2bfbe98e
What this is
The knowledge graph is the set of typed rows Ouroboros holds in
subscriber_sophia_knowledge, each one attached to an entity and carrying
where it came from. Some rows are simple typed notes; a subset — the ones
tagged knowledge_type: 'claim' — carry a structured subject/predicate/object
triple with a verbatim evidence string, which is what makes cross-fact
filtering and joins possible.
Why it exists
A vector-only RAG system gives you back the paragraph that’s semantically
closest to your question. It can’t tell you “every fact where the object
disagrees with another fact on the same predicate,” because a paragraph has
no predicate — it’s prose, not a row. A knowledge graph can, because the
predicate is a value in a column (well: a JSON field), and WHERE and JOIN
are operations a database actually supports.
That difference shows up in two places an agent cares about. First,
sophia.find_contradictions finds every (subject, predicate) pair with
disagreeing objects — a query only possible because predicates are typed and
comparable, not because a similarity search happened to retrieve two
conflicting chunks in the same context window. Second, sophia.query_knowledge
always sorts a user’s correction to the top of the result set ahead of
whatever the model extracted, because corrected_by_user is a real column
the query can order by. A pile of embedded chunks has no such column to sort
on — there’s nothing to prefer, only nearness.
How it works
Two tables do the work. subscriber_entities holds the nodes — id, name,
type, short_name, and a trust_tier of curated (you created or confirmed
it) or mentioned (mining discovered it, unreviewed). subscriber_sophia_knowledge
holds the facts, one row per observation, each with entity_id,
knowledge_type, free-text content, a confidence signal, and provenance
(source_type + source_id) back to the document or connection that wrote it.
Not every row is a triple. Most of the volume on a working install is
entity_mention and summary rows — plain typed notes with no internal
structure. Only claim-typed rows carry a claim_json payload shaped
{ subject, predicate, object, evidence, confidence }; evidence is a
verbatim substring of the source text, capped at 300 characters
(MAX_EVIDENCE_LEN in the extraction pipeline) and checked with a literal
.includes() against the source before the claim is allowed to land — no
evidence string, no claim. Predicates are freeform strings the model writes,
not a fixed enum; sophia.predicates_in_use samples what’s actually live.
Provenance beyond source_id runs through the mutation journal: every write
carries a mutation_id pointing at subscriber_sophia_mutations, which
records actor_kind (human / agent / system) and connection_id —
resolved server-side from the authenticated bearer token, never taken from a
tool-call parameter an agent could fake.
Supersession is two separate mechanisms, not a trust ladder. Same-predicate
facts with a newer valid_from auto-supersede older ones. Separately, once
a fact carries corrected_by_user='1', later extraction passes that would
touch the same content are skipped outright rather than allowed to overwrite
it — the write path checks for an existing correction before it dedupes or
inserts.
flowchart TB
subgraph row["subscriber_sophia_knowledge row"]
direction LR
CJ["claim_json:<br/>subject / predicate / object<br/>evidence (≤300 chars)"]
CJ --- CF[confidence]
CF --- ST["source_type + source_id"]
end
D["Document mining pass<br/>(submit_claim_graph)"] -->|writes| row
row -->|entity_id| E[(subscriber_entities)]
row -->|mutation_id| J[(subscriber_sophia_mutations<br/>actor_kind + connection_id)]
U["You correct a claim"] -->|"corrected_by_user='1'<br/>supersedes old row"| row
style U fill:#1a4d2e,stroke:#22c55e,color:#fff
style D fill:#3a3a1a,stroke:#fbbf24,color:#fff What your agent does with it
The three reads that cover most of what an agent needs: sophia.query_knowledge
for a filtered or semantically-ranked list of facts, sophia.get_entity_profile
for everything known about one entity in one call, and sophia.search with
intent: 'facts', which re-routes to query_knowledge’s own implementation
so it’s the same result shape reached from the general search front door.
// Real responses from this daemon, captured 2026-07-08:
const facts = await sophia.query_knowledge({ entity_name: 'Ouroboros Repo', limit: 20 });
// → { knowledge_facts: [
// { knowledge_type: 'claim',
// content: 'Ouroboros Repo has_tooling_recommendation Use hierarchical code map...',
// claim_json: '{"subject":"Ouroboros Repo","predicate":"has_tooling_recommendation",...}',
// confidence: 'high', source_type: 'document_extraction', source_tier: 'full',
// corrected_by_user: null, superseded_by: null }, ... ],
// total: 2, _search_method: 'filter_rank' }
const profile = await sophia.get_entity_profile({ entity_name: 'Ouroboros Repo', fact_limit: 5 });
// → { entity: { name: 'Ouroboros Repo', short_name: 'OR', type: 'repo', trust_tier: 'curated' },
// knowledge_summary: { total: 1893,
// by_type: { entity_mention: 1591, claim: 256, summary: 36, action_item: 5, amount_mention: 5 } },
// knowledge_facts: [ /* fact_limit rows */ ], documents: [ /* 26 linked source files */ ] }
const hits = await sophia.search({ query: 'knowledge graph', intent: 'facts', limit: 5 });
// → { intent: 'facts', routed_to: 'sophia.query_knowledge', count: 3,
// knowledge_facts: [ { content: 'knowledge graph infrastructure has_compounding_value_vs passive data vaults', ... } ] } sophia.find_contradictions is the one no vector store can offer: it groups
active claims by (subject, predicate) and flags the ones with disagreeing
objects. On this same repo entity today it caught a real duplicate — two
near-identical has_tooling_recommendation claims mined from the same
document, both still active, medium severity because neither side is a user
correction yet.
Those counts (256 claims, 9 distinct live predicates, 3 open contradictions on this one entity) are a snapshot of one dogfood dataset on 2026-07-08, not fixed numbers — they’ll read differently on a fresh install or after the next mining pass.
Boundaries
Confidence on a fact is not a human/extracted/inferred trust ladder — that
column doesn’t exist on this table. What exists is a per-row confidence
signal from the extraction model (or a value force-capped at 0.50 for
agent-authored free-text notes, a deliberate defense against an agent
inflating its own memory), plus source_tier (full vs a cheaper skim
pass) and corrected_by_user, which is the one flag that actually changes
read order and write eligibility. trust_tier is a real column, but it
lives on entities (curated vs mentioned), not on facts.
LLM extraction can misparse a document, invent a predicate that doesn’t generalize, or contradict itself across two passes — the 300-character evidence check catches fabricated content, not misread content. Re-grounding a specific claim against its source, or walking why the graph believes something, is Truth, not this page. What every past version of a row looked like, and how a correction is reverted, is Time Machine. The full 166-tool surface this page’s calls come from is cataloged at MCP Surface; how the daemon stores documents and vault bytes underneath these rows is The State Layer.