<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vivan</title>
    <description>The latest articles on DEV Community by Vivan (@vivantejani).</description>
    <link>https://dev.to/vivantejani</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4192300%2F89c4acb4-7198-44f8-8926-645accb40cd2.png</url>
      <title>DEV Community: Vivan</title>
      <link>https://dev.to/vivantejani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vivantejani"/>
    <language>en</language>
    <item>
      <title>Connected Is Not Proven: Building an Evidence Graph That Admits What It Doesn’t Know</title>
      <dc:creator>Vivan</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:22:59 +0000</pubDate>
      <link>https://dev.to/vivantejani/connected-is-not-proven-building-an-evidence-graph-that-admits-what-it-doesnt-know-4de5</link>
      <guid>https://dev.to/vivantejani/connected-is-not-proven-building-an-evidence-graph-that-admits-what-it-doesnt-know-4de5</guid>
      <description>&lt;p&gt;&lt;em&gt;Graphynx is an evidence graph where every claim is anchored to a source quote and every answer lands in one of seven explicit verdict states.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A path in the graph is not a fact
&lt;/h2&gt;

&lt;p&gt;Picture an on-call engineer at 2 a.m. asking an AI agent whether Checkout depends on the Redis cache. The agent finds a path (Checkout → Gateway → Order Service → Cache), answers yes, and the engineer starts a failover. But one of those links came from a war-room transcript where someone was only guessing, and another from a doc saying the dependency was removed last quarter. The agent saw nodes that were connected and reported a proven relationship.&lt;/p&gt;

&lt;p&gt;That failure comes in three forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination.&lt;/strong&gt; Language models produce believable relationships that no document actually supports, and nothing in the output tells you which ones are invented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge-as-fact bias.&lt;/strong&gt; Many graph pipelines treat every edge as ground truth, whether it is a documented fact, an assumption, or just two things mentioned together. Pairwise edges also flatten multi-party events, so two people who attended the same incident review look directly related.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale memory.&lt;/strong&gt; Agents cache their conclusions and reuse them after the facts underneath have changed, so yesterday's answer gets served with today's confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three come down to the same flaw: the system cannot say what it can prove and what it is merely inferring. Graphynx is built around that gap. A connection in the graph is only a connection until a source quote, an explicit rule, or a fresh check says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the usual approaches fall short
&lt;/h2&gt;

&lt;p&gt;The three failures above set the requirements. Every claim has to trace to a source quote. "Stated," "derived," and "merely connected" have to be different answers. Multi-party events have to stay intact. Memory has to expire when its evidence changes.&lt;/p&gt;

&lt;p&gt;Here is how common patterns measure up. This compares patterns, not specific products.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Common approach&lt;/th&gt;
&lt;th&gt;What it does well&lt;/th&gt;
&lt;th&gt;Where it breaks&lt;/th&gt;
&lt;th&gt;Graphynx's answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM summaries or plain RAG answers&lt;/td&gt;
&lt;td&gt;Fast, readable, broad coverage&lt;/td&gt;
&lt;td&gt;The answer reads equally fluent whether or not a source supports it, and claim-level provenance is usually missing&lt;/td&gt;
&lt;td&gt;Every claim carries a verbatim quote with exact offsets, and claims without evidence default to &lt;code&gt;UNKNOWN&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM-extracted triples in a property graph&lt;/td&gt;
&lt;td&gt;Structured and queryable&lt;/td&gt;
&lt;td&gt;Binary &lt;code&gt;A → B&lt;/code&gt; edges collapse multi-party events and invite false transitive links, and every edge looks equally "true"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RelationEvent&lt;/code&gt; hubs with role-labelled participants, plus a 7-state verdict on every answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merging duplicate entities in place&lt;/td&gt;
&lt;td&gt;A clean, deduplicated graph&lt;/td&gt;
&lt;td&gt;Overwrites IDs and discards history, so a wrong merge can't be cleanly undone&lt;/td&gt;
&lt;td&gt;A reversible identity ledger where merges and splits are overlays on untouched claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent memory as cached outputs or a vector store&lt;/td&gt;
&lt;td&gt;Cheap reuse of earlier work&lt;/td&gt;
&lt;td&gt;Nothing records which facts an answer depended on, so staleness goes unnoticed&lt;/td&gt;
&lt;td&gt;Receipts with SHA-256 fingerprints of what was read, rechecked before reuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A raw query tool for the agent (e.g., arbitrary Cypher)&lt;/td&gt;
&lt;td&gt;Maximum flexibility&lt;/td&gt;
&lt;td&gt;Open to injection and unintended destructive writes&lt;/td&gt;
&lt;td&gt;Typed API endpoints and bounded MCP tools, with writes behind a dual gate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these tools is wrong. They are the wrong defaults when a wrong answer is expensive. Graphynx still lets a model help with extraction, but only as an opt-in hint, and a hint is accepted only if its quote matches the source text character for character.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Graphynx is
&lt;/h2&gt;

&lt;p&gt;Graphynx turns documents and data (CSV, JSON, JSONL, TXT, PDF) into an event-time graph where every claim is tied to the exact source text that supports it, then answers questions about that graph with an explicit verdict instead of a confident guess. It also keeps the audit trail an investigator or an agent needs: where each claim came from, what was inferred, and what has gone stale.&lt;/p&gt;

&lt;p&gt;The core principle is that &lt;strong&gt;Graphynx is an inspectable evidence graph, not a general truth engine.&lt;/strong&gt; If an uploaded document is wrong, Graphynx doesn't know that. It records that the document asserted the claim, and it shows you exactly where. Four design rules follow from that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evidence is mandatory.&lt;/strong&gt; Every claim, relationship, and participant role links to a verbatim quote with exact start and end offsets in the source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connected is not proven.&lt;/strong&gt; A path in the graph is a structural fact only, and it is never silently promoted to a source-backed one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncertainty is a first-class state.&lt;/strong&gt; Negations, conflicts, and hypotheses are stored as what they are, not flattened into generic edges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents act behind a fence.&lt;/strong&gt; Anything that writes state needs explicit authorization at both the request level and the environment level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core ideas
&lt;/h2&gt;

&lt;p&gt;Six ideas carry the design. Each one answers a failure from the problem section.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Evidence anchoring: no quote, no claim
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; a language model can state a relationship with total confidence and no source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; every claim Graphynx stores is paired with an &lt;code&gt;Evidence&lt;/code&gt; record. The record holds the verbatim quote, its character offsets within the source chunk, and a pointer to the chunk it came from. Two links tie them together: the evidence &lt;code&gt;SUPPORTS&lt;/code&gt; the claim, and &lt;code&gt;CITES&lt;/code&gt; the chunk.&lt;/p&gt;

&lt;p&gt;Here is a trimmed, illustrative example. The source chunk reads: &lt;em&gt;"The on-call notes say Checkout depends on the Redis cache."&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Evidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checkout depends on the Redis cache."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src_…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chunk_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chunk_…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"predicate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEPENDS_ON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ev_…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source_assertion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"independently_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slicing the chunk at &lt;code&gt;[22:58]&lt;/code&gt; returns the quote exactly, so anyone can check a claim by reading the original text.&lt;/p&gt;

&lt;p&gt;Three details keep this clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model hints are verified, not trusted.&lt;/strong&gt; When the optional Bedrock extraction is on, a proposed quote has to be an exact substring of the chunk. The only repair is recovering a unique exact slice when the text differs by whitespace alone. It never fixes typos, paraphrases, or changed punctuation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A claim says what it is.&lt;/strong&gt; Each claim is tagged &lt;code&gt;source_assertion&lt;/code&gt; with &lt;code&gt;independently_verified: false&lt;/code&gt;. Graphynx records that a document asserted something, and it doesn't claim the assertion is true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No evidence means no answer.&lt;/strong&gt; If nothing supported answers a question, the default verdict is &lt;code&gt;UNKNOWN&lt;/code&gt; with the explanation "Insufficient supported evidence."&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The seven-state verdict engine
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; in most graphs every edge looks equally true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; every relationship question returns one of seven states, never a bare yes/no and never a numeric probability.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SOURCE_ASSERTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A source states it directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SOURCE_NEGATED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A source explicitly denies it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CONTRADICTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sources disagree over overlapping time periods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RULE_DERIVED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;It follows from a declared rule over source-backed claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CONNECTED_ONLY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A supported path exists, but the asked relationship isn't established&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HYPOTHESIS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A stored, labelled lead; causation unproven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing supported (the default)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The engine checks direct claims first, then declared rules, then bare paths. A hypothesis appears only if the caller asks for one, and &lt;code&gt;UNKNOWN&lt;/code&gt; is the fallback. Three details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A negation alone is not a conflict. &lt;code&gt;CONTRADICTED&lt;/code&gt; needs opposing claims with overlapping validity, so a dependency that ended last quarter doesn't "contradict" an older one.&lt;/li&gt;
&lt;li&gt;There are only two declared rules: &lt;code&gt;dependency_reachability_v1&lt;/code&gt; and &lt;code&gt;shared_employer_temporal_overlap_v1&lt;/code&gt;. Everything else stops at &lt;code&gt;CONNECTED_ONLY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Path search is bounded by a hop limit and an expansion budget, and it skips claims whose time periods don't overlap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A trimmed response when only a path exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CONNECTED_ONLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A supported graph connection exists; the requested predicate is not established"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No numeric truth probability is claimed"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the 2 a.m. scenario done right: the agent can say "these are connected" without saying "this depends on that."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. N-ary events: one hub, many roles
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; binary &lt;code&gt;A → B&lt;/code&gt; edges flatten multi-party events and invite false links between people who merely attended the same incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; a &lt;code&gt;RelationEvent&lt;/code&gt; is a node of its own. Each participant attaches through a &lt;code&gt;HAS_PARTICIPANT&lt;/code&gt; edge with an explicit role (lowercase names like &lt;code&gt;responder&lt;/code&gt;, &lt;code&gt;affected_system&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;), and the event carries its own evidence record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; (responder) on-call engineer ─┐
 (affected_system) Checkout ───┼── RelationEvent: incident
 (context) Redis cache ────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Path search only walks claim edges, so sharing an event never creates a path between participants. If the source never said the engineer and the cache are related, Graphynx won't say so either.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A reversible identity ledger
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; merging duplicate entities in place overwrites IDs and throws away history, so a wrong merge can't be cleanly undone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; Graphynx never merges by editing records. Each source mention is stored immutably and grouped under a versioned entity card. Every change is a new appended revision (&lt;code&gt;provisional_merge&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt;, or &lt;code&gt;revert&lt;/code&gt;). Claims, sources, and events are never rewritten.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rev 1  initial_assignment   mention A and mention B sit on separate cards
rev 2  provisional_merge    both mentions move to one card
rev 3  revert (of rev 2)    back to separate cards; history keeps all three revisions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guardrails are enforced in code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A shared name alone never merges anything. It creates a review candidate with the reason "Shared name alone is insufficient identity evidence."&lt;/li&gt;
&lt;li&gt;Cross-type merges are forbidden, and a merge needs at least two mentions.&lt;/li&gt;
&lt;li&gt;You can't revert over a later identity decision, and initial source assignments can't be reverted at all (use a split).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Downstream work is protected too. A derived artifact records which revision of each mention it was built on. If any of those assignments change, including through a revert, the artifact is reported as &lt;code&gt;stale&lt;/code&gt; instead of being silently reused.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Write fencing and a deliberately small MCP surface
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; give an agent raw query access and one bad prompt, or one injected instruction, can run arbitrary or destructive queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; Graphynx exposes no Cypher at all. Agents get four typed tools over the Model Context Protocol (stdio, JSON-RPC 2.0, no network listener, meant for locally launched trusted clients):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_entities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resolves a name or alias; ambiguous identities stay separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;evaluate_relationship&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read-only; returns the verdict with evidence and paths, with no model calls and no hypotheses saved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_claim_evidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns one stored claim and its citation record by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;investigate_dependency_incident&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Previews a bounded multi-hop investigation by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each tool rejects any argument outside its schema. Writes sit behind two gates. The investigation tool stays in preview unless the request sets &lt;code&gt;authorise_action: true&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; the server was started with &lt;code&gt;UGI_MCP_ALLOW_WRITES=1&lt;/code&gt;. With only one of the two, the call fails with an explicit error. When both are set, the run persists the investigation and opens review tasks in a human queue.&lt;/p&gt;

&lt;p&gt;The fence covers Graphynx's own state. Nothing in it triggers side effects on external systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Memory receipts: answers that expire
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; an agent caches a conclusion, the facts underneath change, and the stale answer gets reused with full confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism:&lt;/strong&gt; when a guided agent response is produced, Graphynx seals a &lt;strong&gt;receipt&lt;/strong&gt; in a small SQLite sidecar. The receipt stores the output, plus a SHA-256 fingerprint of every fact the agent read to produce it. Before reusing it, the agent calls &lt;code&gt;GET /api/agent/receipts/{id}/freshness&lt;/code&gt;, which re-reads those facts as they stand now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fingerprint changed:&lt;/strong&gt; the receipt becomes &lt;code&gt;STALE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The facts now conflict:&lt;/strong&gt; the receipt becomes &lt;code&gt;UNKNOWN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A parent receipt is no longer valid:&lt;/strong&gt; the receipt becomes &lt;code&gt;STALE&lt;/code&gt;, and the change cascades to everything built on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing changed:&lt;/strong&gt; the receipt is revalidated.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VALID ──(a fact it read changes)──► STALE ──(a conflict appears)──► UNKNOWN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Status only moves in this direction. A receipt can get less trusted, never more, and every downgrade is written to an invalidation log. Getting a &lt;code&gt;VALID&lt;/code&gt; receipt again means a new, fully captured read. If freshness can't be verified at all, the API answers &lt;code&gt;503&lt;/code&gt; with "do not reuse."&lt;/p&gt;

&lt;p&gt;The check also runs inside the same SQLite transaction as the protected internal action, so nothing can change between the check and the act.&lt;/p&gt;

&lt;p&gt;These are content fingerprints, not signatures or HMACs. They catch changed facts, not a tampered database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture at a glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;documents (CSV · JSON · JSONL · TXT · PDF)
        │  POST /api/uploads
        ▼
  parse + bounded extraction ◄── optional, opt-in model hints
        ▼
  evidence grounding
  Source · Chunk · Entity · Claim · Evidence · RelationEvent · identity ledger
        ▼
  graph store:  FalkorDB (persistent)  |  in-memory (tests)
        ▼
  FastAPI endpoints          MCP server (stdio, 4 typed tools)
        └──────────────┬──────────────┘
                       ▼
  verdict engine · fenced investigations · memory receipts (SQLite)
                       ▼
  browser workbench (2D evidence map, 3D orbit view, review queue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything runs through five phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingest and bounded extraction.&lt;/strong&gt; Documents arrive through &lt;code&gt;POST /api/uploads&lt;/code&gt; and are split into bounded chunks. A deterministic clause grammar finds entities, predicates, and roles. If model extraction is enabled (AWS Bedrock, off by default), its output is only a hint, and a hint is kept only if its quote matches the source text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence grounding and storage.&lt;/strong&gt; Each claim gets its &lt;code&gt;Evidence&lt;/code&gt; record with the quote and offsets. Binary facts become &lt;code&gt;Claim&lt;/code&gt; nodes and multi-party events become &lt;code&gt;RelationEvent&lt;/code&gt; nodes. Records are committed in one transaction to FalkorDB or the in-memory store, chosen with &lt;code&gt;UGI_BACKEND&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Epistemic reasoning.&lt;/strong&gt; A question to &lt;code&gt;POST /api/relationships&lt;/code&gt; walks direct claims, then declared rules, then bare paths, and returns one of the seven states with its evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlled action.&lt;/strong&gt; Investigations preview by default. Persisting one needs both write gates, and it opens review tasks that a human decides through &lt;code&gt;POST /api/tasks/{id}/decision&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified memory.&lt;/strong&gt; Guided agent responses (&lt;code&gt;POST /api/agent/guided&lt;/code&gt;) seal receipts. Before an agent reuses one, it calls the freshness endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Walkthrough: from upload to verdict
&lt;/h2&gt;

&lt;p&gt;This uses the repo's synthetic incident dataset (&lt;code&gt;demo/incident.json&lt;/code&gt;: eight claims about Checkout, Gateway, and Cache) on the in-memory backend, so no database or model key is needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;UGI_BACKEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;memory uvicorn ugi.api:app &lt;span class="nt"&gt;--port&lt;/span&gt; 8001

curl &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@demo/incident.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-dataset: incident-demo"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     http://localhost:8001/api/uploads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upload response, trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"chunks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"entities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"claims"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"relation_events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"offline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ask whether Checkout transitively depends on Cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8001/api/relationships &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-dataset: incident-demo"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"subject":"Checkout","object":"Cache","predicate":"TRANSITIVELY_DEPENDS_ON"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RULE_DERIVED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dependency_reachability_v1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A forward dependency chain entails dependency reachability; it does not prove a direct dependency or incident cause"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"claim_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"a069db93…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a7d60678…"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"directions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two source-backed claims (Checkout → Gateway → Cache) are chained by a declared rule, and the response says exactly what that does and doesn't prove. Now ask the same pair for the stronger claim by changing the predicate to &lt;code&gt;DEPENDS_ON&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CONNECTED_ONLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A supported graph connection exists; the requested predicate is not established"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's the same path with a different answer, because nothing in the data states a direct dependency. Three more questions against the same dataset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gateway &lt;code&gt;DEPENDS_ON&lt;/code&gt; Cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SOURCE_ASSERTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One source row states it, and that row comes back as the evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache &lt;code&gt;USES&lt;/code&gt; Redis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTRADICTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The data holds both a positive and a negated row for the same relation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache &lt;code&gt;CAUSED&lt;/code&gt; Incident 7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The only row marks the cause as "possible," which doesn't count as an assertion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because this source is structured, the evidence for each claim is the source row itself. For prose, it is the exact quoted sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation choices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A fixed grammar first, a model second
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ugi/clauses.py&lt;/code&gt; finds entities, predicates, and roles with a bounded clause grammar. Model extraction is off by default. Bedrock only runs when the provider is set to &lt;code&gt;bedrock&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; paid calls are explicitly allowed, and a model hint is kept only if its quote is found in the source chunk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; a sentence form the grammar doesn't cover isn't extracted unless a model hint is enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. One store, two backends
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ugi/store.py&lt;/code&gt; switches on &lt;code&gt;UGI_BACKEND&lt;/code&gt;: &lt;code&gt;falkordb&lt;/code&gt; (the default) or &lt;code&gt;memory&lt;/code&gt;. The in-memory backend needs no database or key, which is how the Part 6 walkthrough runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; two backends have to behave the same, and a run on the in-memory store says nothing about FalkorDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Typed access only
&lt;/h3&gt;

&lt;p&gt;Graph access goes through FastAPI routes and an MCP server with four tools: &lt;code&gt;search_entities&lt;/code&gt;, &lt;code&gt;evaluate_relationship&lt;/code&gt;, &lt;code&gt;get_claim_evidence&lt;/code&gt;, &lt;code&gt;investigate_dependency_incident&lt;/code&gt;. Each tool rejects any argument it doesn't list. &lt;code&gt;evaluate_relationship&lt;/code&gt; is read-only: it sets &lt;code&gt;allow_hypothesis=False&lt;/code&gt; and &lt;code&gt;use_ai=False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; you can only ask what the tools were built for.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Writes are opt-in, and MCP has a second lock
&lt;/h3&gt;

&lt;p&gt;Investigations preview by default. Persisting one needs &lt;code&gt;authorise_action=true&lt;/code&gt; on the request. On the MCP server there is a second requirement: the server process must be started with &lt;code&gt;UGI_MCP_ALLOW_WRITES=1&lt;/code&gt;, which the caller can't set. Without it the call is rejected. The built-in agent bridge hard-codes writes off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; the HTTP API has only the request flag. The second lock exists on MCP, not on HTTP.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Receipts are a SQLite sidecar
&lt;/h3&gt;

&lt;p&gt;Memory receipts are stored in SQLite (&lt;code&gt;ugi/memory/core.py&lt;/code&gt;), separate from the graph. Every write runs in a &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; transaction, and the freshness gate for the protected internal action is held inside the same transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. A framework-free front end
&lt;/h3&gt;

&lt;p&gt;The dashboard is plain HTML, CSS, and vanilla JavaScript (&lt;code&gt;ugi/assets/&lt;/code&gt;), with a hand-written WebGL view alongside the SVG map.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; you maintain the renderer yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Hard input limits
&lt;/h3&gt;

&lt;p&gt;Uploads are capped at 5 MB and 2,000 records by default, and text is split into 4,000-character chunks. Identity-ledger uploads are capped at 200 mentions.&lt;/p&gt;

&lt;p&gt;The 2,000-record cap is a quota, not a throughput guarantee. Graph queries run under a 5,000 ms budget, and large uploads can exceed it. 1,000 and 2,000 rows timed out at baseline, and after the write-path fix (see Challenges) fresh 1,000- and 2,000-row uploads completed in one local run each. Large uploads that touch existing records still use the older, slower writer and keep the old capacity limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and dead ends
&lt;/h2&gt;

&lt;p&gt;Every number below comes from the repo's own work log and milestone reports, and every sample is small.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The grammar alone found almost nothing on outside data
&lt;/h3&gt;

&lt;p&gt;I ran the offline grammar on a fixed sample of 128 FewRel validation cases, with zero model calls. It emitted 1 claim and matched 0 of 128 target head/tail pairs. The check only screens directed pairs, so it measures coverage of an open-relation benchmark, not wrong answers. It still showed the grammar does not generalise beyond the text it was tuned on.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A small local model didn't fix it, and "fixing" it made things stricter
&lt;/h3&gt;

&lt;p&gt;I pointed a local Qwen3 4B model (via Ollama) at the first four FewRel examples. The result was 4 model calls, 0 claims, and 0 of 4 target pairs. The log's diagnosis was that the full candidate schema exposed too many fields to a small model, so Ollama now gets a compact schema while Bedrock's is unchanged.&lt;/p&gt;

&lt;p&gt;The next change replaced one review call per candidate with one batched call of up to 12 candidates per chunk. On the same four examples, model calls fell from 8 to 5, accepted claims fell from 4 to 1, and review tasks rose from 2 to 5. Target-pair hits stayed at 0 of 4. Batching made the system more conservative, not more accurate. Four examples is too few to say more.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The model reviewer rejected a correct claim
&lt;/h3&gt;

&lt;p&gt;The reviewer rejected a claim built on "just south of" even though the source said it. The fix was to normalise directional predicates to base NORTH, SOUTH, EAST and WEST relations. The source quote is kept, and the claim still has to pass the standard semantic review.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A faster writer that failed real tests
&lt;/h3&gt;

&lt;p&gt;Large uploads timed out at the 5,000 ms graph-query budget. At baseline, 1,000 and 2,000 rows timed out in both CSV and JSON. The bottleneck was not a missing index. Repeated endpoint lookups emitted about 60,200 pending records per call at 100 rows, which made the write quadratic.&lt;/p&gt;

&lt;p&gt;Three attempts to speed up the write path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct &lt;code&gt;UNWIND MERGE&lt;/code&gt;:&lt;/strong&gt; failed the real label and payload checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CREATE&lt;/code&gt; that still merged endpoints:&lt;/strong&gt; correct, but large cases still timed out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined new and existing node pool:&lt;/strong&gt; passed fresh benchmarks, then caused two &lt;code&gt;Invalid node id&lt;/code&gt; failures in the full regression run. It was not shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What shipped is a writer for all-new records that links nodes by sorted ordinal. Any upload touching an existing endpoint falls back to the earlier writer, so large mixed or update documents keep the old capacity limit. The timeout was not raised and documents are not committed in partial batches.&lt;/p&gt;

&lt;p&gt;Measured on one traced run per case on local Docker/Windows, ingest finished at:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;CSV&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;8.1 s&lt;/td&gt;
&lt;td&gt;10.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are single observations that overlapped an image rebuild. They are not throughput or p95 figures.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The model didn't beat the rules on a four-source probe
&lt;/h3&gt;

&lt;p&gt;On four public dev sources with a local Ollama Qwen model, F1 was 16.67%. The rules alone scored 20% on the identical sources. The log keeps the latest run rather than the best one, notes that earlier reruns differed, and says not all raw intermediate traces were retained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and dead ends
&lt;/h2&gt;

&lt;p&gt;Every number below comes from the repo's own work log and milestone reports, and every sample is small.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The grammar alone found almost nothing on outside data
&lt;/h3&gt;

&lt;p&gt;I ran the offline grammar on a fixed sample of 128 FewRel validation cases, with zero model calls. It emitted 1 claim and matched 0 of 128 target head/tail pairs. The check only screens directed pairs, so it measures coverage of an open-relation benchmark, not wrong answers. It still showed the grammar does not generalise beyond the text it was tuned on.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A small local model didn't fix it, and "fixing" it made things stricter
&lt;/h3&gt;

&lt;p&gt;I pointed a local Qwen3 4B model (via Ollama) at the first four FewRel examples. The result was 4 model calls, 0 claims, and 0 of 4 target pairs. The log's diagnosis was that the full candidate schema exposed too many fields to a small model, so Ollama now gets a compact schema while Bedrock's is unchanged.&lt;/p&gt;

&lt;p&gt;The next change replaced one review call per candidate with one batched call of up to 12 candidates per chunk. On the same four examples, model calls fell from 8 to 5, accepted claims fell from 4 to 1, and review tasks rose from 2 to 5. Target-pair hits stayed at 0 of 4. Batching made the system more conservative, not more accurate. Four examples is too few to say more.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The model reviewer rejected a correct claim
&lt;/h3&gt;

&lt;p&gt;The reviewer rejected a claim built on "just south of" even though the source said it. The fix was to normalise directional predicates to base NORTH, SOUTH, EAST and WEST relations. The source quote is kept, and the claim still has to pass the standard semantic review.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A faster writer that failed real tests
&lt;/h3&gt;

&lt;p&gt;Large uploads timed out at the 5,000 ms graph-query budget. At baseline, 1,000 and 2,000 rows timed out in both CSV and JSON. The bottleneck was not a missing index. Repeated endpoint lookups emitted about 60,200 pending records per call at 100 rows, which made the write quadratic.&lt;/p&gt;

&lt;p&gt;Three attempts to speed up the write path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct &lt;code&gt;UNWIND MERGE&lt;/code&gt;:&lt;/strong&gt; failed the real label and payload checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CREATE&lt;/code&gt; that still merged endpoints:&lt;/strong&gt; correct, but large cases still timed out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined new and existing node pool:&lt;/strong&gt; passed fresh benchmarks, then caused two &lt;code&gt;Invalid node id&lt;/code&gt; failures in the full regression run. It was not shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What shipped is a writer for all-new records that links nodes by sorted ordinal. Any upload touching an existing endpoint falls back to the earlier writer, so large mixed or update documents keep the old capacity limit. The timeout was not raised and documents are not committed in partial batches.&lt;/p&gt;

&lt;p&gt;Measured on one traced run per case on local Docker/Windows, ingest finished at:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;CSV&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;8.1 s&lt;/td&gt;
&lt;td&gt;10.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are single observations that overlapped an image rebuild. They are not throughput or p95 figures.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The model never beat the rules on the same texts
&lt;/h3&gt;

&lt;p&gt;On the first four public development texts, an early run of a local Qwen model scored F1 16.67%, against 20% for the rules alone. After later extraction fixes, the model run scored 82.35% (7 correct out of 8 predictions, 9 targets) and the rules alone scored 87.5% on identical texts. The report states the model added no exact recall beyond the rules. The model's earlier first probe (77.78%) is kept in the repo rather than hidden. The labels are AI-provisional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results and evidence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Read this first:&lt;/strong&gt; the extraction numbers use a development set with AI-provisional labels, and the extraction rules were tuned on that set. They are not independent accuracy. No human adjudication was added, and no paid calls were needed for the local runs. Counts are written as correct / predicted / targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extraction on the development set
&lt;/h3&gt;

&lt;p&gt;Same documents, rerun after the extraction fixes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mixed binary claims (15 targets)&lt;/td&gt;
&lt;td&gt;4 / 4 / 15&lt;/td&gt;
&lt;td&gt;10 / 10 / 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed precision / recall / F1&lt;/td&gt;
&lt;td&gt;100% / 26.67% / 42.11%&lt;/td&gt;
&lt;td&gt;100% / 66.67% / 80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public-only complete binary (12 targets)&lt;/td&gt;
&lt;td&gt;1 / 1 / 12&lt;/td&gt;
&lt;td&gt;7 / 7 / 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public recall / F1&lt;/td&gt;
&lt;td&gt;8.33% / 15.38%&lt;/td&gt;
&lt;td&gt;58.33% / 73.68%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact emitted quotes&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;12 / 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one public research event (0 of 1, roles 0 of 4) is still missed. Two earlier synthetic sets are retained: a 20-case set at 22 / 24, F1 95.65%, and a 26-case AI-assisted challenge set at 17 / 18 / 25, F1 79.07%. Each is tiny, and the domain samples are too small to rank domains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outside benchmarks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FewRel:&lt;/strong&gt; the offline grammar matched 0 of 128 directed pairs (1 claim emitted). A later frozen selection of 32 public examples gave 0 of 32 directed hits and 32 abstentions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T2D (entity matching):&lt;/strong&gt; 24 row pairs (12 gold matches, 12 non-matches), fixed seed. The resolver never sees gold URIs. The right candidate appeared in the pool 12 of 12 times, but 0 matches were approved, so recall is 0 and precision is undefined. It made 24 abstentions and 0 false merges. This is a derived pair probe, not the full T2D benchmark, and zero false merges here is not useful real-world recall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock Qwen3 32B (shipped model):&lt;/strong&gt; compared on 4 public Re-DocRED documents. It accepted 6 claims, answered 12 / 12 fixed-reference questions, and answered 7 / 12 questions from its own graph. Two of the four documents produced zero accepted relations for every model tried. This is not full Re-DocRED F1.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ingestion scale
&lt;/h3&gt;

&lt;p&gt;Fresh, ledger-off uploads of 100, 1,000 and 2,000 rows pass in both CSV and JSON. A 2,000-row upload produces 12,002 records and 8,000 edges. In the latest recorded run, 2,000-row ingest took 6,154.5 ms (CSV) and 6,048.4 ms (JSON), with write times of 1,637.8 ms and 1,562.6 ms. The 5,000 ms budget applies per query, so total ingest can run longer. This is not capacity for feature-on or large mixed-update uploads, and there are no p95 or server CPU figures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ledger overhead
&lt;/h3&gt;

&lt;p&gt;Single traced run on 100 rows, ledger off versus on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Off&lt;/th&gt;
&lt;th&gt;On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ingest&lt;/td&gt;
&lt;td&gt;431 ms&lt;/td&gt;
&lt;td&gt;661 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;td&gt;94.9 ms&lt;/td&gt;
&lt;td&gt;191.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cypher queries&lt;/td&gt;
&lt;td&gt;308&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact records&lt;/td&gt;
&lt;td&gt;602&lt;/td&gt;
&lt;td&gt;1,305&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact edges&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a single observation. The report makes no p95 claim and no large-scale identity claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restart persistence
&lt;/h3&gt;

&lt;p&gt;Eight seeded datasets kept identical payload and edge fingerprints across a real API-and-graph container restart. The check compares fingerprints, not just counts. The report says this is not crash or backup certification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and limitations
&lt;/h2&gt;

&lt;p&gt;UGI is a working prototype, and its own README says it is not production-ready. Here is what it costs and where it stops.&lt;/p&gt;

&lt;h3&gt;
  
  
  What each design choice costs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conservative grammar and strict quote matching&lt;/td&gt;
&lt;td&gt;Every claim points at an exact source span&lt;/td&gt;
&lt;td&gt;Low recall on ordinary prose: 0 of 128 FewRel pairs, and ambiguous pronouns are deliberately left unassigned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cautious entity linking&lt;/td&gt;
&lt;td&gt;Few false merges (0 in the T2D probe)&lt;/td&gt;
&lt;td&gt;True identities may stay fragmented unless a stable ID exists, and 0 of 24 T2D pairs were approved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model review on top of extraction&lt;/td&gt;
&lt;td&gt;A second check on model hints&lt;/td&gt;
&lt;td&gt;The reviewer is fallible: it can accept under-specified claims and reject supported ones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typed endpoints and a fixed tool set&lt;/td&gt;
&lt;td&gt;No raw query access&lt;/td&gt;
&lt;td&gt;You can only ask the questions the tools were built for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic guided agent&lt;/td&gt;
&lt;td&gt;Predictable, auditable behaviour&lt;/td&gt;
&lt;td&gt;It is not a free-form LLM planner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Limits worth knowing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The benchmarks are small, and most are tuned.&lt;/strong&gt; The development set has AI-provisional labels and the extraction rules were tuned on it, so the F1 figures there are not independent accuracy. FewRel and T2D are diagnostics, not full benchmark runs. On the latest matched public subset, the local model's exact recall did not improve over the rules alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounded is not true.&lt;/strong&gt; UGI checks that a claim is faithfully tied to the source text. If the source is wrong, UGI records that the source said it. The exact-quote check measures span correctness, not whether the sentence actually entails the claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receipt fingerprints are not signatures.&lt;/strong&gt; They are plain SHA-256 content hashes. They catch changed facts, not a tampered database. There are no signatures or HMACs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fence covers internal stores only.&lt;/strong&gt; Writes to the graph, review tasks and receipt store are gated and run inside one transaction. There is deliberately no adapter for external side effects, because atomicity can't be promised outside SQLite. On the HTTP API the write gate is the request flag alone. The &lt;code&gt;UGI_MCP_ALLOW_WRITES&lt;/code&gt; second lock applies to the MCP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security is demo-grade.&lt;/strong&gt; API keys are not a substitute for real IAM, TLS or tenant policy, and the README says not to expose the demo container publicly as-is. It also says there is no production authority RBAC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale is proven only at small sizes.&lt;/strong&gt; Fresh, ledger-off uploads of 1,000 and 2,000 rows complete in later runs, after timing out at baseline. Large uploads that touch existing records keep the old limit. The identity ledger rejects more than 200 mentions per upload, and nothing here is a million-record identity engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability was smoke-tested only.&lt;/strong&gt; Restart persistence was checked by comparing fingerprints across a container restart. That is not crash or backup testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input gaps.&lt;/strong&gt; Scanned PDFs need OCR, which isn't bundled. Extraction is a rule system, not general language understanding.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it can be used
&lt;/h2&gt;

&lt;p&gt;All of the examples below run on the repo's synthetic fixtures. The public datasets (FewRel, T2D, Re-DocRED) were used only as diagnostics, so none of this is evidence from real deployments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incident response.&lt;/strong&gt; Upload &lt;code&gt;demo/incident.json&lt;/code&gt;, investigate a service such as &lt;code&gt;Cache&lt;/code&gt;, and inspect the saved review tasks. This serves an on-call engineer who needs to separate a source-backed dependency (&lt;code&gt;SOURCE_ASSERTED&lt;/code&gt;) from a derived chain (&lt;code&gt;RULE_DERIVED&lt;/code&gt;) and from a path that proves nothing about the predicate (&lt;code&gt;CONNECTED_ONLY&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chains.&lt;/strong&gt; &lt;code&gt;demo/supply_chain.csv&lt;/code&gt; and &lt;code&gt;supply_network.json&lt;/code&gt; exercise the same code on a second domain. In the recorded run, a possible supply event stayed &lt;code&gt;UNKNOWN&lt;/code&gt; instead of being promoted to a fact. This serves anyone auditing which statements a document actually makes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research and organisations.&lt;/strong&gt; &lt;code&gt;research_identity.json&lt;/code&gt; and &lt;code&gt;shared_employer.json&lt;/code&gt; cover collaborations, shared employers and identity changes. The identity ledger lets an operator merge, split or undo entity records without rewriting the claims underneath. A deliberately wrong merge was recorded and reverted in the demo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent builders.&lt;/strong&gt; The MCP server exposes four typed tools, and receipts tell an agent whether a cached conclusion is still valid. This serves anyone building an agent that reuses earlier answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://main.d6ybcxya0jl1.amplifyapp.com/" rel="noopener noreferrer"&gt;https://main.d6ybcxya0jl1.amplifyapp.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo turns source documents into an evidence graph. It answers questions about how things connect and tells you how well each answer is supported, instead of presenting every link as a fact. Nothing to install.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load a dataset.&lt;/strong&gt; "IT incident" is preselected under &lt;strong&gt;Dataset&lt;/strong&gt;. Click &lt;strong&gt;Load evidence&lt;/strong&gt;. The evidence map fills in, and the counters show entities, claims, events, saved cases and open reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the evidence.&lt;/strong&gt; Click any node or link. The &lt;strong&gt;Source inspector&lt;/strong&gt; shows the exact source passage behind it. Use &lt;strong&gt;3D Orbit&lt;/strong&gt; to switch the view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask a question.&lt;/strong&gt; Under &lt;strong&gt;Evaluate a connection&lt;/strong&gt;, the form is pre-filled with &lt;code&gt;Checkout&lt;/code&gt; / &lt;code&gt;TRANSITIVELY_DEPENDS_ON&lt;/code&gt; / &lt;code&gt;Cache&lt;/code&gt;. Click &lt;strong&gt;Evaluate evidence&lt;/strong&gt;. Expect &lt;code&gt;RULE_DERIVED&lt;/code&gt;: two source-backed hops chained by a declared rule. Then change the predicate and ask again:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Checkout&lt;/code&gt; &lt;code&gt;DEPENDS_ON&lt;/code&gt; &lt;code&gt;Cache&lt;/code&gt; gives &lt;code&gt;CONNECTED_ONLY&lt;/code&gt;, because a path exists but no source states a direct dependency.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Gateway&lt;/code&gt; &lt;code&gt;DEPENDS_ON&lt;/code&gt; &lt;code&gt;Cache&lt;/code&gt; gives &lt;code&gt;SOURCE_ASSERTED&lt;/code&gt;, with the source row as evidence.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cache&lt;/code&gt; &lt;code&gt;CAUSED&lt;/code&gt; &lt;code&gt;Incident 7&lt;/code&gt; gives &lt;code&gt;UNKNOWN&lt;/code&gt;, because the only row calls the cause "possible".&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview an investigation.&lt;/strong&gt; Under &lt;strong&gt;Investigate and act&lt;/strong&gt;, the target is &lt;code&gt;Cache&lt;/code&gt;. Click &lt;strong&gt;Preview investigation&lt;/strong&gt;. This is read-only. It shows what depends on &lt;code&gt;Cache&lt;/code&gt; and which claims are uncertain or in conflict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save it (optional).&lt;/strong&gt; Tick the authorize checkbox and click &lt;strong&gt;Execute authorized action&lt;/strong&gt;. This creates a saved investigation and review tasks, which appear under &lt;strong&gt;Saved investigations&lt;/strong&gt; and &lt;strong&gt;Review queue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record a decision.&lt;/strong&gt; Decide on a review task. A decision records a human judgement. It never turns a model proposal into a source assertion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see other domains, choose "Supply chain" or "Research &amp;amp; identity" under &lt;strong&gt;Dataset&lt;/strong&gt; and click &lt;strong&gt;Load evidence&lt;/strong&gt; again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The idea behind Graphynx is simple: a connection in a graph is not a fact until a source says so. Every claim points at an exact quote and offsets in the original document. Every answer comes back as one of seven states, so &lt;code&gt;CONNECTED_ONLY&lt;/code&gt; and &lt;code&gt;UNKNOWN&lt;/code&gt; stay visible in the cases where most systems would quietly draw a confident line. Agent writes need explicit authorisation, and a saved agent conclusion goes stale when any fact it read changes.&lt;/p&gt;

&lt;p&gt;This is a prototype, and the limits are real. The benchmarks are small, the grammar misses a lot of ordinary prose, and the write fence covers internal stores only. Receipt fingerprints are plain SHA-256 hashes, not signatures. What the project does show is a design where an answer carries its evidence and its uncertainty with it, so a reader can tell what a system found in the sources, what it inferred by rule, and what it doesn't know.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>agents</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
