Improve semantic reconciliation retries
This commit is contained in:
76
docs/adr/0016-feedback-aware-module-requested-retries.md
Normal file
76
docs/adr/0016-feedback-aware-module-requested-retries.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# ADR-0016: Use feedback-aware module-requested retries
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-08-28
|
||||
|
||||
## Context
|
||||
|
||||
An LLM-backed module can discover that a structurally valid model response is
|
||||
unsafe while translating it into a typed candidate, before the ordinary
|
||||
validator chain runs. Semantic registry reconciliation is the first such case:
|
||||
the shared core can identify invalid duplicate-group proposals, and typed
|
||||
application can reject a domain-incompatible group while preserving a safe
|
||||
partial result. Repeating the original request without the rejected response or
|
||||
corrective explanation gives the model no information with which to improve.
|
||||
|
||||
The existing feedback-aware validation mechanism already establishes the safe
|
||||
correction protocol, but module-owned retry directives also carry internal
|
||||
reason codes, operator messages, and fallback diagnostics. Those values are not
|
||||
model instructions. Some module retry conditions, including exhausted
|
||||
structured-output repair, also have no valid exact response to append.
|
||||
|
||||
## Decision
|
||||
|
||||
An LLM-backed normalizer may attach optional, bounded model-facing correction
|
||||
guidance to a module-requested retry. Guidance is a separate contract field; the
|
||||
framework never derives it from a reason code, operator message, diagnostic, or
|
||||
error. A feedback-capable directive must include the exact model candidate that
|
||||
controlled the safe fallback and must use `single_response_v1`.
|
||||
|
||||
The artifact-neutral producer-attempt state machine constructs the correction
|
||||
from that exact latest response and the supplied guidance. The producer rebuilds
|
||||
its complete ordinary request and appends the response as one assistant message
|
||||
and the guidance as one user message. Earlier attempts do not accumulate, and
|
||||
the attempt consumes the same configured stage retry budget as operational,
|
||||
structural, validation, and feedback-free module retries.
|
||||
|
||||
A module retry without guidance remains valid and starts a fresh attempt. This
|
||||
is the required behavior when no valid model candidate exists, including after
|
||||
structured-output repair is exhausted. If feedback is supplied without a valid
|
||||
supported candidate, the framework fails the module contract rather than
|
||||
retrying blindly or inventing guidance.
|
||||
|
||||
After retry exhaustion, the normalizer's safe fallback continues through its
|
||||
validator chain. Safe semantic groups may remain applied, unsafe groups remain
|
||||
separate, and bounded fallback diagnostics may explain the process degradation.
|
||||
Exact responses and correction text remain attempt-local and are excluded from
|
||||
ordinary errors, warnings, manifests, receipts, caches, and checkpoints.
|
||||
|
||||
This decision extends, rather than supersedes,
|
||||
[ADR-0014](0014-feedback-aware-validation-retries.md): both correction sources
|
||||
use the same transport-neutral payload, replacement-request semantics, outer
|
||||
retry budget, and sensitive-data boundary.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Continue blind module retries. This preserves a smaller contract but wastes
|
||||
the module's deterministic diagnosis and commonly repeats the same defect.
|
||||
- Convert module safety checks into validators. Typed reconciliation must apply
|
||||
only safe proposal groups and retain a fallback before validation; moving
|
||||
artifact-owned translation and application policy into validators would blur
|
||||
stage ownership.
|
||||
- Copy the retry reason or operator message into the model request. Those values
|
||||
are written for provenance and humans, can contain opaque internal labels,
|
||||
and do not reliably describe a correct replacement.
|
||||
- Require feedback for every module retry. Structural failures may have no
|
||||
valid exact candidate, so this would either prevent useful fresh retries or
|
||||
fabricate prior-response material.
|
||||
|
||||
## Consequences
|
||||
|
||||
The normalize retry contract and generic producer-attempt directive gain an
|
||||
optional correction-guidance field and candidate-pairing validation. Modules
|
||||
that use it must provide semantically meaningful bounded prose and an exact
|
||||
candidate. Registry reconciliation maintains separate operator and model
|
||||
renderers, and policy fingerprints change so checkpoints created under blind
|
||||
retry behavior are not reused.
|
||||
@@ -29,13 +29,17 @@ The D&D registrar registers the family’s artifact codecs, extractors, typed
|
||||
append-order mergers, normalizers, validators, prompt assets, fallback LLM
|
||||
profile asset, and default validator chains. Each extractor and normalizer has
|
||||
a stable module spec, explicit execution class, strict option decoding, and a
|
||||
typed builder. Scene chunking, every extractor, and NPC, location, and item-registry
|
||||
normalization are registered as `llm_backed`; the remaining current D&D mergers
|
||||
and normalizers are `deterministic`. The metadata is available to catalog inspection and
|
||||
resolved-pipeline debug data and determines which selected bindings inherit the
|
||||
pipeline profile. The registry normalizers use `single_response_v1`, forwarding
|
||||
corrections to their reconciliation completion and retaining the accepted raw
|
||||
proposal only as an owned model candidate. Configuration remains the canonical owner of the exact keys,
|
||||
typed builder. Scene chunking, every extractor, and NPC, location, and
|
||||
item-registry normalization are registered as `llm_backed`; the remaining
|
||||
current D&D mergers and normalizers are `deterministic`. The metadata is
|
||||
available to catalog inspection and resolved-pipeline debug data and determines
|
||||
which selected bindings inherit the pipeline profile. The registry normalizers
|
||||
use `single_response_v1`, forwarding corrections to their reconciliation
|
||||
completion and retaining the accepted raw proposal only as an owned model
|
||||
candidate. When deterministic proposal safety or typed application rejects a
|
||||
group, they provide separate model-facing prose for a corrective module retry;
|
||||
internal issue categories, reason codes, and operator messages remain
|
||||
diagnostic-only. Configuration remains the canonical owner of the exact keys,
|
||||
profile precedence, and validator order.
|
||||
|
||||
Private structured-LLM response schemas are deliberately minimal. They reject
|
||||
@@ -117,6 +121,17 @@ presentation, and final ephemeral generic transcript windows. These orders and
|
||||
cache controls are prompt behavior; change them only through the owning
|
||||
manifest and prompt declaration.
|
||||
|
||||
NPC, item, and location registry reconciliation translate shared proposal
|
||||
safety categories into bounded prose that references only the response-local
|
||||
duplicate-group ordinals and candidate handles. Item reconciliation appends its
|
||||
typed rule that currency may be consolidated only with aliases of the same
|
||||
denomination and never with non-currency items. The next normalize attempt
|
||||
receives that prose with the exact defective proposal under the shared
|
||||
replacement-request protocol. Structurally invalid output has no valid proposal
|
||||
candidate and receives a fresh feedback-free attempt instead. If the stage
|
||||
budget is exhausted, safe groups stay applied, unsafe groups stay separate, and
|
||||
one fallback warning summarizes the final defect without raw model content.
|
||||
|
||||
## Evidence, Candidates, And Normalization
|
||||
|
||||
The current transcript is the only durable evidence source. Extractors assign
|
||||
|
||||
@@ -247,8 +247,12 @@ redaction boundary.
|
||||
Structural repair does not replace pipeline retry behavior: a binding's
|
||||
configured retry count reruns its complete stage attempt after an operational
|
||||
or structural error, module-requested retry, or actionable semantic rejection.
|
||||
The pipeline owns attempt lifecycle, validation chains, and retry diagnostics;
|
||||
see [Pipeline Internals](pipeline.md#validation-retries-and-output) and the
|
||||
An actionable module-requested retry and a validator rejection both use the
|
||||
same correction payload when the producer exposes an exact latest response;
|
||||
feedback-free module retries reconstruct the ordinary request without appended
|
||||
messages. The pipeline owns attempt lifecycle, validation chains, and retry
|
||||
diagnostics; see
|
||||
[Pipeline Internals](pipeline.md#validation-retries-and-output) and the
|
||||
[binding reference](../config.md#module-bindings-and-validators).
|
||||
|
||||
## Timeout Ownership
|
||||
|
||||
@@ -115,6 +115,16 @@ eligible producers. Deterministic skip, limit, and fallback outcomes carry no
|
||||
model candidate, so a later rejection applies terminal policy without spending
|
||||
an ineffective semantic retry.
|
||||
|
||||
When proposal assessment or typed group application rejects a structurally
|
||||
valid group, the normalizer may return its safe partial value with a
|
||||
module-requested retry. A feedback-capable directive supplies bounded
|
||||
model-facing correction guidance separately from operator diagnostics and
|
||||
retains the exact proposal response as its candidate. The shared stage retry
|
||||
mechanism appends that response and guidance to a fresh complete request. A
|
||||
structurally invalid completion has no valid candidate and therefore requests a
|
||||
feedback-free fresh attempt. On exhaustion, only the final safe fallback and
|
||||
its bounded process diagnostic advance to validation.
|
||||
|
||||
The core supplies a conservative generic prompt and the single private
|
||||
response schema. A domain prompt may substitute its semantic instructions but
|
||||
mounts the core-owned protocol and candidate/transcript presentation assets.
|
||||
|
||||
@@ -154,18 +154,25 @@ complete validation chain. It preserves terminal diagnostics only from the final
|
||||
or rejected attempt, plus one fixed validation-incomplete warning per validator whose execution
|
||||
budget was exhausted under `warn_continue`. Cancellation stops retries.
|
||||
Normalizer-specific retry directives consume this same budget and validate any
|
||||
final safe fallback through the normalizer chain.
|
||||
final safe fallback through the normalizer chain. A directive may carry bounded
|
||||
correction guidance only when it also exposes the exact latest
|
||||
`single_response_v1` candidate. The state machine then uses the same replacement
|
||||
request shape as validator correction. A directive without guidance clears any
|
||||
prior correction and starts a fresh attempt, which preserves structural retry
|
||||
behavior when no valid response exists.
|
||||
|
||||
The artifact-neutral producer-attempt state machine owns that shared budget,
|
||||
attempt provenance, semantic-correction material, and terminal-policy
|
||||
selection. It accepts producer and complete-validation closures, so artifact
|
||||
materialization, cache handling, checkpoints, and debug output stay at the
|
||||
operation boundary. It distinguishes operational, structural, module-requested,
|
||||
and semantic retries. A semantic retry is available only for a valid latest
|
||||
`single_response_v1` candidate; a deterministic or no-model rejection instead
|
||||
settles the semantic policy immediately. Structural-output errors alone use the
|
||||
structural policy, and validation failure without rejection settles the
|
||||
validator-failure policy without regenerating the producer.
|
||||
and validator-semantic retries. Model feedback from either semantic source is
|
||||
available only for a valid latest `single_response_v1` candidate. A
|
||||
deterministic or no-model validator rejection instead settles the semantic
|
||||
policy immediately, while a feedback-free module directive remains an ordinary
|
||||
fresh retry. Structural-output errors alone use the structural policy, and
|
||||
validation failure without rejection settles the validator-failure policy
|
||||
without regenerating the producer.
|
||||
|
||||
Chunk planning uses this state machine for generated plans. A rejected or
|
||||
validation-incomplete automatic cache hit is not model material and therefore
|
||||
|
||||
@@ -201,6 +201,13 @@ they are not model instructions. The framework constructs model-facing retry
|
||||
text only from the semantic guidance and fails the contract rather than
|
||||
inventing or truncating missing guidance.
|
||||
|
||||
An LLM-backed module may also request a feedback-aware retry when its own
|
||||
deterministic translation or typed safety policy rejects a structurally valid
|
||||
model response. It must supply model-facing guidance separately from its
|
||||
reason code, operator message, and fallback diagnostics, together with the
|
||||
exact `single_response_v1` candidate. A feedback-free module retry remains
|
||||
valid when no exact candidate exists.
|
||||
|
||||
Default validator chains are production composition policy and are registered
|
||||
centrally by stage and module. Configuration may replace a stage-local default,
|
||||
including with an explicitly empty chain. Configured validator order is
|
||||
@@ -222,12 +229,14 @@ the two budgets must remain separate.
|
||||
|
||||
An LLM-backed producer can participate in semantic correction only when it
|
||||
declares `single_response_v1` and returns the exact one response that directly
|
||||
controlled its candidate. On an actionable rejection, the framework rebuilds
|
||||
the ordinary request and appends only the latest defective response as an
|
||||
`assistant` message plus one aggregated `user` correction message. This is a
|
||||
fresh replacement request, not a growing conversation. The retry budgets,
|
||||
terminal policy, and sensitive-data rationale are recorded in
|
||||
[ADR-0014](../adr/0014-feedback-aware-validation-retries.md).
|
||||
controlled its candidate. On an actionable validator rejection or
|
||||
feedback-capable module retry, the framework rebuilds the ordinary request and
|
||||
appends only the latest defective response as an `assistant` message plus one
|
||||
aggregated `user` correction message. This is a fresh replacement request, not
|
||||
a growing conversation. The retry budgets, terminal policy, and sensitive-data
|
||||
rationale are recorded in
|
||||
[ADR-0014](../adr/0014-feedback-aware-validation-retries.md) and
|
||||
[ADR-0016](../adr/0016-feedback-aware-module-requested-retries.md).
|
||||
|
||||
When a model selects an application entity, callers must supply a contextual
|
||||
selection and deterministically attach the opaque application identity whenever
|
||||
|
||||
@@ -15,7 +15,7 @@ diagnostics. The remaining near-term work applies those completed foundations
|
||||
to domain review and empirical evaluation.
|
||||
|
||||
The active D&D reliability work is defined by
|
||||
[D&D Item-Occurrence Holder Reliability](item-occurrence-holder-reliability.md).
|
||||
[Feedback-Aware Semantic-Reconciliation Retries](semantic-reconciliation-retry-feedback.md).
|
||||
|
||||
## Near-Term D&D Pipeline
|
||||
|
||||
|
||||
117
docs/roadmap/semantic-reconciliation-retry-feedback.md
Normal file
117
docs/roadmap/semantic-reconciliation-retry-feedback.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Feedback-Aware Semantic-Reconciliation Retries
|
||||
|
||||
## Purpose
|
||||
|
||||
Improve semantic registry reconciliation for smaller models by turning the
|
||||
existing module-requested retry into a bounded corrective exchange. Preserve
|
||||
the current deterministic safety checks, shared retry budget, and safe fallback
|
||||
when correction is unsuccessful.
|
||||
|
||||
## Problem
|
||||
|
||||
NPC, item, and location registry normalizers can reject unsafe duplicate-group
|
||||
proposals and retain a safe partial result. They currently ask the pipeline for
|
||||
another attempt, but the next attempt receives the original prompt without the
|
||||
defective response or an explanation of the defect. The normalizers also build
|
||||
their retry messages from internal issue-category strings, which are suitable
|
||||
for diagnostics but not useful model instructions. If all attempts are
|
||||
exhausted, the warning reports the omitted-group count without explaining the
|
||||
final defect or confirming that affected candidates were preserved separately.
|
||||
|
||||
## Target State
|
||||
|
||||
- A normalizer retry directive may carry model-facing correction guidance
|
||||
separately from its stable reason code, operator message, and fallback
|
||||
diagnostics.
|
||||
- When the directive includes correction guidance and a valid
|
||||
`single_response_v1` model candidate, the shared producer-attempt state
|
||||
machine reconstructs the complete original prompt and appends the exact
|
||||
defective response as an assistant message followed by one user correction
|
||||
message. Earlier failed attempts do not accumulate.
|
||||
- This correction consumes the existing normalize-stage retry budget. It does
|
||||
not add a module-local loop or alter PromptKit's independent structured-output
|
||||
repair budget.
|
||||
- A retry caused by structurally invalid output remains a fresh stage attempt
|
||||
without semantic feedback when no valid model candidate is available.
|
||||
- Internal issue categories remain available for operator and debug
|
||||
diagnostics, but are never sent to a model. A shared renderer translates
|
||||
every known category into concise prose that explains the defect and the
|
||||
required valid shape.
|
||||
- Item reconciliation appends its domain rule when a proposal attempts to
|
||||
combine different currency denominations or currency with a non-currency
|
||||
item. NPC and location reconciliation use only the shared protocol guidance.
|
||||
- On final exhaustion, safe proposal groups remain applied, unsafe groups
|
||||
remain separate, and one process warning gives a bounded summary of the final
|
||||
defect and explicitly states that the affected candidates were preserved
|
||||
separately.
|
||||
|
||||
## Required Work
|
||||
|
||||
1. Extend `contracts.NormalizeRetry` and its clone, validation, and debug paths
|
||||
with an optional, bounded model-facing correction-guidance field. Keep
|
||||
reason codes and operator messages out of model input. Require guidance to
|
||||
be paired with a valid `single_response_v1` model candidate; preserve the
|
||||
existing feedback-free directive for structural retry cases.
|
||||
2. Extend the generic producer-attempt state machine so a feedback-capable
|
||||
module retry constructs `SemanticCorrection` from the latest exact response
|
||||
and the supplied guidance. Preserve attempt provenance, cancellation,
|
||||
terminal policy, diagnostic handling, byte-faithful response provenance,
|
||||
and the rule that only the latest response is included.
|
||||
3. Add a model-facing renderer to `internal/framework/semanticreconcile`.
|
||||
Implement it with a typed lookup table keyed by `IssueCategory` and an
|
||||
authoritative list of all categories, rather than a positional array.
|
||||
Render response-local duplicate-group numbers in human-friendly form,
|
||||
de-duplicate repeated guidance, enforce a size bound, and return an error for
|
||||
any unmapped category instead of exposing its raw value. Retain
|
||||
`IssueDetails` for operator/debug diagnostics.
|
||||
4. Update the NPC, item, and location registry normalizers to populate the new
|
||||
correction guidance for semantically invalid proposals. Add the item-only
|
||||
currency guidance after the shared prose when typed application rejects a
|
||||
group. Do not add guidance to the invalid-structured-output path when no
|
||||
exact valid candidate exists.
|
||||
5. Improve exhausted-fallback diagnostics across all three registries. Describe
|
||||
the final proposal failure accurately, state that affected candidates were
|
||||
preserved separately, and keep warning samples bounded and free of raw model
|
||||
responses.
|
||||
6. Update the shared semantic-reconciliation and affected normalizer policy
|
||||
fingerprints so checkpoints produced under blind retry behavior are not
|
||||
reused. Record the durable module-retry correction decision in a new ADR,
|
||||
and update the architecture, pipeline, LLM-runtime, and D&D internal docs in
|
||||
their canonical scopes when the behavior is implemented.
|
||||
7. Add lean offline behavioral coverage for corrective module retries, exact
|
||||
latest-response use, feedback-free structural retries, contract validation,
|
||||
complete category-to-prose coverage, domain-specific item guidance, bounded
|
||||
messages, safe final fallback, and warning content. Avoid exact prose
|
||||
snapshots, prompt-length assertions, live providers, and redundant tests of
|
||||
PromptKit internals.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Changing normalize retry counts, PromptKit repair settings, or pipeline
|
||||
terminal policies.
|
||||
- Changing the reconciliation prompt protocol, response schema, registry
|
||||
artifact schemas, or request-local candidate IDs.
|
||||
- Reintroducing provider-sensitive schema constraints such as `uniqueItems`.
|
||||
- Automatically applying an unsafe duplicate proposal or treating a fallback
|
||||
warning as an extraction-quality warning.
|
||||
- Persisting raw model responses or correction text in ordinary warnings,
|
||||
receipts, manifests, caches, or checkpoints.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A semantic proposal rejected by an NPC, item, or location registry normalizer
|
||||
receives the exact defective response and actionable prose on the next
|
||||
normalize attempt, using only the configured stage retry budget.
|
||||
- Every `IssueCategory` has non-empty model-facing guidance, no raw category or
|
||||
reason-code string reaches the model, and an unmapped future category fails
|
||||
closed.
|
||||
- An invalid item consolidation additionally tells the model to keep currency
|
||||
denominations distinct and not mix currency with non-currency items.
|
||||
- Structural-output failures without a valid model candidate retain the current
|
||||
fresh-retry behavior.
|
||||
- After retry exhaustion, safe consolidations remain applied, affected
|
||||
candidates remain separate, and the single fallback warning explains both
|
||||
the final cause and preservation behavior without including raw model output.
|
||||
- Checkpoint identities change where policy changed, canonical documentation
|
||||
and a new ADR describe the implemented behavior, and focused tests plus
|
||||
`go test ./...`, `go vet ./...`, and `go build ./cmd/notarius` pass.
|
||||
Reference in New Issue
Block a user