Moves the 'Dev Notes' sections from the lore-engine-merge PRD stories
(S2-phase-1-substrate-merge.md, S3-phase-2-ontology-time-planes.md)
into lore-engine-poc as documentation, since the lore-engine-merge
codebase is not yet bootstrapped as a separate working repo.
These notes document decisions made during the planned worker migration
from GraphMCP into the lore-engine stack:
- port remap 8765 → 8766 (damascus stack conflict)
- nsc plugin as single-file to match existing plugin convention
- discord-connector disabled via env var (per PRD ambiguity)
- multi-world ontology migration with defensive cleanup
When the lore-engine-merge codebase is bootstrapped, these notes can
be moved into docs/decisions/ there.
Ports the GraphMCP-Example substrate into lore-engine-poc:
- 8 Go workers under workers/ (discord-connector, discord-filter, lore-watcher, ingestion-worker, entity-extractor, lore-extractor, encounter-processor, mcp-server), each with Dockerfile + go.mod
- 3 Go unit-test files (encounter-processor, ingestion-worker, lore-extractor) — other 5 workers rely on integration tests via the live stack
- plugins/nsc.py: thin httpx proxy from gateway to lore-mcp-server:9000, exposes all 11 inherited GraphMCP tools (input schemas verbatim from mcp-server/main.go)
- docker-compose.yml: adds lore-redis + lore-mcp-server + the 7 worker services (lore- prefix to avoid clash with other GraphMCP stacks)
- verify-merge.sh (171 LOC, 7 pass conditions) + docs/VERIFICATION.md
- tests/contract/test_graphmcp_tool_contracts.py (15 tests; skipped when stack is down — TDD pattern, becomes active once docker compose up brings the stack)
- README.md + test.sh updated for the merged service inventory
Leader notes (2026-06-27 03:50):
- Worker self-blocked review-required after 2 runs (run #7 hit 120/120 iteration budget; run #8 staged 40 files and reported shippable).
- Tests are SKIPPED until docker compose up — worker chose that pattern over mocking (consistent with the lore-engine-poc project convention). To activate, run `docker compose up -d --build && pytest tests/contract/`.
- File Scope reconciliation: story said gateway/plugins/nsc/__init__.py; worker shipped plugins/nsc.py (flat file). Justified by the existing plugins/ convention in lore-engine-poc (server.py glob("*.py")). A future PR could split nsc into a package once server.py learns __init__.py discovery.
- nsc plugin exposes 11 tools (not 8) — the AC said "8" but the worker enumerated all 11 tools present in mcp-server/main.go. The encounter-specific 3 tools (list_encounters, search_encounters, get_encounter) were included for consistency. Story AC #2 reads "≥ 8 GraphMCP tools" so this exceeds AC.
Refs: S2-phase-1-substrate-merge, milestone #64 P1 — Substrate merge
Worker shipped 20/20 tests green on first run. Inventory catalogs all 10 Go workers, 11 MCP tools, 4 Redis streams from the pinned GraphMCP-Example substrate (commit 064daa9). Closes story S1-phase-0-inventory.
Phase 0 of the lore-engine × GraphMCP merge (gate story S1).
- docs/merge/00-inventory.md: canonical catalog of every worker (10),
MCP tool (11), and Redis stream (4) in the GraphMCP-Example substrate
pinned at commit 064daa9. Each row includes env vars, streams read/
written, Cypher queries emitted, LLM call sites, and source line refs
in services/<worker>/main.go. Under the 500-line budget (450 lines).
- tests/test_inventory_completeness.py: TDD gate. 20 tests covering
existence, line budget, name coverage, required attribute coverage,
source path accuracy against the pinned checkout, and bidirectional
cross-links. RED→GREEN: test_inventory_doc_exists failed with
FileNotFoundError before the doc was written; all 20 pass now.
- meta/prd.md + planning-artifacts/architecture.md: mirrored from the
lore-engine-merge-prds repo with a 'Phase 0' index link back to
00-inventory.md appended, satisfying the cross-link acceptance
criterion in the story.
Acceptance criteria from S1-phase-0-inventory.md: all 7 met.
Refs: lore-engine-merge-prds/_bmad-output/planning-artifacts/stories/S1-phase-0-inventory.md
This merge also brings wt/t6-multi-world (4f92289) since T5 was rebased
on top of T6's namespace work.
Conflicts resolved (always took theirs — the implementation supersedes
the stub):
- README.md: updated 'What's not in this POC' to reflect that T5
detection is now real, and marked T5 done in 'Next steps'
- plugins/consistency.py: T5's full 254-line implementation replaces
T1's 153-line stub (find_contradictions / find_anachronisms /
find_orphans / find_ontology_violations all backed by real Cypher)
Resolved conflicts in neo4j/init.cypher and seed.py by taking theirs:
- neo4j/init.cypher: T2's version adds severity/status indexes on all 4
violation types (T1 had them on Contradiction/Anachronism only)
- seed.py: T2 adds seed_embeddings() function for pgvector backfill
on first run; HEAD removed it accidentally during T1 merge
The 4 tools (find_contradictions, find_anachronisms, find_orphans,
find_ontology_violations) now read pre-materialized violation nodes
from Neo4j, populated by seed.py:seed_violations. The seed computes
the 5 hand-crafted violations from the same heuristics the design
calls for (overlapping MEMBER_OF windows, Person.born > event year,
orphaned entities, OntologyRule-driven checks) so the math is
visible in plain Python — not hidden in Cypher.
* plugins/consistency.py: 4 tools fully implemented; _severity_where
helper moves the WHERE BEFORE the OPTIONAL MATCH in the ontology
query (trailing WHERE on OPTIONAL MATCH rolls the optional row
back to null when the predicate doesn't match, which broke the
severity filter).
* seed.py: 5 violations pre-materialized (1 contradiction, 1
anachronism, 1 orphan, 2 ontology) + 1 OntologyRule
(persons_born_before_280_must_die). Rule id was normalized from
'persons-born-before-280-must-die' to underscored form so it
parses cleanly as a node id.
* examples/test_consistency.sh: 10 assertions across 4 tools
(severity filter variants), exits 0.
* tests/test_consistency.py: 10 pytest cases — envelope shape,
per-tool counts, severity filter, OntologyRule node presence.
* README.md: T5 marked done.
Verification:
pytest tests/test_consistency.py 10/10 PASS
bash examples/test_consistency.sh 10/10 assertions, exit 0
bash test.sh no regressions, exit 0
Every read tool (entity_context, state_at, was_true_at, ancestors_of,
descendants_of, lineage_of, recall_images, search_images_by_caption,
register_image) now accepts an optional world_id parameter, defaulting
to 'default' so v1 callers see no change.
* Neo4j: every Person/Faction/Location/Item/Event/Lineage/Image node
carries a world_id string property. seed.py backfills existing nodes
to 'default' and writes a second minimal world 'arda_greyscale'
(2 people, 1 faction, 1 location, 4 relations, 1 event, 1 era).
* Cypher: every MATCH in the world/lineage/images plugins filters by
world_id on the right node type.
* New admin tool list_worlds() — runs
MATCH (n) WHERE n.world_id IS NOT NULL RETURN DISTINCT n.world_id
to expose the namespace.
* Postgres image_manifest gains a world_id column (init.sql).
* 4 placeholder images generated for the greyscale world (portraits of
Mael and Sira, Ashen Hall, the Ashen Oath).
* test.sh now calls every tool with world_id='default' to verify v1
behaviour, plus a new section 12 that calls list_worlds().
* tests/test_multi_world.py: 14 pytest cases covering list_worlds,
entity_context/world isolation, was_true_at, state_at, lineage
filter, image recall isolation, and the image_manifest schema.
Verification:
pytest tests/test_multi_world.py 14/14 pass
bash test.sh 12/12 sections green,
MinIO image bytes flow 200 OK
list_worlds() returns [{arda_greyscale}, {default}]
- examples/llm_consumer.py: raw httpx + urllib driver — discovers tools
via tools/list, runs the tool-use loop against LiteLLM (minimax-m3), saves
per-question JSON traces. No agent framework per task scope.
- examples/system_prompt.txt: 5 question types + tool protocol (per
lore-engine/docs/07-reasoning-harness.md).
- examples/run_questions.sh: bash driver — exits 0 iff all 5 questions pass
hand-verified correctness against the seed data.
- examples/results/*.json: traces from a real end-to-end run, all 5 PASS.
- examples/REPORT.md: per-question ground truth vs answer, with tool-call
audit. The model used 9 distinct tools across 5 questions (requirement
was >=4); every factual claim is grounded in a tool result; no
fabrication.
T1 deliverables for the v2 iteration:
- .gitignore — keep __pycache__, .smoke-*.log, and editor noise out
- docs/SMOKE.md — single source of truth for bringing up the v1 stack
from a fresh clone. Documents the 5-command path, expected output at
each step, the CI runner, and a troubleshooting table.
- scripts/ci-smoke.sh — CI smoke runner. Brings the stack up, waits for
all four services healthy, polls /healthz, idempotently seeds (skip
if data already present), runs test.sh. Exits 0 on pass. Supports
--keep-up and --skip-build for dev iteration. Shell-only because the
Gitea instance has no Actions runner wired up yet — the script is
the same shape a future Actions step will wrap.
Gitea milestones T1-T9 are created on kaykayyali/lore-engine-poc via
'tea milestones create'. See docs/SMOKE.md for the milestone → task
mapping (T7, T8 are deferred per the T9 integration task's body).
Verification:
- ./scripts/ci-smoke.sh --skip-build --keep-up → SMOKE PASSED
(12 v1 tools registered, 11/11 test.sh sections green)
- tea milestones list → all 9 milestones present
Co-located with the v1 baseline commit already on wt/t1-gitea-push
(commit 8e8503e adds the T3 consistency-skeleton plugin on top of v1;
that is T3's work, surfaced here only because T1, T2, T3 share a
single working dir). The T1 deliverables are additive: SMOKE.md,
ci-smoke.sh, .gitignore.
- plugins/consistency.py: find_contradictions, find_anachronisms, find_orphans,
find_ontology_violations — all stub queries returning the
{violations, count} envelope, ready for T5 to populate violation nodes.
Two of the tools accept a severity filter (any|critical|major|minor).
- neo4j/init.cypher: uniqueness constraints on Contradiction, Anachronism,
Orphan, OntologyViolation (id) + severity/status indexes on the
contradiction/anachronism labels.
- README.md: bump plugin list to five, replace 'no consistency engine' copy
with 'consistency engine is a stub', drop the two T3 bullet points from
the next-steps section.
Verification: /healthz lists 18 tools (was 14), all 4 new tools return
{"violations": [], "count": 0}, full test.sh passes.