- Status: proposed
- Date: 2026-02-16
Recent traces exposed a lifecycle gap at the user boundary:
- Guardrail retries are correctly executed (ADR 0016).
- When retries exhaust, raw internal guardrail diagnostics can leak to user-facing responses.
- Internal messages are implementation details (policy strings, schema fragments, correction hints), not stable user-facing semantics.
This is a generic lifecycle issue. It is not specific to any one guardrail type:
- provenance violations,
- registry-shape violations,
- singleton-method violations,
- contract/validation guardrails,
- future guardrails added over time.
This conflicts with project tenets:
- Agent-first mental model: internal diagnostics should stay precise for Tool Builder/Tool evolution.
- Tolerant interfaces by default: user-facing outcomes should be stable, typed, and capability-oriented.
- Runtime ergonomics and clarity: internals should remain observable without becoming user API.
- Ubiquitous language: lifecycle semantics should be generic, not category-specialized.
Related decisions:
- ADR 0016 defines validation-first retries and recoverable guardrail recovery.
- ADR 0017 preserves observational runtime semantics (no hidden semantic coercion).
- ADR 0021 defines provenance-required success for external-data behavior as one specific guardrail.
Adopt a generic boundary normalization policy for all guardrail retry-exhaustion outcomes.
All guardrail violations SHOULD include machine-readable subtype metadata (for example missing_external_provenance, context_tools_shape_misuse, singleton_method_mutation), in addition to human-readable message text.
Subtype metadata becomes first-class for:
- retry correction prompts,
- telemetry aggregation,
- user-boundary normalization.
When a top-level invocation exhausts recoverable guardrail retries, the surfaced user-facing outcome MUST be normalized to a stable typed error.
Normalization requirements:
- no raw internal policy strings in user-facing
error_message, - preserve retry exhaustion fact and class as metadata,
- keep mapping generic across guardrail subtypes.
V1 message policy:
- Use subtype-agnostic user-facing message text (for example: "This request couldn't be completed after multiple attempts.").
- Keep subtype detail in metadata/logs only.
- Defer subtype-aware user templates until evidence shows users need differentiated corrective guidance.
Normalization is presentation at user boundary only.
Internal artifacts/logs/traces MUST retain full diagnostics:
- guardrail class,
- subtype,
- original message,
- correction hints,
- retry counters and exhaustion metadata,
- failed-attempt diagnostics (
attempt_failures,latest_failure_*) captured by lifecycle telemetry.
Depth scope for v1:
- Apply boundary normalization only at top-level (
depth == 0). - Do not normalize depth-1+ tool outcomes; preserve raw typed errors for parent orchestration decisions.
- Keep subtype signal intact for tool-level retries, alternates, and escalation.
Runtime MUST NOT introduce category-specific normalization lanes (for example external-data-only lifecycle treatment). Category-specific invariants (such as ADR 0021 provenance) remain as individual guardrails with subtype metadata.
User-facing normalized errors should use stable typed vocabulary aligned to capabilities and lifecycle state (for example guardrail_retry_exhausted, optional subtype-aware user message templates), not implementation internals.
In scope:
- generic normalization of exhausted recoverable guardrails at top-level boundary,
- structured subtype metadata for guardrail violations,
- preserving internal diagnostics while stabilizing user-facing semantics.
Out of scope:
- changing individual guardrail invariants (for example ADR 0021 provenance requirements),
- domain-specific parser behavior or quality heuristics,
- special-case runtime handlers by category/domain.
- User-facing errors become stable and trustworthy.
- Guardrail implementation details remain internal while still fully observable.
- New guardrails automatically benefit from boundary policy without special casing.
- Lifecycle language stays generic and extensible.
- Requires explicit subtype tagging discipline in guardrail definitions.
- Adds a boundary-mapping layer that must be maintained as guardrails evolve.
- Some existing tests expecting raw messages will need updates.
- Keep current behavior (raw internal messages can surface).
- Rejected: unstable and leaks implementation detail.
- Add category-specific normalizers (external-data-only, etc.).
- Rejected: creates special lanes and weakens generic lifecycle model.
- Hide all details from both users and telemetry.
- Rejected: harms repair quality and out-of-band evolution.
- Ensure all current guardrails emit subtype metadata.
- Add subtype fields to observability entries where missing.
- Normalize top-level exhausted guardrail outcomes to stable user-facing typed failures.
- Keep original diagnostics in outcome metadata/logs.
- Aggregate guardrail exhaustion by subtype and capability pattern.
- Feed these signals into maintenance/evolution prioritization.
- Validate at least two distinct guardrail subtypes in live traces.
- Confirm user-facing outputs do not contain raw policy/schema internals.
- Boundary normalization is a presentation concern, not semantic success coercion (ADR 0017).
- Retry lifecycle semantics remain unchanged (ADR 0016).
- Individual invariants (for example ADR 0021 provenance) remain enforced as their own guardrails.
- What evidence threshold should trigger subtype-aware user templates in v2?
- Should v2 expose limited user-action hints for selected subtypes without leaking internal policy text?