Files
lore-engine-poc/docs/CONSISTENCY_DEMO.md
kanban-dev 99535a8f3a docs(v2): T8 — update README + CHANGELOG + 3 worked-example docs
- README.md: 5 plugins / 19 tools (matches /healthz); 'what this proves'
  now lists consistency engine, multi-world namespace, LLM consumer;
  'next steps' section replaced with 'shipped in v2'
- docs/CONSISTENCY_DEMO.md: 4 tools, 5 violations, all output verified
  against live bash examples/test_consistency.sh
- docs/MULTI_WORLD_DEMO.md: list_worlds() + entity_context in both
  worlds + cross-world isolation tests, all output verified live
- docs/LLM_CONSUMER_DEMO.md: 5 question types, 9 distinct tools, all
  output traced to examples/results/*.json
- CHANGELOG.md: v1 -> v2 entry, all 9 task refs (T1-T9)
- examples/test_e2e.sh: T7 E2E validation script (untracked)
2026-06-17 00:45:30 +00:00

7.3 KiB
Raw Permalink Blame History

Consistency Engine — Worked Example

This is a live end-to-end run of the four consistency tools that landed in v2.T5. Everything below is real tool output from bash examples/test_consistency.sh against the current gateway at localhost:8765, taken from the v2 build (8261c2d on wt/t5-consistency-impl).

What the engine does

The consistency engine has four read-only tools, each backed by pre-materialized violation nodes in Neo4j. The seed (seed.py:seed_violations) computes the violations from the same heuristics the tools re-run defensively, so every violation id is stable, the math is visible in plain Python, and an operator can re-derive any flagged issue by hand from the seed.

Tool Neo4j label Live count (this run)
find_contradictions :Contradiction 1
find_anachronisms :Anachronism 1
find_orphans :Orphan 1
find_ontology_violations :OntologyViolation 2
Total 5

All four tools support an optional severity argument ("any", "error", "warn"), and the world-scoped read tools accept world_id="default". The default world contains the violations; the arda_greyscale world is clean (its seed doesn't inject any hand-crafted ones).

1. Contradictions — overlapping faction memberships

A :Contradiction is a pair of MEMBER_OF relations on the same person whose [valid_from, valid_until] windows overlap but whose target factions differ. It's the classic "sworn to two houses at once" case.

curl -s -X POST http://localhost:8765/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":1,"method":"tools/call",
    "params":{"name":"find_contradictions","arguments":{"world_id":"default"}}
  }'
{
  "violations": [
    {
      "id": "c_aldric_double_membership",
      "label": "Contradiction",
      "severity": "error",
      "status": "open",
      "details": "Aldric Raventhorne is MEMBER_OF House Vyr (240-) and MEMBER_OF Crimson Pact (260-285); the two memberships overlap.",
      "detected_at": "2026-06-16T23:04:51.238226Z"
    }
  ],
  "count": 1
}

The math: Aldric's MEMBER_OF House Vyr opens at year 240 with no end date. His MEMBER_OF Crimson Pact runs 260285. The two windows overlap from 260 to 285. He can't be a sworn member of both houses at once.

The seed source is seed.py:c_aldric_double_membership — see Aldric Raventhorne relations block in seed_world_default for the underlying MEMBER_OF rows.

2. Anachronisms — a person at an event before they were born

A :Anachronism is a :PARTICIPATED_IN (or similar) relation between a person and an event where event.in_fiction_time is before person.born.

curl -s -X POST http://localhost:8765/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":1,"method":"tools/call",
    "params":{"name":"find_anachronisms","arguments":{"world_id":"default"}}
  }'
{
  "violations": [
    {
      "id": "a_vex_at_founding",
      "label": "Anachronism",
      "severity": "error",
      "status": "open",
      "details": "Vex the Silent (born 180) is recorded as participating in the Founding of House Vyr (year 85) — 95 years before his birth.",
      "detected_at": "2026-06-16T23:04:51.238226Z"
    }
  ],
  "count": 1
}

Vex the Silent, born in 180, is tagged as a participant in the "Founding of House Vyr" event in year 85. The Cypher check joins the PARTICIPATED_IN edge to the person's born property and the event's in_fiction_time, extracted as an integer year.

3. Orphans — entities with no relations

A :Orphan is a Person/Item/Location/Event node that exists in the world but has zero outgoing or incoming relations of any kind. These are typically world-builder placeholders that haven't been wired into the story yet.

curl -s -X POST http://localhost:8765/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":1,"method":"tools/call",
    "params":{"name":"find_orphans","arguments":{"world_id":"default"}}
  }'
{
  "violations": [
    {
      "id": "o_unfinished_npc",
      "label": "Orphan",
      "severity": "warn",
      "status": "open",
      "details": "Person 'Lyssa the Watcher' exists but has no relations — world-builder placeholder, not yet connected.",
      "detected_at": "2026-06-16T23:04:51.238226Z"
    }
  ],
  "count": 1
}

Lyssa the Watcher is a real Person node in the seed (see seed.py:Lyssa the Watcher) with no PARENT_OF, MEMBER_OF, SPOUSE_OF, or any other relation. Note the severity: warn, not error — an unfinished NPC is a real artifact of worldbuilding, not a story-level inconsistency.

4. Ontology violations — rule-driven checks

A :OntologyViolation is a (:Person) node that fails an active :OntologyRule. Rules are themselves Neo4j nodes ((:OntologyRule)) with a predicate (a short Python expression) and a description. The consistency plugin runs each rule over the world and materializes a violation node for every person that fails it.

curl -s -X POST http://localhost:8765/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":1,"method":"tools/call",
    "params":{"name":"find_ontology_violations","arguments":{"world_id":"default"}}
  }'
{
  "violations": [
    {
      "id": "ov_maric_no_died",
      "label": "OntologyViolation",
      "severity": "warn",
      "status": "open",
      "details": "Person 'Maric Vyr' (born 85) has no death year; rule 'persons_born_before_280_must_die' applies.",
      "detected_at": "2026-06-16T23:04:51.238226Z",
      "entity_id": "maric",
      "rule_id": "persons_born_before_280_must_die"
    },
    {
      "id": "ov_theron_no_died",
      "label": "OntologyViolation",
      "severity": "warn",
      "status": "open",
      "details": "Person 'Theron Ashveil' (born 10) has no death year; rule 'persons_born_before_280_must_die' applies.",
      "detected_at": "2026-06-16T23:04:51.238226Z",
      "entity_id": "theron",
      "rule_id": "persons_born_before_280_must_die"
    }
  ],
  "count": 2
}

The rule persons_born_before_280_must_die is a world-builder convention: in the default world's narrative, anyone born before the Age of Iron (before year 280) must have a recorded death year, because the present day is well past 280 and a living person from the 1st Age is unprecedented. Maric (born 85) and Theron (born 10) are intentionally un-dead in the seed — they are long-lived lineages who are still alive in the present. The two violations are expected by the world-builder but flagged so the LLM (or operator) knows the rule is being broken.

How the seed side-stays the violation math

seed.py:seed_violations is the Python source of truth for what the tools return. Five pre-materialized violation nodes (one Con, one Ana, one Orph, two OV) get MERGE'd into the default world, and the tool Cypher queries read them back. If a tool query and the seed drift apart, the detection surface in seed.py is the one to trust; the queries are a defensive layer so a missing seed row doesn't silently hide a violation.

Files

  • plugins/consistency.py — the four tools
  • seed.py:seed_violations — the 5 hand-crafted violations
  • tests/test_consistency.py — 10 pytest cases
  • examples/test_consistency.sh — the live E2E runner that produced every block of output above