Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Episode Ingestion

Implementation status (2026-07-23, kelly): ✅ Implemented. src/episode/mod.rs — ingest_episode, ingest_episode_with_resolution, ingest_batch, the Episode struct with group_id/shapes, prov:Activity/prov:wasGeneratedBy provenance, and edge-confidence reification (bare triple when absent; rdf:Statement + quipu:confidence for enum/numeric grades — tested in src/episode/tests.rs). SHACL episode + persistent shape gates and the quipu episode CLI are wired. Verified by grep + tests.

Episodes are the structured write path for agent-extracted knowledge. When an agent observes something – a deploy, an incident, a configuration change – it packages that knowledge as an episode with typed nodes and edges.

Anatomy of an Episode

{
  "name": "koror-rebuild-2026-03-29",
  "episode_body": "Koror was rebuilt on kota after disk failure",
  "source": "aegis/crew/ellie",
  "group_id": "aegis-ontology",
  "nodes": [
    {
      "name": "koror",
      "type": "ProxmoxNode",
      "description": "Proxmox host, rebuilt after failure",
      "properties": { "hostname": "koror.example", "status": "recovered" }
    },
    {
      "name": "kota",
      "type": "ProxmoxNode",
      "description": "Primary Proxmox host"
    }
  ],
  "edges": [
    {
      "source": "koror",
      "target": "kota",
      "relation": "rebuilt_on"
    }
  ]
}

Fields

FieldRequiredDescription
nameYesEpisode identifier (becomes IRI)
episode_bodyNoNatural language description
sourceNoAgent or system that produced this
group_idNoProvenance label for the episode (see note below)
nodesNoEntities to create
edgesNoRelationships between entities
shapesNoSHACL Turtle for validation gate
replace_snapshotNoReplace this episode’s prior facts atomically (default: false)

Ordinary episodes are additive: changing an episode does not retract entities that were generated by its earlier version. A producer publishing a complete current inventory should set replace_snapshot: true. Quipu then retracts facts missing from the new inventory and asserts new facts in one transaction. An empty nodes/edges payload therefore represents an empty snapshot, and readers never observe a retract-then-repost gap.

group_id is a label, not a partition. It is recorded as a single aegis:groupId literal on the episode’s prov:Activity; nodes inherit it transitively via prov:wasGeneratedBy. All groups share one graph — there is no isolation boundary, and the group_ids filter on search is best-effort. Facts asserted directly via /knot (raw Turtle, not through an episode) carry no group at all and are invisible to group_ids filtering. Use group_id for provenance/organization, not as a multi-tenant security boundary.

Node Fields

FieldRequiredDescription
nameYesEntity identifier
typeNordf:type (e.g., “ProxmoxNode”)
descriptionNordfs:comment
propertiesNoKey-value pairs as typed literals

Edge Fields

FieldRequiredDescription
sourceYesSource entity name
targetYesTarget entity name
relationYesRelationship name (becomes predicate)
confidenceNoConfidence qualifier for the generated triple (see below)

Edge confidence. Supply confidence to grade how trustworthy an AUTO-extracted edge is — either an enum ("EXTRACTED", "INFERRED", "AMBIGUOUS") or a 0–1 number. The bare triple is always asserted; when a confidence is present the statement is additionally reified (rdf:Statement with rdf:subject/rdf:predicate/rdf:object) and qualified with quipu:confidence, so agents can filter or flag uncertain facts via SPARQL. Omitting it (the common case) leaves an unqualified triple — fully back-compatible.

# All edges an agent flagged as merely inferred
SELECT ?s ?p ?o WHERE {
  ?stmt rdf:subject ?s ; rdf:predicate ?p ; rdf:object ?o ;
        quipu:confidence "INFERRED" .
}

How Ingestion Works

  1. Episode JSON is converted to RDF Turtle
  2. If shapes is provided, SHACL validation runs first – rejects on failure
  3. Turtle is ingested via the standard RDF pipeline in a single transaction
  4. Episode node gets prov:Activity type with provenance links
  5. Each entity gets prov:wasGeneratedBy linking back to the episode

Provenance Tracking

Every entity created by an episode carries a provenance link:

SELECT ?entity WHERE {
  ?entity <http://www.w3.org/ns/prov#wasGeneratedBy>
          <http://aegis.gastown.local/ontology/episode/koror-rebuild-2026-03-29>
}

The episode_provenance() function returns all entities and their facts for a given episode name.

Description Revisions

A current entity carries one rdfs:comment. When an ordinary (additive) episode supplies a different description for an existing node, the superseded text is not silently discarded and not left to pile up on the entity: in the same transaction, each superseded comment is retracted from the entity and re-asserted on the episode entity whose prov:wasGeneratedBy assertion shared its original transaction (src/episode/descriptions.rs). Last write wins on the entity; the history stays lossless because every prior description remains readable on the episode that produced it.

Two edges of that rule:

  • Unattributable history refuses the whole write. If a current comment has no same-transaction prov:wasGeneratedBy attribution, the ingest errors before the transaction opens rather than discarding provenance. One revision migrates every attributable legacy comment it supersedes.
  • The reconciliation is per (entity, new text) — a node listed more than once in an episode (for example under multiple types) produces one revision, not duplicate retractions.

SHACL Validation Gate

Episodes can include a shapes field with SHACL constraints. If provided, the episode data is validated before writing – invalid episodes are rejected with structured feedback explaining exactly what failed.

{
  "name": "new-service",
  "shapes": "@prefix sh: ... (SHACL Turtle)",
  "nodes": [{ "name": "myapp", "type": "WebApplication" }]
}

CLI Usage

# From file
quipu episode deploy.json --db my.db

# From stdin (pipe from another tool)
echo '{"name": "test", "nodes": [...]}' | quipu episode - --db my.db

Batch Ingestion

Multiple episodes can be ingested sequentially. Processing stops on the first error, so earlier episodes are committed while later ones may not be.

#![allow(unused)]
fn main() {
use quipu::episode::ingest_batch;

let results = ingest_batch(&mut store, &episodes, &timestamps)?;
for (tx_id, count) in results {
    println!("tx={tx_id}, triples={count}");
}
}