From 0ef89316978ebd191e3f1d99fdd4caaac7c07561 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 25 Aug 2026 19:40:44 +0000 Subject: [PATCH] Forward repair policy from D&D modules --- docs/roadmap/implementation.md | 2 +- internal/modules/dnd/chunk/scenes/chunker.go | 13 +++++++------ .../modules/dnd/extract/combatturns/extractor.go | 13 +++++++------ .../modules/dnd/extract/enemyevents/extractor.go | 13 +++++++------ .../dnd/extract/itemoccurrences/extractor.go | 2 +- .../modules/dnd/extract/itemregistry/extractor.go | 2 +- .../dnd/extract/locationoccurrences/extractor.go | 2 +- .../dnd/extract/locationregistry/extractor.go | 2 +- .../modules/dnd/extract/npcoccurrences/extractor.go | 13 +++++++------ .../modules/dnd/extract/npcregistry/extractor.go | 13 +++++++------ .../dnd/extract/scenedescriptions/extractor.go | 13 +++++++------ internal/modules/dnd/extract/spells/extractor.go | 13 +++++++------ .../dnd/normalize/itemregistry/normalizer.go | 2 +- .../dnd/normalize/locationregistry/normalizer.go | 2 +- .../modules/dnd/normalize/npcregistry/normalizer.go | 2 +- 15 files changed, 57 insertions(+), 50 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 6ba22ace..8f403f38 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -488,7 +488,7 @@ git diff --check calls. - Existing behavior remains unchanged while all values are nil. -## Stage 7: Forward Repair Policy From Every LLM-Backed Module +## Stage 7: Forward Repair Policy From Every LLM-Backed Module ✅ ### Goal diff --git a/internal/modules/dnd/chunk/scenes/chunker.go b/internal/modules/dnd/chunk/scenes/chunker.go index 3066e209..5aa19a26 100644 --- a/internal/modules/dnd/chunk/scenes/chunker.go +++ b/internal/modules/dnd/chunk/scenes/chunker.go @@ -95,12 +95,13 @@ func (c *Chunker) Plan(ctx context.Context, req contracts.ChunkRequest) (contrac } var response chunkResponse if _, err := c.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: ResponseSchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: shared.PromptInputs(req.SourceInput, req.References), + StageName: Key, + PromptID: PromptID, + PromptVersion: ResponseSchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: shared.PromptInputs(req.SourceInput, req.References), }, &response); err != nil { return contracts.ChunkPlanResult{}, chunkerErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/combatturns/extractor.go b/internal/modules/dnd/extract/combatturns/extractor.go index ebe955a5..38027ea5 100644 --- a/internal/modules/dnd/extract/combatturns/extractor.go +++ b/internal/modules/dnd/extract/combatturns/extractor.go @@ -209,12 +209,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe inputs := shared.PromptInputs(sourceInput, req.References) inputs[NPCRegistryReferenceSlot] = npcRegistry.PromptInput() if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: inputs, + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.CombatTurnList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/enemyevents/extractor.go b/internal/modules/dnd/extract/enemyevents/extractor.go index d3914a66..0f789c86 100644 --- a/internal/modules/dnd/extract/enemyevents/extractor.go +++ b/internal/modules/dnd/extract/enemyevents/extractor.go @@ -143,12 +143,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe } var response extractionResponse if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: inputs, + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.EnemyEventList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/itemoccurrences/extractor.go b/internal/modules/dnd/extract/itemoccurrences/extractor.go index b61fc580..6db36d93 100644 --- a/internal/modules/dnd/extract/itemoccurrences/extractor.go +++ b/internal/modules/dnd/extract/itemoccurrences/extractor.go @@ -157,7 +157,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe inputs[ItemRegistryReferenceSlot] = registry.PromptInput() if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ StageName: Key, PromptID: PromptID, PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, SessionID: req.SessionID, Inputs: inputs, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.ItemOccurrenceList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/itemregistry/extractor.go b/internal/modules/dnd/extract/itemregistry/extractor.go index ca6a57e4..6a26ae36 100644 --- a/internal/modules/dnd/extract/itemregistry/extractor.go +++ b/internal/modules/dnd/extract/itemregistry/extractor.go @@ -111,7 +111,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe var response extractionResponse if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ StageName: Key, PromptID: PromptID, PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, SessionID: req.SessionID, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, Inputs: shared.PromptInputs(sourceInput, req.References), }, &response); err != nil { return contracts.TypedExtractionResult[dnd.ItemRegistry]{}, extractorErrorf("complete structured output: %w", err) diff --git a/internal/modules/dnd/extract/locationoccurrences/extractor.go b/internal/modules/dnd/extract/locationoccurrences/extractor.go index eaa1e564..9c93f7bd 100644 --- a/internal/modules/dnd/extract/locationoccurrences/extractor.go +++ b/internal/modules/dnd/extract/locationoccurrences/extractor.go @@ -153,7 +153,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe inputs[LocationRegistryReferenceSlot] = grounding.PromptInput() if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ StageName: Key, PromptID: PromptID, PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, SessionID: req.SessionID, Inputs: inputs, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.LocationOccurrenceList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/locationregistry/extractor.go b/internal/modules/dnd/extract/locationregistry/extractor.go index 18e0b359..2c1a1e2a 100644 --- a/internal/modules/dnd/extract/locationregistry/extractor.go +++ b/internal/modules/dnd/extract/locationregistry/extractor.go @@ -111,7 +111,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe var response extractionResponse if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ StageName: Key, PromptID: PromptID, PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, SessionID: req.SessionID, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, Inputs: shared.PromptInputs(sourceInput, req.References), }, &response); err != nil { return contracts.TypedExtractionResult[dnd.LocationRegistry]{}, extractorErrorf("complete structured output: %w", err) diff --git a/internal/modules/dnd/extract/npcoccurrences/extractor.go b/internal/modules/dnd/extract/npcoccurrences/extractor.go index 2d415324..3bba2a1d 100644 --- a/internal/modules/dnd/extract/npcoccurrences/extractor.go +++ b/internal/modules/dnd/extract/npcoccurrences/extractor.go @@ -160,12 +160,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe inputs := shared.PromptInputs(sourceInput, req.References) inputs[NPCRegistryReferenceSlot] = npcRegistry.PromptInput() if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: inputs, + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.NPCOccurrenceList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/npcregistry/extractor.go b/internal/modules/dnd/extract/npcregistry/extractor.go index 7b04e85e..a1b5e04b 100644 --- a/internal/modules/dnd/extract/npcregistry/extractor.go +++ b/internal/modules/dnd/extract/npcregistry/extractor.go @@ -118,12 +118,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe var response extractionResponse if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: shared.PromptInputs(sourceInput, req.References), + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: shared.PromptInputs(sourceInput, req.References), }, &response); err != nil { return contracts.TypedExtractionResult[dnd.NPCRegistry]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/scenedescriptions/extractor.go b/internal/modules/dnd/extract/scenedescriptions/extractor.go index 6188fd1d..404181b9 100644 --- a/internal/modules/dnd/extract/scenedescriptions/extractor.go +++ b/internal/modules/dnd/extract/scenedescriptions/extractor.go @@ -120,12 +120,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe var response extractionResponse if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: shared.PromptInputs(sourceInput, req.References), + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: shared.PromptInputs(sourceInput, req.References), }, &response); err != nil { return contracts.TypedExtractionResult[dnd.SceneDescriptionList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/extract/spells/extractor.go b/internal/modules/dnd/extract/spells/extractor.go index 94f0dcf2..5cfe36ec 100644 --- a/internal/modules/dnd/extract/spells/extractor.go +++ b/internal/modules/dnd/extract/spells/extractor.go @@ -183,12 +183,13 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.TypedExtractionRe inputs[spellcatalog.SpellCatalogReferenceSlot] = e.catalogPromptInput.Clone() inputs[NPCRegistryReferenceSlot] = npcRegistry.PromptInput() if _, err := e.llm.CompleteStructured(ctx, contracts.StructuredCompletionRequest{ - StageName: Key, - PromptID: PromptID, - PromptVersion: SchemaVersion, - ProfileID: req.LLMProfile, - SessionID: req.SessionID, - Inputs: inputs, + StageName: Key, + PromptID: PromptID, + PromptVersion: SchemaVersion, + ProfileID: req.LLMProfile, + SessionID: req.SessionID, + StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, + Inputs: inputs, }, &response); err != nil { return contracts.TypedExtractionResult[dnd.SpellList]{}, extractorErrorf("complete structured output: %w", err) } diff --git a/internal/modules/dnd/normalize/itemregistry/normalizer.go b/internal/modules/dnd/normalize/itemregistry/normalizer.go index 9b236f50..86ac4bc8 100644 --- a/internal/modules/dnd/normalize/itemregistry/normalizer.go +++ b/internal/modules/dnd/normalize/itemregistry/normalizer.go @@ -119,7 +119,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize } reconciliation, err := n.engine.Reconcile(ctx, semanticreconcile.Request{ StageName: Key, Source: req.Source, Candidates: candidates, - ProfileID: req.LLMProfile, SessionID: req.SessionID, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, }) if err != nil { return contracts.TypedNormalizeResult[dnd.ItemRegistry]{}, normalizerErrorf("reconcile semantic duplicates: %w", err) diff --git a/internal/modules/dnd/normalize/locationregistry/normalizer.go b/internal/modules/dnd/normalize/locationregistry/normalizer.go index 0324bb0a..83c547c6 100644 --- a/internal/modules/dnd/normalize/locationregistry/normalizer.go +++ b/internal/modules/dnd/normalize/locationregistry/normalizer.go @@ -120,7 +120,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize } reconciliation, err := n.engine.Reconcile(ctx, semanticreconcile.Request{ StageName: Key, Source: req.Source, Candidates: candidates, - ProfileID: req.LLMProfile, SessionID: req.SessionID, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, }) if err != nil { return contracts.TypedNormalizeResult[dnd.LocationRegistry]{}, normalizerErrorf("reconcile semantic duplicates: %w", err) diff --git a/internal/modules/dnd/normalize/npcregistry/normalizer.go b/internal/modules/dnd/normalize/npcregistry/normalizer.go index d2c55911..56e40fa3 100644 --- a/internal/modules/dnd/normalize/npcregistry/normalizer.go +++ b/internal/modules/dnd/normalize/npcregistry/normalizer.go @@ -119,7 +119,7 @@ func (n *Normalizer) Normalize(ctx context.Context, req contracts.TypedNormalize } reconciliation, err := n.engine.Reconcile(ctx, semanticreconcile.Request{ StageName: Key, Source: req.Source, Candidates: candidates, - ProfileID: req.LLMProfile, SessionID: req.SessionID, + ProfileID: req.LLMProfile, SessionID: req.SessionID, StructuredOutputRepairAttempts: req.StructuredOutputRepairAttempts, }) if err != nil { return contracts.TypedNormalizeResult[dnd.NPCRegistry]{}, normalizerErrorf("reconcile semantic duplicates: %w", err)