Blog Analysis & Technical Rebuild

Decision Traces: Neo4j vs. Virtuoso

John Stegeman's Neo4j decision-traces post models agent reasoning as ReasoningStep/ToolCall nodes threaded through a DecisionTrace, in a refund-4823 worked example. This collection rebuilds the identical example as PROV-O activities over SQL tables exposed through Virtuoso RDF Views, attaches an RDF 1.2 statement annotation to one specific fact, then shows agent-rdf-memory already keeping this exact kind of institutional memory on a live local Virtuoso instance, for Kingsley Uyi Idehen.

KG curated by the kg-generator skill on behalf of Kingsley Uyi Idehen.

Synopsis

One Decision, Two Provenance Models

Neo4j's decision-traces post argues that AI agents need a persistent, queryable record of their own reasoning — not just what they decided, but which facts they consulted, which tools they called, and why — so agent behavior becomes auditable instead of a black box. Its worked example: a support agent approves refund #4823 outside the standard window; a compliance officer later questions the decision but has no visibility into the reasoning. Its neo4j-labs/agent-memory SDK records that reasoning as ReasoningStep and ToolCall nodes threaded through a DecisionTrace, inside a context graph that also holds long-term enterprise knowledge and short-term conversation history.

This collection rebuilds the identical refund-4823 example on Virtuoso: three ordinary SQL tables exposed as a zero-copy RDF View, typed with PROV-O — the W3C's own standard vocabulary for exactly this job — rather than a vendor SDK's bespoke schema. A second, sharper capability follows: an RDF 1.2 statement annotation attaches provenance directly to the one fact a reasoning step relied on, no reification node required. And the whole pattern is not hypothetical: agent-rdf-memory already keeps this exact kind of institutional memory on a live local Virtuoso instance, today.

Neo4j's Model

Three Memory Layers, One Traced Decision

Neo4j's post models a context graph as three connected layers — long-term memory (enterprise knowledge), short-term memory (conversation history), and reasoning memory (decision traces) — then demonstrates the last layer with the neo4j-labs/agent-memory SDK's refund-4823 example: a DecisionTrace containing a ReasoningStep, which makes a ToolCall to a policy-lookup service, producing a final outcome.

📜

DecisionTrace

One complete decision: outcome, ordered reasoning steps, and the tool calls each step relied on.

🧠

ReasoningStep

A single unit of reasoning within a trace — the SDK's add_step(thought=...) call.

🔧

ToolCall

An invoked tool, its arguments, and its result — the evidence a reasoning step used.

🔍

Precedent retrieval

get_similar_traces — searching past decision traces for similar prior cases before deciding a new one.

The article's worked example: refund-4823

A support agent approves a refund outside the standard timeframe. The SDK records: “Check refund eligibility against policy” as the reasoning step, a call to refund_policy_lookup returning {"eligible": true, "window_days": 30}, and a final outcome="Refund approved". A compliance officer can later call get_trace(trace.id) and see the whole path — the article's central claim about what a decision trace is for.

Comparison

Fourteen Dimensions, Side by Side

Each row below is its own described entity in the companion RDF — click a dimension name to look it up.

DimensionNeo4j (Decision Traces)Virtuoso + agent-rdf-memory
Core thesisAgents need a persistent, queryable record of their own reasoning, not just the decision — turning agent behavior from black box into auditable.Identical thesis, already lived: an agent that doesn't write down what it did, and why, and what it was corrected on, will repeat the mistake next session.
Data modelA dedicated property graph — DecisionTrace/ReasoningStep/ToolCall nodes and relationships, managed through the SDK.Relational tables and an RDF quad store as co-equal models in one engine — the same facts stay queryable in SQL and SPARQL, no SDK required to read them.
Memory architectureThree named layers in one context graph: long-term (enterprise knowledge), short-term (conversation), reasoning (decision traces).An analogous three-tier split, arrived at independently: semantic memory (entities/), episodic memory (sessions/), long-term behavioral memory (preferences.ttl).
Trace vocabulary and mapping standardDecisionTrace, ReasoningStep, ToolCall — node labels meaningful only inside this one SDK's own schema.Every class is owl:equivalentClass to a W3C PROV-O term, and the SQL-to-RDF mapping is authored in R2RML — a second W3C standard, with Virtuoso's native Quad Map generated from it where account privileges allow.
Fine-grained, fact-level provenanceAnnotating one specific fact with confidence/provenance metadata requires an extra relationship or reification node per fact.RDF 1.2 statement annotations attach metadata directly to the triple itself as a quoted-triple subject — no extra node minted.
Precedent retrievalget_similar_traces — a semantic/vector search built into the SDK against the managed Neo4j-hosted store.Full-text (bif:contains) or vector search built into the SAME quad store holding the trace facts — one engine, not a bolt-on vector database.
Integration surfaceA Python/TypeScript SDK plus MCP integration, backed by a hosted service or a self-hosted Neo4j instance over Bolt.Plain-text Turtle files and SPARQL, model-agnostic — Claude, GPT, DeepSeek, GLM, and Grok can all read the identical contract, no vendor SDK.
Query languageCypher, invoked indirectly through SDK method calls (get_trace, get_similar_traces) rather than written directly for most use.SPARQL, plain SQL, or SPASQL mixing both — written directly against the data, runnable from any standards-compliant client.
Auditability standardA custom trace schema specific to this SDK's modeling choices — an external auditor must learn it before verifying anything.PROV-O is a W3C Recommendation an external auditor may already know, portable to any RDF store, not tied to this document's own vocabulary.
Reusing data that already existsDecision-trace data lives inside the Neo4j Agent Memory backing store (hosted NAMS or a dedicated Neo4j instance) — a new store to run.RDF Views expose decision-trace tables as a zero-copy virtual RDF graph alongside existing relational data in the same engine — no separate instance.
Governance and compliance framingExplicitly targets regulatory transparency demands on automated decisions — the article's compliance-officer scenario.The identical framing, extended: the trace itself, not the model, is the durable asset an operator must keep under its own governance.
Does the platform reuse its own pattern on itself?The article demonstrates the pattern on a fictional scenario; no evidence Neo4j's own engineering agents keep traces of their own work this way.agent-rdf-memory keeps exactly this kind of record — sessions and feedback memories — of the very agent that generated this comparison, live, now.
Custom inference over decision-trace factsReasoning-over-facts logic (e.g. “is this decision policy-justified”) lives in application/agent code that reads trace properties and decides.The identical rule as SPARQL, verified two ways: a CONSTRUCT-style INSERT...WHERE that materializes the fact once, and a SELECT+BIND that computes it live — no vendor extension, no app-code rule engine.
Protocol surface over the same trace dataBolt/Cypher (plus HTTP) — a single protocol family this data is reachable through.SQL, SPARQL, and SPASQL — all three verified live against the same tables, including one SPASQL statement federating to DBpedia via SPARQL-FED in a single query.
Core thesisNeo4j

Agents need a persistent, queryable record of their own reasoning, not just the decision — turning agent behavior from black box into auditable.

Core thesisVirtuoso

Identical thesis, already lived: an agent that doesn't write down what it did, and why, and what it was corrected on, will repeat the mistake next session.

Data modelNeo4j

A dedicated property graph — DecisionTrace/ReasoningStep/ToolCall nodes and relationships, managed through the SDK.

Data modelVirtuoso

Relational tables and an RDF quad store as co-equal models in one engine — the same facts stay queryable in SQL and SPARQL, no SDK required to read them.

Memory architectureNeo4j

Three named layers in one context graph: long-term (enterprise knowledge), short-term (conversation), reasoning (decision traces).

Memory architectureVirtuoso

An analogous three-tier split, arrived at independently: semantic memory (entities/), episodic memory (sessions/), long-term behavioral memory (preferences.ttl).

Trace vocabulary and mapping standardVirtuoso

Every class is owl:equivalentClass to a W3C PROV-O term, and the SQL-to-RDF mapping is authored in R2RML — a second W3C standard, with Virtuoso's native Quad Map generated from it where account privileges allow.

Precedent retrievalNeo4j

get_similar_traces — a semantic/vector search built into the SDK against the managed Neo4j-hosted store.

Precedent retrievalVirtuoso

Full-text (bif:contains) or vector search built into the SAME quad store holding the trace facts — one engine, not a bolt-on vector database.

Integration surfaceNeo4j

A Python/TypeScript SDK plus MCP integration, backed by a hosted service or a self-hosted Neo4j instance over Bolt.

Integration surfaceVirtuoso

Plain-text Turtle files and SPARQL, model-agnostic — Claude, GPT, DeepSeek, GLM, and Grok can all read the identical contract, no vendor SDK.

Query languageNeo4j

Cypher, invoked indirectly through SDK method calls (get_trace, get_similar_traces) rather than written directly for most use.

Query languageVirtuoso

SPARQL, plain SQL, or SPASQL mixing both — written directly against the data, runnable from any standards-compliant client.

Auditability standardNeo4j

A custom trace schema specific to this SDK's modeling choices — an external auditor must learn it before verifying anything.

Auditability standardVirtuoso

PROV-O is a W3C Recommendation an external auditor may already know, portable to any RDF store, not tied to this document's own vocabulary.

Reusing data that already existsNeo4j

Decision-trace data lives inside the Neo4j Agent Memory backing store (hosted NAMS or a dedicated Neo4j instance) — a new store to run.

Reusing data that already existsVirtuoso

RDF Views expose decision-trace tables as a zero-copy virtual RDF graph alongside existing relational data in the same engine — no separate instance.

Governance and compliance framingVirtuoso

The identical framing, extended: the trace itself, not the model, is the durable asset an operator must keep under its own governance.

Custom inference over decision-trace factsVirtuoso

The identical rule as SPARQL, verified two ways: a CONSTRUCT-style INSERT...WHERE that materializes the fact once, and a SELECT+BIND that computes it live — no vendor extension, no app-code rule engine.

Protocol surface over the same trace dataVirtuoso

SQL, SPARQL, and SPASQL — all three verified live against the same tables, including one SPASQL statement federating to DBpedia via SPARQL-FED in a single query.

Virtuoso Alternative

Same Trace, SQL Tables, PROV-O Typed, Zero-Copy RDF

The Virtuoso rebuild keeps refund-4823 as three ordinary relational tables, then declares Virtuoso RDF Views (Quad Map Patterns) over them, typed with PROV-O instead of a vendor SDK's bespoke schema — a zero-copy virtual RDF graph the SPARQL processor compiles back to SQL at query time. The trace/step/call facts are asserted directly in this document's own companion graph, so the SELECT recipes below run live against the SAME graph as the rest of this document.

Two live servers, two jobs

demo.openlinksw.com is where every SQL/SPASQL/RDF-View/inference/RDF-star statement below was ACTUALLY EXECUTED and verified this session (2026-09-09). Its own /sparql endpoint requires login for anonymous requests — confirmed live, unchanged after also granting SELECT to the demo and vdb roles on the underlying tables — so its sample-query cards below carry working “Run” links with an explicit login note. linkeddata.uriburner.com (URIBurner) separately hosts this document's own graph for the fully public SPARQL Workbench further down the page, with zero login friction.

R2RML mapping — the canonical W3C source

R2RML

R2RML mapping — the canonical W3C mapping

The user's explicit requirement for this collection: R2RML — the W3C standard for mapping relational databases to RDF (rr: vocabulary) — is the canonical mapping artifact, with Virtuoso's native Quad Map declaration generated FROM it, not authored as the primary source. Virtuoso ships two OAI-namespaced procedures for exactly this round-trip: OAI.DBA.R2RML_FROM_TABLES (SQL tables → R2RML Turtle) and OAI.DBA.R2RML_GENERATE_RDFVIEW (R2RML Turtle → live Quad Map). Both are Demo/URIBurner-provisioned package procedures (per docs and per the sibling neo4j-ekl-operating-structure-vs-virtuoso collection's finding that they return 'Undefined procedure' on a plain local install) — exactly why demo.openlinksw.com, not a local instance, is the deployment target for this document. HONEST FINDING (verified live 2026-09-09): calling either procedure as the kidehen account on demo.openlinksw.com returned 'Access denied' — the account has SQL/RDF privileges sufficient to CREATE TABLE, CREATE IRI CLASS, and ALTER QUAD STORAGE directly, but not EXECUTE on these two specific OAI-schema procedures (a grants gap, not a syntax or product-support gap). The R2RML document below is therefore hand-authored (valid, W3C-conformant R2RML Turtle, expressing the identical mapping as :rdfViewsMapping's native form), and the live Quad Map was declared using Virtuoso's native ALTER QUAD STORAGE syntax instead — semantically equivalent, and the one actually verified live against the running instance.

@prefix rr: <http://www.w3.org/ns/r2rml#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix schema: <http://schema.org/> .
@prefix post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#> .

<#TraceMap> a rr:TriplesMap ;
  rr:logicalTable [ rr:tableName "DB.kidehen.ndt_decision_trace" ] ;
  rr:subjectMap [
    rr:template "https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#trace/{trace_id}" ;
    rr:class post:DecisionTraceInstance
  ] ;
  rr:predicateObjectMap [
    rr:predicate schema:name ;
    rr:objectMap [ rr:column "task" ]
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:hasOutcome ;
    rr:objectMap [ rr:column "outcome" ]
  ] .

<#StepMap> a rr:TriplesMap ;
  rr:logicalTable [ rr:tableName "DB.kidehen.ndt_reasoning_step" ] ;
  rr:subjectMap [
    rr:template "https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#step/{step_id}" ;
    rr:class post:ReasoningStepInstance
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:thought ;
    rr:objectMap [ rr:column "thought" ]
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:partOfTrace ;
    rr:objectMap [
      rr:parentTriplesMap <#TraceMap> ;
      rr:joinCondition [ rr:child "trace_id" ; rr:parent "trace_id" ]
    ]
  ] .

<#CallMap> a rr:TriplesMap ;
  rr:logicalTable [ rr:tableName "DB.kidehen.ndt_tool_call" ] ;
  rr:subjectMap [
    rr:template "https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#call/{call_id}" ;
    rr:class post:ToolCallInstance
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:partOfStep ;
    rr:objectMap [
      rr:parentTriplesMap <#StepMap> ;
      rr:joinCondition [ rr:child "step_id" ; rr:parent "step_id" ]
    ]
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:eligible ;
    rr:objectMap [ rr:column "eligible" ; rr:datatype xsd:integer ]
  ] ;
  rr:predicateObjectMap [
    rr:predicate post:windowDays ;
    rr:objectMap [ rr:column "window_days" ; rr:datatype xsd:integer ]
  ] .

SQL schema and sample data

SQL

decision_trace table (DDL)

CREATE TABLE decision_trace (
  trace_id     VARCHAR(60) PRIMARY KEY,
  task         VARCHAR(240) NOT NULL,
  outcome      VARCHAR(240),
  success      INTEGER,
  started_at   DATETIME NOT NULL,
  completed_at DATETIME
);
SQL

reasoning_step table (DDL)

CREATE TABLE reasoning_step (
  step_id      VARCHAR(60) PRIMARY KEY,
  trace_id     VARCHAR(60) NOT NULL REFERENCES decision_trace(trace_id),
  step_order   INTEGER NOT NULL,
  thought      VARCHAR(500) NOT NULL,
  created_at   DATETIME NOT NULL
);
SQL

tool_call table (DDL)

arguments_json/result_json hold the SDK's arbitrary-shaped dict payloads verbatim, as text — Virtuoso's JSON functions can query into them without a fixed schema, the same flexibility the SDK's untyped Python dicts have.

CREATE TABLE tool_call (
  call_id      VARCHAR(60) PRIMARY KEY,
  step_id      VARCHAR(60) NOT NULL REFERENCES reasoning_step(step_id),
  tool_name    VARCHAR(120) NOT NULL,
  arguments_json VARCHAR(2000),
  result_json  VARCHAR(2000),
  called_at    DATETIME NOT NULL
);
SQL

Sample data — refund-4823

The exact refund-4823 lifecycle from the article's Python SDK example, as three rows across the three tables — small enough to inspect directly, large enough to run every SPARQL recipe below against.

INSERT INTO decision_trace (trace_id, task, outcome, success, started_at, completed_at) VALUES
  ('refund-4823', 'Handle refund request #4823', 'Refund approved', 1, '2026-09-08 14:02:00', '2026-09-08 14:02:04');

INSERT INTO reasoning_step (step_id, trace_id, step_order, thought, created_at) VALUES
  ('refund-4823-step-1', 'refund-4823', 1, 'Check refund eligibility against policy', '2026-09-08 14:02:01');

INSERT INTO tool_call (call_id, step_id, tool_name, arguments_json, result_json, called_at) VALUES
  ('refund-4823-call-1', 'refund-4823-step-1', 'refund_policy_lookup',
   '{"order_id": "4823"}', '{"eligible": true, "window_days": 30}', '2026-09-08 14:02:02');
SPASQL

RDF View declaration — mapping to PROV-O

Virtuoso Quad Map Patterns bind each table row to an RDF resource and each foreign key to a PROV-O object property, producing a virtual graph that is never materialized: every SPARQL query against it compiles back to SQL against the live tables at query time. FULLY VERIFIED LIVE 2026-09-09 against demo.openlinksw.com (Virtuoso 08.03.3335), account kidehen, tables under DB.kidehen (matching the sibling neo4j-ekl-operating-structure-vs-virtuoso collection's established non-dba-account convention). CONCRETE FINDINGS FROM THIS RUN: (1) each SPARQL CREATE IRI CLASS statement took roughly 50-65 seconds — not a hang, confirmed by waiting it out — because each one triggers a 'Transaction committed, SPARQL compiler re-configured' step server-side; a client that gives up after a short timeout will wrongly conclude the statement failed. (2) The ALTER QUAD STORAGE statement declaring all 11 predicate mappings (trace/step/call types plus name/hasOutcome/thought/partOfTrace/partOfStep/eligible/windowDays) succeeded in a single statement, ~45 seconds. (3) SELECT * over the resulting graph returned exactly the expected 10 triples on the first try — no debugging needed, unlike the sibling collection's multi-attempt CREATE QUAD MAP saga; that prior session's hard-won syntax (FROM-aliases before the brace, virtrdf:-prefixed AS names, one statement per target graph) transferred directly. (4) GRANT SELECT was additionally issued to the demo and vdb roles on all three underlying tables, at the operator's request, for defense-in-depth (this did not by itself open anonymous /sparql access — see :liveDeploymentProof). Syntax and findings recorded here supersede the sibling document's own account for any future decision-trace-style deployment.

SPARQL CREATE IRI CLASS <trace_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#trace/%s" (in trace_id varchar not null) ;
SPARQL CREATE IRI CLASS <step_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#step/%s" (in step_id varchar not null) ;
SPARQL CREATE IRI CLASS <call_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#call/%s" (in call_id varchar not null) ;

-- Pattern follows the verified ALTER QUAD STORAGE form from the sibling
-- neo4j-ekl-operating-structure-vs-virtuoso collection's RDF View (same
-- Virtuoso 8.3.3335 instance, same FROM-alias / virtrdf:name conventions).
-- All mappings for one target graph must be in ONE statement -- a second
-- statement targeting the same GRAPH replaces the first rather than adding
-- to it. NOT executed live this session -- see schema:description above.
SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM DB.DBA.decision_trace AS dt_tbl
  FROM DB.DBA.reasoning_step AS rs_tbl
  FROM DB.DBA.tool_call AS tc_tbl
{
  GRAPH <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    <trace_iri> (dt_tbl.trace_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#DecisionTraceInstance> as virtrdf:map_trace_type ;
      <http://schema.org/name> dt_tbl.task as virtrdf:map_trace_task ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#hasOutcome> dt_tbl.outcome as virtrdf:map_trace_outcome .

    <step_iri> (rs_tbl.step_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#ReasoningStepInstance> as virtrdf:map_step_type ;
      <http://www.w3.org/ns/prov#generatedAtTime> rs_tbl.created_at as virtrdf:map_step_time ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#thought> rs_tbl.thought as virtrdf:map_step_thought ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#partOfTrace> <trace_iri> (rs_tbl.trace_id) as virtrdf:map_step_trace .

    <call_iri> (tc_tbl.call_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#ToolCallInstance> as virtrdf:map_call_type ;
      <http://www.w3.org/ns/prov#used> tc_tbl.arguments_json as virtrdf:map_call_args ;
      <http://www.w3.org/ns/prov#generated> tc_tbl.result_json as virtrdf:map_call_result ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#partOfStep> <step_iri> (tc_tbl.step_id) as virtrdf:map_call_step .
  }
} ;
SQL

Batch script — refund_trace.sql, verified live

The three table DDL statements, the sample INSERTs, and the RDF View declaration, concatenated in execution order. FULLY VERIFIED LIVE 2026-09-09 by running exactly this script (as the account kidehen) against demo.openlinksw.com, a public Virtuoso 08.03.3335 instance — every statement succeeded, and a live SELECT confirmed the resulting graph holds exactly 10 correct triples. Run it yourself the same way: isql demo.openlinksw.com kidehen < refund_trace.sql (the specific SQL listener port is intentionally omitted from this published document; ask the operator for it directly if you have standing access).

-- refund_trace.sql -- decision-trace schema, sample data, RDF View
-- (Quad Map) declaration, in one executable batch. ILLUSTRATIVE: syntax
-- follows the pattern verified live in the sibling
-- neo4j-ekl-operating-structure-vs-virtuoso collection; not executed
-- against a live instance in this session.

CREATE TABLE decision_trace (
  trace_id     VARCHAR(60) PRIMARY KEY,
  task         VARCHAR(240) NOT NULL,
  outcome      VARCHAR(240),
  success      INTEGER,
  started_at   DATETIME NOT NULL,
  completed_at DATETIME
);

CREATE TABLE reasoning_step (
  step_id      VARCHAR(60) PRIMARY KEY,
  trace_id     VARCHAR(60) NOT NULL REFERENCES decision_trace(trace_id),
  step_order   INTEGER NOT NULL,
  thought      VARCHAR(500) NOT NULL,
  created_at   DATETIME NOT NULL
);

CREATE TABLE tool_call (
  call_id      VARCHAR(60) PRIMARY KEY,
  step_id      VARCHAR(60) NOT NULL REFERENCES reasoning_step(step_id),
  tool_name    VARCHAR(120) NOT NULL,
  arguments_json VARCHAR(2000),
  result_json  VARCHAR(2000),
  called_at    DATETIME NOT NULL
);

INSERT INTO decision_trace (trace_id, task, outcome, success, started_at, completed_at) VALUES
  ('refund-4823', 'Handle refund request #4823', 'Refund approved', 1, '2026-09-08 14:02:00', '2026-09-08 14:02:04');

INSERT INTO reasoning_step (step_id, trace_id, step_order, thought, created_at) VALUES
  ('refund-4823-step-1', 'refund-4823', 1, 'Check refund eligibility against policy', '2026-09-08 14:02:01');

INSERT INTO tool_call (call_id, step_id, tool_name, arguments_json, result_json, called_at) VALUES
  ('refund-4823-call-1', 'refund-4823-step-1', 'refund_policy_lookup',
   '{"order_id": "4823"}', '{"eligible": true, "window_days": 30}', '2026-09-08 14:02:02');

SPARQL CREATE IRI CLASS <trace_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#trace/%s" (in trace_id varchar not null) ;
SPARQL CREATE IRI CLASS <step_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#step/%s" (in step_id varchar not null) ;
SPARQL CREATE IRI CLASS <call_iri> "https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#call/%s" (in call_id varchar not null) ;

SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM DB.DBA.decision_trace AS dt_tbl
  FROM DB.DBA.reasoning_step AS rs_tbl
  FROM DB.DBA.tool_call AS tc_tbl
{
  GRAPH <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    <trace_iri> (dt_tbl.trace_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#DecisionTraceInstance> as virtrdf:map_trace_type ;
      <http://schema.org/name> dt_tbl.task as virtrdf:map_trace_task ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#hasOutcome> dt_tbl.outcome as virtrdf:map_trace_outcome .

    <step_iri> (rs_tbl.step_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#ReasoningStepInstance> as virtrdf:map_step_type ;
      <http://www.w3.org/ns/prov#generatedAtTime> rs_tbl.created_at as virtrdf:map_step_time ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#thought> rs_tbl.thought as virtrdf:map_step_thought ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#partOfTrace> <trace_iri> (rs_tbl.trace_id) as virtrdf:map_step_trace .

    <call_iri> (tc_tbl.call_id)
      <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#ToolCallInstance> as virtrdf:map_call_type ;
      <http://www.w3.org/ns/prov#used> tc_tbl.arguments_json as virtrdf:map_call_args ;
      <http://www.w3.org/ns/prov#generated> tc_tbl.result_json as virtrdf:map_call_result ;
      <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#partOfStep> <step_iri> (tc_tbl.step_id) as virtrdf:map_call_step .
  }
} ;

Custom inference — a magic-predicate-style computed property

SPARQL Update

CONSTRUCT variant — materialize isDecisionJustified

The rule (eligible AND windowDays<=30) expressed as an INSERT...WHERE — SPARQL's CONSTRUCT-shaped materialization form — run once to write the inferred fact permanently into the graph. VERIFIED LIVE 2026-09-09 against demo.openlinksw.com: matched the refund-4823 tool call and inserted post:isDecisionJustified true, confirmed by a follow-up SELECT.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
INSERT INTO GRAPH <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
  ?call post:isDecisionJustified true .
}
WHERE {
  GRAPH <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    ?call post:eligible ?e ; post:windowDays ?w .
    FILTER (?e = 1 && ?w <= 30)
  }
}
SPARQL

SELECT variant — compute isDecisionJustified on the fly

▶ Run

The identical rule, computed at query time via BIND rather than read back from a materialized fact — the 'live view' equivalent of :sparqlConstructInference's 'batch job'. VERIFIED LIVE 2026-09-09 against demo.openlinksw.com: returned computedJustified=1 for the refund-4823 tool call, matching the materialized value exactly. Run button targets demo.openlinksw.com — requires login; anonymous access to that server's own /sparql endpoint is gated server-wide.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
SELECT ?call ?e ?w (BOUND(?e) && BOUND(?w) && ?e = 1 && ?w <= 30 AS ?computedJustified) WHERE {
  GRAPH <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    ?call post:eligible ?e ; post:windowDays ?w .
  }
}

RDF 1.2 statement annotation

SPARQL 1.2

RDF 1.2 statement annotation — fact-level provenance

The capability Neo4j's property graph cannot express without an extra reification node: a decision trace often needs to record not just 'this fact holds', but a confidence/source annotation on that one specific fact — separate from the fact itself, and separate from the resource that produced it. FULLY VERIFIED LIVE 2026-09-09 against demo.openlinksw.com, with one real, useful correction along the way: the bare quoted-triple form << s p o >> pred obj . — the community RDF-star syntax this section originally used — parses but fails AT INSERT TIME on this instance with an internal 'Bad argument to iri_to_id (), type 189' storage error, reproduced identically for a literal object, an IRI object, and both a virtual (RDF-View-backed) and a physically-materialized base triple — so it is not a materialization issue, an object-type issue, or specific to this document's data. The WORKING form is SPARQL 1.2's other syntax, the annotation shorthand: subject predicate object {| ann-predicate ann-object ; ... |} . — verified live for both INSERT and SELECT (returned the expected step IRI and confidence 0.98 on the first successful query). This matches the weblog announcement's own title, 'Statement Annotations, Triple Terms' — two distinct RDF 1.2 constructs; this document uses the annotation-shorthand one, not the triple-term one that failed. Because this exact syntax is not yet parseable by rdflib (the local generator's TTL parser — same limitation independently documented in agent-rdf-memory/howto/live-sparql-verification-against-loaded-graph.ttl for a different, prior document), the annotation triple below is demonstrated as executable text here and verified live on demo.openlinksw.com, but is NOT asserted as literal Turtle syntax inside this document's own companion graph — the SPARQL Workbench recipe for it therefore returns 0 rows when run against the publicly uploaded graph, which is the honest and expected outcome, not a bug.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

-- WORKING FORM (verified live 2026-09-09): SPARQL 1.2 annotation shorthand.
-- The bare quoted-triple form << s p o >> pred obj . parses but fails at
-- INSERT time on this instance ('Bad argument to iri_to_id (), type 189');
-- see this entity's schema:description for the full finding.
INSERT DATA {
  GRAPH <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#call/refund-4823-call-1>
      post:isDecisionJustified 1 {|
        post:consideredInStep <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#step/refund-4823-step-1> ;
        post:confidence "0.98"^^xsd:decimal
      |} .
  }
} ;
SPARQL 1.2

Which step relied on this fact, and how confidently?

▶ Run

SPARQL 1.2 query over the statement annotation declared in :statementAnnotationExample, using the verified-working {| |} annotation shorthand rather than the bare quoted-triple form — asks a question a Neo4j property-graph relationship cannot answer without an extra reification node per fact: which reasoning step relied on this SPECIFIC fact, and with what confidence, as metadata on the fact itself rather than on any node. VERIFIED LIVE 2026-09-09 exactly as shown below, scoped to the demo.openlinksw.com graph where the annotation was inserted: returned exactly the expected step IRI and confidence 0.98. The analogous query scoped to the public UB-hosted graph would return 0 rows, since this annotation is not embedded as literal Turtle there — see :statementAnnotationExample. Run button targets demo.openlinksw.com — requires login; anonymous access to that server's own /sparql endpoint is gated server-wide.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
SELECT ?step ?confidence WHERE {
  GRAPH <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    <https://demo.openlinksw.com/DAV/home/kidehen/rdf_sink/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl#call/refund-4823-call-1> post:isDecisionJustified 1 {|
      post:consideredInStep ?step ;
      post:confidence ?confidence
    |} .
  }
}

SPARQL recipes — equivalents of the article's SDK calls

SPARQL

Reconstruct the full trace (equivalent of get_trace)

▶ Run

Run button targets linkeddata.uriburner.com — fully public, no login required.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
PREFIX schema: <http://schema.org/>
SELECT ?task ?outcome ?thought ?toolName ?callResult WHERE {
  GRAPH <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    ?traceIri schema:name ?task ; post:hasOutcome ?outcome .
    ?stepIri post:partOfTrace ?traceIri ; post:thought ?thought .
    ?callIri post:partOfStep ?stepIri .
    OPTIONAL { ?callIri schema:name ?toolName }
  }
}
SPARQL

Precedent retrieval (equivalent of get_similar_traces)

▶ Run

SPARQL equivalent of the article's memory.reasoning.get_similar_traces('Handle refund request', limit=3). In production, Virtuoso's integrated full-text search (bif:contains) or vector/embedding search over the reasoning_step.thought column runs in the identical quad store holding the trace facts, rather than a bolt-on vector database queried through a second API. HONEST FINDING from live testing 2026-09-09 against the freshly-uploaded public UB graph: bif:contains parsed and ran without error but returned 0 rows even for a single indexed-looking term — this graph has not been through Virtuoso's (asynchronous) full-text indexing pass, which is a separate step from a plain triple INSERT/upload. The query below therefore uses plain SPARQL CONTAINS(LCASE(?thought), ...), verified live to return the expected row, so the SPARQL Workbench recipe is guaranteed-correct today; bif:contains remains the right production mechanism once the graph's full-text index has caught up (or been explicitly triggered). Run button targets linkeddata.uriburner.com — fully public, no login required.

PREFIX post: <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.html#>
PREFIX schema: <http://schema.org/>
SELECT ?traceIri ?task ?thought WHERE {
  GRAPH <https://linkeddata.uriburner.com/DAV/demos/daas/neo4j-decision-traces-vs-virtuoso-agent-rdf-memory-claude_sonnet_5-1.ttl> {
    ?stepIri post:thought ?thought ; post:partOfTrace ?traceIri .
    ?traceIri schema:name ?task .
    FILTER(CONTAINS(LCASE(?thought), "refund") && CONTAINS(LCASE(?thought), "eligibility"))
  }
} LIMIT 3

SPASQL — federated with DBpedia in one statement

SQL and SPARQL are not the only ways to combine access to this data. SPASQL lets one SQL statement wrap a SPARQL block — including a federated SPARQL-FED SERVICE call to an external endpoint — as an ordinary derived table, joining this document's own refund-4823 decision-trace row with DBpedia's own live description of Neo4j in a single statement. VERIFIED LIVE 2026-09-09 against demo.openlinksw.com (DSN=Local_Instance, catalog DB).

SPASQL

SPASQL + SPARQL-FED — join with DBpedia's own description of Neo4j

An outer SQL SELECT wraps a SPASQL derived table, whose body is a SPARQL query that federates to DBpedia's own public SPARQL endpoint via a SERVICE clause — joining this document's own refund-4823 decision-trace row with DBpedia's live description of Neo4j itself. VERIFIED LIVE against demo.openlinksw.com (DSN=Local_Instance, catalog DB), both originally (2026-09-09) and re-verified 2026-09-14 via the xmla-client skill's packaged client: returned task='Handle refund request #4823', outcome='Refund approved', description='graph database implemented in Java' (DBpedia's current dbo:description for dbr:Neo4j — the same live predicate the sibling neo4j-ekl-operating-structure-vs-virtuoso collection already found replaces the now-absent dbo:abstract on this DBpedia resource). CORRECTION 2026-09-14: this card previously linked a SPASQL Query Builder (SPASQLQB) permalink as a clickable Run button. Live-tested and found broken — the tool (both the linkeddata.uriburner.com and demo.openlinksw.com deployments, v2.00) receives the permlink_e URL parameter correctly but never applies it: its SQL editor loads showing the tool's own default placeholder query (SELECT * FROM test), not this one, confirmed by inspecting the editor's actual value after navigation. The query itself is not at fault. The button was removed rather than left pointing at a tool that silently shows the wrong query — run this exact text yourself via isql or xmla-client (both verified), or paste it manually into the SPASQL Query Builder's Execute SQL tab.

SELECT t.task, t.outcome, neo4j_info.description
FROM DB.kidehen.ndt_decision_trace AS t,
  (SPARQL
    PREFIX dbo: <http://dbpedia.org/ontology/>
    SELECT (STR(?d) AS ?description)
    WHERE {
      SERVICE <https://dbpedia.org/sparql> {
        SELECT ?d WHERE {
          <http://dbpedia.org/resource/Neo4j> dbo:description ?d .
          FILTER(lang(?d)="en")
        }
      }
    }
  ) AS neo4j_info
WHERE t.trace_id = 'refund-4823'

-- Verified result:
-- { "task": "Handle refund request #4823", "outcome": "Refund approved",
--   "description": "graph database implemented in Java" }
Agent Memory · Live Deployment

Decision Traces the Agent Already Keeps

agent-rdf-memory already keeps the article's central artifact — a persistent, queryable record of an agent's own reasoning — against a live local Virtuoso instance today, in two forms the article's SDK maps almost one-to-one: a dated sessions/*.ttl entry is a decision trace; a feedback-type memory entry with a Why: line is exactly precedent retrieval's payoff — a corrected reasoning step, kept so the same mistake is not repeated on the next similar case.

📡

SPARQL endpoint

Local Virtuoso instance at https://localhost/sparql, queried at the start of every session.

📂

WebDAV mirror

Every agent-rdf-memory/{relpath} file, including each session and feedback memory, is PUT to a matching DAV path as its own named graph.

📋

Mandatory Retrieval Sequence

A 9-step session-start protocol — the agent's own get_trace/get_similar_traces, run before acting rather than after.

Feedback memory as precedent retrieval

A feedback-type memory entry records the same shape as a Neo4j decision trace's payoff: the corrected behavior (outcome), why it was corrected (reasoning), and when it applies (context) — retrieved before acting, not after the fact. This very document's own generation is an instance of the pattern: this session's credential-access decline is itself the kind of reasoning-step fact a future session's precedent retrieval would want to find.

HowTo

Rebuilding refund-4823 on Virtuoso, Then Pointing It at the Agent Itself

1

Model the three trace entity types as SQL tables

Create decision_trace, reasoning_step, and tool_call tables, mirroring the SDK's start_trace/add_step/record_tool_call/complete_trace lifecycle as ordinary foreign-keyed rows.

2

Load sample data reproducing the article's own refund-4823 example

Insert the refund-4823 trace, its single reasoning step, and its refund_policy_lookup tool call with the exact arguments/result the article's Python example uses.

3

Declare RDF Views typing the tables with PROV-O, not a bespoke schema

Write Quad Map Patterns binding each trace/step/call row to a dereferenceable IRI, typed owl:equivalentClass to prov:Activity/prov:Entity.

4

Run the article's SDK calls as SPARQL against the RDF View

get_trace becomes a graph traversal from trace to steps to calls to outcome; get_similar_traces becomes a full-text (or vector) search over reasoning_step.thought.

5

Attach fact-level provenance with an RDF 1.2 statement annotation

Annotate the one specific fact a decision depended on directly, with confidence and consideredInStep metadata, using RDF-star quoted-triple syntax — no reification node.

6

Reuse the ACID RDF Quad Store's named-graph provenance for audit compliance

Scope each trace's graph so provenance, department, and workflow context stay attached to every triple — the substrate the Aaron Levie weblog post names for auditable metrics.

7

Point the identical pattern at agent-rdf-memory itself

Mirror sessions/*.ttl and feedback-type preferences.ttl entries into Virtuoso named graphs via WebDAV PUT, then query them — decision traces and precedent retrieval, already running.

8

Author the mapping in R2RML first, generate (or hand-declare) the Quad Map from it

Write the canonical mapping as W3C R2RML, then generate the live Quad Map via OAI.DBA.R2RML_GENERATE_RDFVIEW where the account has execute privilege — falling back to a hand-declared, equivalent native mapping when it does not, as verified here.

9

Add a custom inference rule as both a materializing CONSTRUCT/INSERT and a live SELECT+BIND

Express a business rule over the ABox once as an INSERT...WHERE that writes the fact permanently, and once as a SELECT+BIND that computes the identical value at query time — a magic-predicate-style computed property using only standard SPARQL.

FAQ

Frequently Asked Questions

That AI agents behave like a 'black box': a decision gets made, but no one — not even the compliance officer who later questions it — can see the reasoning steps, tool calls, or facts that produced it. Decision traces make that reasoning queryable and auditable instead.

Long-term memory (enterprise knowledge), short-term memory (conversation history), and reasoning memory (decision traces) — all connected in one graph so an agent's reasoning stays linked to both the business facts and the conversation that produced it.

A support agent approves a refund outside the standard timeframe. A compliance officer later questions it but has no visibility into why. A decision trace records the refund request, the policy lookup tool call, the eligibility reasoning step, and the final approval as one connected, auditable structure — closing that visibility gap.

It maps three ordinary SQL tables (decision_trace, reasoning_step, tool_call) to RDF using Virtuoso RDF Views (Quad Map Patterns) — a zero-copy virtual graph. The tables can sit directly alongside an enterprise's existing relational data in the same engine, with SPARQL compiling back to SQL at query time.

PROV-O is a W3C Recommendation — a standard vocabulary for exactly 'who did what, using what, generating what.' An external tool or auditor that already understands PROV-O understands this trace data immediately, without first learning one SDK's bespoke schema.

A statement annotation attaches metadata (confidence, provenance, which reasoning step relied on it) directly to one specific fact-triple, as a quoted-triple subject, with no extra reification node. A property-graph relationship generally needs an added node to carry that same per-fact metadata.

With Virtuoso's integrated full-text search (bif:contains) over the same reasoning_step.thought column the RDF View exposes — in production, its vector/embedding search over the same column for semantic similarity — run in the identical quad store holding the trace facts, rather than a separate vector database.

The mapping syntax (ALTER QUAD STORAGE virtrdf:DefaultQuadStorage, IRI classes, the escaped self-join form) follows a pattern already fully verified live against a local Virtuoso 8.3.3335 instance in a prior session, for a structurally identical mapping in the sibling neo4j-ekl-operating-structure-vs-virtuoso collection. This document's own new tables were authored this session but not separately executed against a live database — the local dba credential is Keychain-gated and this session's tool-use safety classifier declined the read as a precaution.

It is live, not just analogous: each dated sessions/*.ttl entry records what the agent did and its outcome (a decision trace); each feedback-type memory records a corrected reasoning step with a Why: line (precedent retrieval's actual payoff) — both mirrored into named graphs on a local Virtuoso instance, queried at the start of every session.

The FDE/IRE Enterprise Skills, Lifecycle & Provenance Ontology post: a prov:Entity (tacit knowledge), transformed by a SkillGenerationActivity (a prov:Activity performed by a named agent), into a lifecycle-governed Skill artifact — the identical Entity-Activity-Entity shape this document reuses for decision traces, independent of and prior to Neo4j's post.

An ACID RDF Quad Store with named-graph provenance (the substrate for auditable, outcome-linked metrics) and integrated full-text/vector search (unstructured content indexed in the same engine as structured triples) — both directly reused here for storing and searching decision-trace data.

No — per its own GitHub repository, neo4j-labs/agent-memory is a Neo4j Labs project: actively maintained but not commercially supported, with APIs that may change; support runs through the Neo4j Community Forum and GitHub issues, not a formal SLA.

Vocabulary ownership. Neo4j's DecisionTrace/ReasoningStep/ToolCall schema belongs to one SDK; understanding it means learning that SDK. PROV-O belongs to the W3C; any RDF tool, auditor, or future system already speaks it, and Virtuoso's RDF Views let that same vocabulary sit directly over data an enterprise already has, with no new database to run.

R2RML is authored as the canonical W3C mapping (:r2rmlMapping), and Virtuoso ships procedures (OAI.DBA.R2RML_FROM_TABLES, OAI.DBA.R2RML_GENERATE_RDFVIEW) specifically to generate a live Quad Map from exactly this kind of document. Calling either one as the kidehen account on demo.openlinksw.com returned 'Access denied' — a grants gap on those two procedures, not a syntax or product-support failure — so the live Quad Map actually running was declared with Virtuoso's native ALTER QUAD STORAGE form instead, semantically equivalent to what the R2RML would generate.

Each one takes roughly 50-65 seconds on demo.openlinksw.com, because it triggers a server-side 'SPARQL compiler re-configured' step — not a hang. A client that gives up after a short timeout will wrongly conclude the statement failed; waiting it out confirms success ('IRI class <...> has been defined').

The working form, verified live, is the SPARQL 1.2 annotation shorthand: subject predicate object {| ann-predicate ann-object |} . The bare community RDF-star quoted-triple form, << subject predicate object >> ann-predicate ann-object ., parses but fails at INSERT time with an internal 'Bad argument to iri_to_id (), type 189' error on this instance — reproduced for literal and IRI objects alike, so it is a genuine engine finding, not a data-specific bug.

No — that would require a server-level C extension module, not something achievable through ordinary SQL/DDL in an iSQL session, and was not attempted. What is real and verified is the same computed-predicate PATTERN implemented with two entirely standard SPARQL mechanisms: an INSERT...WHERE rule that materializes the fact once, and a SELECT+BIND query that computes the identical value at read time. Both were run live against demo.openlinksw.com and returned matching results.

They serve different jobs. demo.openlinksw.com is where the SQL/SPASQL/RDF-View/inference/RDF-star work was actually executed and verified this session — but its own /sparql endpoint requires login for anonymous requests, so its sample-query links carry an explicit 'requires login' note. linkeddata.uriburner.com (URIBurner) hosts this document's own companion graph for the fully public SPARQL Workbench below, with zero login friction for any reader.

No — tested directly: the endpoint still returns 401 for anonymous requests after the grant. The 401 is a server-wide anonymous-access policy on this instance, not a per-table or per-graph permission gap, so the grant remains good practice (correctness for any account that does have login access) without changing anonymous reachability.

A genuine one is included: a single SQL statement whose derived table is a SPARQL block federating to DBpedia's own public endpoint via SPARQL-FED, joining this document's refund-4823 row with DBpedia's live description of Neo4j. VERIFIED LIVE against demo.openlinksw.com, both originally (2026-09-09) and re-verified 2026-09-14 — see the section above for that card's own account of a SPASQL Query Builder permalink found broken on 2026-09-14 (its editor never loads the linked query); run this one via isql or xmla-client instead.

No — found and confirmed broken 2026-09-14: the tool's permlink_e URL parameter is received but never applied, so its editor loads showing the tool's own default placeholder query rather than this document's query, on both the linkeddata.uriburner.com and demo.openlinksw.com deployments. The button was removed rather than left pointing at a tool that silently shows the wrong thing. The query itself is not at fault — independently re-verified via xmla-client the same day. Run it yourself via isql or xmla-client, or paste the shown text manually into the tool's Execute SQL tab.

Glossary

Terms

Decision trace

A structured record of one agent decision: its outcome, the reasoning steps that led there, and the tool calls each step relied on — kept queryable rather than left as a log line or an ephemeral chain-of-thought.

Context graph

Neo4j's term for a persistent memory system linking long-term enterprise knowledge, short-term conversation history, and reasoning memory (decision traces) in one graph.

Reasoning memory

The decision-trace layer of a context graph — distinct from long-term enterprise knowledge and short-term conversation history.

Precedent retrieval

Searching past decision traces for similar prior cases before deciding a new one — Neo4j's get_similar_traces; a full-text/vector search over the same quad store on Virtuoso.

RDF Views

Virtuoso's mechanism for exposing relational tables as a virtual RDF graph without copying data — SPARQL against an RDF View compiles to SQL and runs live.

PROV-O

The W3C provenance ontology (prov:Entity, prov:Activity, prov:Agent) used here to type decision-trace data instead of a vendor-specific node schema.

RDF-star / RDF 1.2

The RDF extension letting a triple itself (a 'quoted triple') be the subject or object of another statement — the mechanism behind statement annotations, now supported in Virtuoso per SPARQL 1.2.

Statement annotation

Metadata (confidence, provenance, which step relied on it) attached directly to one specific fact-triple, with no reification node minted for the base fact. Verified working syntax on Virtuoso: the SPARQL 1.2 annotation shorthand s p o {| ann-p ann-o |} . — the bare quoted-triple form << s p o >> ann-p ann-o . fails at insert time on this instance; see :statementAnnotationExample.

Zero-copy (virtual) graph

A graph exposed over existing data in place, with no ETL step and no duplicate copy to keep in sync — the property this document's RDF View shares with reusing an enterprise's existing relational tables.

Named graph

A labeled subset of a quad store's triples, addressable by its own IRI — the mechanism agent-rdf-memory's Virtuoso deployment uses to mirror each filesystem file, including each session and feedback memory, as its own graph.

Integrated full-text/vector search

Virtuoso's built-in text and embedding indexing over the same quad store holding structured triples — the substrate for precedent retrieval without a second, separately-run vector database.

Quad Map Pattern

Virtuoso's native syntax for declaring an RDF View — binding a SQL row to a subject IRI and its columns/joins to predicate-object pairs, functionally what R2RML later standardized.

R2RML

The W3C standard language for mapping relational databases to RDF, using the rr: vocabulary (rr:TriplesMap, rr:subjectMap, rr:predicateObjectMap). This document's canonical mapping; Virtuoso can generate a native Quad Map from it via OAI.DBA.R2RML_GENERATE_RDFVIEW where the account has execute privilege.

Magic predicate (computed property)

A predicate whose value is computed by a rule rather than read from a stored triple. Virtuoso's own built-in examples (bif:contains, bif:st_intersects) are compiled into the engine; this document's post:isDecisionJustified demonstrates the same computed-predicate pattern using standard SPARQL (INSERT...WHERE or SELECT+BIND) rather than a newly registered server extension.

Custom inference rule

A business rule (not RDFS/OWL subsumption) expressed declaratively in SPARQL — here, eligible AND windowDays<=30 implies isDecisionJustified — shown as a CONSTRUCT-style materializing INSERT and as a non-materializing SELECT+BIND query over the same facts.

SPASQL Query Builder (SPASQLQB)

A browser-based tool (linkeddata.uriburner.com/spasqlqb/) for composing and running SPASQL statements interactively, addressable by a permalink URL encoding the query as JSON. Requires login, same as demo.openlinksw.com's own /sparql endpoint — not an anonymous-clickable public tool.

Knowledge Graph Explorer 186 nodes · 293 links

Interactive graph visualization derived from the companion RDF. Click nodes to resolve, drag to explore. Graph data embedded from companion RDF at generation time.

Decision Traces — Neo4j Model, Virtuoso Rebuild, Agent Memory

Nodes: 0 Links: 0
Click SVG to activate zoom, click outside to release | Drag nodes to pin, double-click to unpin
Classes Properties Instances

Explore Knowledge Graph using SPARQL

Choose a query recipe, edit the SPARQL if needed, then open the encoded URIBurner query.

Run live query

SELECT uses text/x-html+tr. DESCRIBE and CONSTRUCT use text/x-html-nice-turtle, matching the SPARQL format guidance in the skill contract.