From fbb8e0d241772c4ba83a6b4ddc6cffb99544e211 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 26 Jul 2026 01:46:23 +0000 Subject: [PATCH] Document NPC semantic normalization --- docs/config.md | 20 ++++++++++---- docs/integrations/dnd-npc-artifacts.md | 15 ++++++++--- docs/internal/llm.md | 16 ++++++++++++ docs/internal/modules.md | 26 +++++++++++++++---- docs/internal/overview.md | 2 +- docs/internal/pipeline.md | 25 +++++++++++++++--- docs/operations.md | 16 ++++++++++++ .../roadmap/dnd-npc-semantic-normalization.md | 2 +- docs/roadmap/implementation.md | 2 +- examples/dnd-complete.config.yml | 5 +++- 10 files changed, 107 insertions(+), 22 deletions(-) diff --git a/docs/config.md b/docs/config.md index 9d0ee2e..6e2686f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -61,9 +61,9 @@ No pipelines are built in. A run requires a configured pipeline. If `scriptorium` is omitted, Notarius uses Scriptorium's built-in profile catalog. Prompt definitions may also name default profile IDs. The current D&D -scene and spell prompts default to the built-in `mistral-small-3` profile when a -module binding does not set `llm_profile`. That built-in profile reads its -credential from `OPENROUTER_API_KEY`. +scene, extraction, and NPC-normalization prompts default to `gemini-2-flash` +when a module binding does not set `llm_profile`. The selected built-in profile +reads its credentials from its Scriptorium profile definition. ## Scriptorium Profiles @@ -322,7 +322,8 @@ Binding fields: - `llm_profile`: optional Scriptorium profile ID. Empty or omitted lets the Scriptorium prompt default select the profile. - `retries`: non-negative retry count for extra runtime attempts after the - first attempt. Default: `0`. Supported on `chunk`, `extract`, `merge`, and + first attempt. Default: `0`, which permits one initial attempt and no + additional attempts. Supported on `chunk`, `extract`, `merge`, and `normalize` bindings. - `options`: optional module-specific settings. - `references`: optional reference bindings. Supported only for `chunk`, @@ -338,6 +339,15 @@ During resolution, each selected module's registered option validator runs. Production input, chunk, and output bindings reject unknown or invalid options with the affected binding context. +### NPC Semantic Normalization + +The `dnd/npcs` normalizer combines deterministic canonical-name consolidation +with a document-level LLM identity decision for eligible differently named NPC +records. Its `llm_profile` and `retries` use the ordinary `normalize` binding +fields above; `retries: 0` therefore still makes one normalization attempt. +The normalizer declares no references and accepts no options. Context-window +selection is current module policy, not configuration. + ### JSON Output Options The `json` output module accepts only `include_chunk_map`, a boolean that @@ -388,7 +398,7 @@ production validators do not call the LLM and must not set `llm_profile`. | merge | `appendorder` | Combines typed artifacts in chunk order. | | normalize | `noop` | Passes merged typed artifacts through unchanged. | | normalize | `dnd/spells` | Deterministically canonicalizes and de-duplicates typed D&D spell-list artifacts. | -| normalize | `dnd/npcs` | Deterministically consolidates typed D&D NPC-list artifacts by canonical name and unions exact evidence. | +| normalize | `dnd/npcs` | Deterministically prepares typed D&D NPC-list artifacts, then uses an LLM-assisted document-level identity decision to consolidate safe name-based matches and union exact evidence. | | normalize | `dnd/combat-turns` | Deterministically canonicalizes, orders, and de-duplicates typed D&D combat-turn artifacts. | | normalize | `dnd/item-events` | Deterministically trims, source-orders, and removes only exact D&D item-event duplicates. | | normalize | `dnd/npc-interactions` | Canonicalizes registry NPC names, orders interaction occurrences, and removes only exact duplicates. | diff --git a/docs/integrations/dnd-npc-artifacts.md b/docs/integrations/dnd-npc-artifacts.md index aad1ef2..79478c3 100644 --- a/docs/integrations/dnd-npc-artifacts.md +++ b/docs/integrations/dnd-npc-artifacts.md @@ -75,10 +75,17 @@ The production identities are: The extractor maps private model records to the current source identity and assigns deterministic IDs. Extraction validation checks shape, source -references, and source relatedness. The normalizer then consolidates records -only when their normalized canonical names match, preserves the first record's -display and output position, unions exact evidence, and validates the retained -registry's identity. No LLM is used for consolidation. +references, and source relatedness. The normalizer first consolidates equal +canonical-name matches, then may make one document-level LLM-assisted identity +decision per configured normalize attempt for eligible distinctly named +records. Consolidation is name-based: it retains a supplied canonical display +name, keeps the earliest affected output position, derives its ID again, and +unions exact evidence in canonical order. Ambiguous, invalid, or conflicting +proposals are not applied; independently safe matches may still be retained. +After the retry budget is exhausted, the safe result is accepted with bounded +normalization warnings and the usual validation. The durable v1 artifact shape +does not add aliases, proposal fields, or any other semantic-normalization +representation. The extraction prompt asks only for individually identifiable NPC names backed by source evidence. Groups, generic roles, invented labels, and descriptive or diff --git a/docs/internal/llm.md b/docs/internal/llm.md index 47b5525..d208888 100644 --- a/docs/internal/llm.md +++ b/docs/internal/llm.md @@ -154,6 +154,22 @@ identity, campaign-reference, and instruction messages are ephemeral cache boundaries; the transcript is last and has no cache control. Compatible shared messages remain canonical shared assets rather than copied package text. +### D&D NPC Normalization Prompt Ordering And Cache Boundaries + +NPC normalization has a distinct prompt and response-schema identity from NPC +extraction. Its stable message tiers are the common D&D system and identity +assets, followed by package-owned task and normalization instructions. Cache +boundaries follow the shared identity tier and the package instructions. The +variable tail contains the private candidate-name-and-range input and a +windowed transcript input whose cited units provide local context; neither has +a cache boundary because it changes with the document. + +This prompt intentionally omits extraction-evidence and campaign-reference +assets: it reconciles existing records rather than extracting events or adding +evidence. Its package-owned manifest and schema identity are fingerprinted +separately, so a normalization prompt or schema change cannot reuse a prior +normalization checkpoint. + Shared wording belongs in the canonical assets under `internal/modules/dnd/shared`; extraction packages reference those assets in their manifests instead of copying similar text into package-local files. diff --git a/docs/internal/modules.md b/docs/internal/modules.md index b11ee13..6f336f1 100644 --- a/docs/internal/modules.md +++ b/docs/internal/modules.md @@ -395,11 +395,27 @@ omission-summary warning when truncated. ### `internal/modules/dnd/normalize/npcs` -The NPC normalizer performs deterministic identity-aware consolidation in -merged input order. It consolidates only equal canonical-name comparison keys, -retains the first display record, and unions exact source references. It exposes -the identity policy as its local checkpoint fingerprint and emits bounded -normalization warnings. +The NPC normalizer deterministically trims display names, recomputes IDs, +canonicalizes evidence, and consolidates equal comparison keys before semantic +work. Records are eligible for the document-level identity call only when they +have a non-empty comparison key and wholly valid current-document references. +It sends private candidate names and source ranges plus coalesced, cited +transcript windows to its own prompt; stable NPC IDs and the durable artifact +shape are not prompt inputs. + +The private structured response proposes groups of supplied names and a +canonical supplied name. Deterministic comparison-key resolution validates each +group, discards unsafe or overlapping groups, and independently applies safe +ones. Application preserves earliest record order, unions canonical evidence, +and derives the final canonical ID. Invalid structured output and discarded +groups request framework retry with a safe fallback; bounded diagnostics become +durable only on final fallback exhaustion. + +The normalizer records prompt and response-schema identities and digests, +identity and normalization policies, and semantic-context policy and radius as +manifest metadata. Its local checkpoint fingerprints cover the prompt, response +schema, identity policy, normalization policy, and semantic-context policy so a +meaningful behavior change invalidates prior normalize reuse. ## Merger And Normalizer diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 287de7c..acd47c0 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -119,7 +119,7 @@ Configuration. The implemented module packages are: | `internal/modules/generic/merge/appendorder` | Combines accepted extraction results in chunk order. | | `internal/modules/generic/normalize/noop` | Preserves accepted merged output. | | `internal/modules/dnd/normalize/spells` | Canonicalizes catalog-backed spell names and exact source references, conservatively collapses duplicate casts, and reports deterministic warnings and independently scoped catalog checkpoint identity. | -| `internal/modules/dnd/normalize/npcs` | Consolidates NPC records deterministically by canonical name, unions exact evidence, and reports bounded warnings. | +| `internal/modules/dnd/normalize/npcs` | Deterministically prepares and safely applies document-level LLM-assisted NPC identity consolidation, preserving canonical evidence, order, and diagnostics. | | `internal/modules/generic/output/json` | Encodes manifests, lane payloads, warnings, rejections, and an explicitly enabled accepted chunk map as logical JSON files. | `internal/modules/dnd/shared` owns reusable D&D prompt fragments, diff --git a/docs/internal/pipeline.md b/docs/internal/pipeline.md index bb09765..4f6f248 100644 --- a/docs/internal/pipeline.md +++ b/docs/internal/pipeline.md @@ -322,9 +322,24 @@ error when attempts are exhausted. A rejection becomes a recorded `RejectedOutput` when attempts are exhausted. Cancellation stops retry processing immediately. -Rejected output is a non-fatal pipeline outcome and does not advance. Warnings -from discarded attempts are not promoted. Configuration owns retry counts and -validator overrides; see [Module Bindings](../config.md#module-bindings). +Structured-completion adapters classify malformed or undecodable provider +output with the provider-neutral `contracts.ErrInvalidStructuredOutput` error. +A typed normalizer may turn that condition, or another unsafe proposal, into a +normalize retry directive with a deterministic safe candidate, stable +diagnostic, and fallback warnings. The directive consumes the same configured +normalize retry budget: `retries` permits that many additional attempts after +the initial attempt. It neither creates a normalizer-local retry loop nor +records an accepted checkpoint for the discarded attempt. + +When a later normalize attempt succeeds, its candidate alone proceeds through +the usual validation and checkpoint path. When the final attempt still returns +a directive, the runner validates its supplied safe fallback through that same +normalizer validator chain before accepting or rejecting it. Ordinary +attempt-local warnings and fallback warnings remain unpromoted while another +attempt is available; only final exhaustion promotes the supplied fallback +warnings. Rejected output is a non-fatal pipeline outcome and does not advance. +Configuration owns retry counts and validator overrides; see +[Module Bindings](../config.md#module-bindings). ## Checkpoint And Debug Hooks @@ -389,7 +404,9 @@ boundaries. Every executed chunk, extract, merge, and normalize attempt writes one terminal envelope for acceptance, validator rejection, module or validator error, or applicable candidate or final serialization error. The envelope contains its attempt-local warnings, any available candidate and rejection, -and terminal error text; failures before a candidate exists omit that payload. +and terminal error text; normalize retry directives retain their attempt-local +candidate and diagnostic, while only the final safe fallback reaches validation. +Failures before a candidate exists omit that payload. Only LLM calls made by the module operation belong to the module attempt. Validator calls retain independent scopes under `validate/` and are not duplicated into the module envelope. A failed terminal-envelope write is a diff --git a/docs/operations.md b/docs/operations.md index c933a12..d50bbe8 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -86,6 +86,22 @@ workflow intentionally crosses a process or session boundary. Those files are validated against the consumer slot and must be protected as sensitive campaign data. They are not part of the maintained ordered handoff workflow. +### NPC Semantic Normalization + +Before the first step can release its accepted NPC artifact across the ordered +generated-reference barrier, `dnd/npcs` performs one document-level semantic +normalization call for each configured normalize attempt when eligible distinct +names remain. The normalize binding's `retries` setting controls additional +attempts. If an invalid or unsafe identity proposal exhausts that budget, the +run safely accepts the deterministic and any independently safe partial +consolidation, with a bounded warning; ordinary validation still applies before +the artifact can cross the barrier. + +An accepted normalized NPC checkpoint can be reused on `--resume` just like +other accepted normalize work. A changed normalization prompt, response schema, +or policy identity produces a cold cache miss, so the current reconciliation is +recomputed rather than silently reusing incompatible state. + ## Chunk-Plan Cache Chunk plans are stored at: diff --git a/docs/roadmap/dnd-npc-semantic-normalization.md b/docs/roadmap/dnd-npc-semantic-normalization.md index 41f108c..e7e4899 100644 --- a/docs/roadmap/dnd-npc-semantic-normalization.md +++ b/docs/roadmap/dnd-npc-semantic-normalization.md @@ -2,7 +2,7 @@ ## Status -Accepted scope; not implemented. +Implemented. ## Purpose diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 5b92b7e..a03e8a1 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -2,7 +2,7 @@ ## Status -Ready for implementation. +Completed. ## Objective diff --git a/examples/dnd-complete.config.yml b/examples/dnd-complete.config.yml index 78453cb..3c52ecf 100644 --- a/examples/dnd-complete.config.yml +++ b/examples/dnd-complete.config.yml @@ -43,7 +43,10 @@ pipelines: module: dnd/npcs retries: 2 merge: appendorder - normalize: dnd/npcs + normalize: + module: dnd/npcs + llm_profile: gemini-2-flash + retries: 2 scene-descriptions: extract: module: dnd/scene-descriptions