core/docs/adr/ADR-0031-score-decomposition-surface.md
Shay 54e6bfc0d0
docs: reorganize docs landscape
Implements the 4-phase documentation reorganization master plan.

- Consolidation: Merged brief/, handoff/, planning/, and decisions/ into briefs/, handoffs/, plans/, and adr/ respectively (101 ADRs relocated)
- Root Cleanup: Relocated HANDOFF-gpt55-*.md and key top-level docs (runtime_contracts.md, etc.) to canonical folders. Added superseded alerts.
- Indices & Navigation: Created docs/README.md navigation document, docs/sessions/README.md index, docs/adr/README.md index
- Note: Also includes prior commit adding ADR-0200+ corpus hygiene governance (ADR-0225, dependency map, backfilled cross-references)
2026-06-30 16:59:36 -07:00

10 KiB
Raw Permalink Blame History

ADR-0031: Score-Decomposition Surface — Per-Axis Hedge Phrases

Status: Accepted (2026-05-17) Author: Joshua Shay + planner pass Companion docs: identity_packs.md, ADR-0028-identity-surface-wiring.md, ADR-0030-depth-language-hedge.md

Context

ADR-0028 and ADR-0030 made identity-pack swap visibly affect the surface across English, Hebrew, and Koine Greek. But the differentiation today consults a single scalarSurfaceContext.identity_alignment. The system can hedge harder when the trajectory drifts; it cannot say which aspect of identity is at issue when it hedges.

IdentityScore already carries the information we need: deviation_axes: FrozenSet[str] names the specific axes the IdentityCheck flagged. Today that field is computed and then ignored at the surface layer. Wiring it through closes the gap: when the system hedges on a trajectory whose deviation is truthfulness, the hedge can read "Evidence is thin that…"; on a coherence deviation, "This does not yet cohere:…"; on reverence, "Reports suggest…". The user learns why the system is hedging.

This is the score-decomposition surface.

Two interpretations considered

Interpretation A — Dominance-driven phrasing. Every assertion's character shifts based on which axis is the leader of the manifold. Truthfulness-dominant identity → precise phrasing on every assertion; coherence-dominant → unifying phrasing; reverence-dominant → deferential. Rejected for this ADR: requires new dominance scoring, changes confident assertions too (large blast radius), and isn't structurally connected to anything already computed.

Interpretation B — Deviation-driven hedge phrasing (this ADR). When the hedge band fires and the score reports a specific deviating axis for which the pack supplies an axis_hedges entry, the assembler uses that axis's phrase instead of the generic preferred_hedge_*. Otherwise the ADR-0028 generic phrase fires. The data we need (deviation_axes) already exists; we just plumb it through.

Interpretation A is preserved as a future possibility — nothing in this ADR forecloses it. The pack schema extension is named axis_hedges (not axis_phrasing) precisely so a future "axis phrasing" concept doesn't collide.

Decision

Pack schema extension (optional, additive)

A new optional axis_hedges sub-block inside surface_preferences:

"surface_preferences": {
  "...existing ADR-0028 fields...": "...",
  "axis_hedges": {
    "truthfulness": {
      "strong":    "Evidence is thin that",
      "soft":      "It is hard to confirm that",
      "qualifier": "Where evidence is partial,"
    },
    "coherence": {
      "strong":    "This does not yet cohere:",
      "soft":      "The threads loosely connect:",
      "qualifier": "Where the connection holds,"
    },
    "reverence": {
      "strong":    "Reports suggest",
      "soft":      "It is said that",
      "qualifier": "By some accounts,"
    }
  }
}

Each axis entry is keyed by axis_id (must match an existing value_axes[*].axis_id semantically, though the loader doesn't enforce that — a pack may declare hedges for axes it doesn't expose, which is harmless because no deviation will reference them). Each entry has three required phrases: strong, soft, qualifier, matching the three bands of the ADR-0028 hedge algorithm.

Selection algorithm

When the English hedge band fires (after threshold gating):

  1. Compute matching_axes = ctx.deviation_axes ∩ {ah.axis_id for ah in ctx.axis_hedges}.
  2. If matching_axes is empty → use the pack's generic preferred_hedge_* (ADR-0028 behavior).
  3. Otherwise → use the lex-smallest matching axis's phrase. The loader emits axis_hedges in lex order on axis_id for hashability + determinism; the assembler does a linear scan and takes the first match, which is the lex-smallest.

Lex tie-break is deliberate: when multiple axes deviate, the assembler must pick one phrase. Lex order is the simplest deterministic choice that doesn't require additional scoring. If a deployment cares about a different priority (e.g., "always prefer the truthfulness phrase when truthfulness is among the deviators"), they can re-key their axis_hedges so the preferred axis sorts earliest (a_truthfulness, b_coherence, …) — operational discipline, not architectural.

Three v1 pack profiles

Each pack ships its own English axis_hedges block tuned to its character:

Pack Truthfulness strong Coherence strong Reverence strong
default_general_v1 "Evidence is thin that" "This does not yet cohere:" "Reports suggest"
precision_first_v1 "The evidence does not support that" "This contradicts what is established:" "Source attestation is weak:"
generosity_first_v1 "Some hold that" "There is a thread connecting this:" "It is reported that"

Result at alignment=0.30 (strong band) with deviation_axes={"truthfulness"}:

Pack Surface
default_general_v1 "Evidence is thin that truth reveals reality."
precision_first_v1 "The evidence does not support that truth reveals reality."
generosity_first_v1 "Truth reveals reality." (generosity's strong threshold is 0.20; 0.30 is above the hedge band so no phrase prepends regardless of deviation)

Same trajectory, same deviating axis, three different surfaces.

Implementation

  • core/physics/identity.py: new AxisHedge frozen dataclass (strong / soft / qualifier strings); SurfacePreferences gains axis_hedges: Tuple = () field (tuple of (axis_id, AxisHedge) pairs, lex order).
  • packs/identity/loader.py: _build_axis_hedges() parses the optional sub-block, bounds-checks each phrase via the existing _validate_hedge_phrase (length 164), emits pairs in lex order on axis_id.
  • generate/surface.py: SurfaceContext gains two new frozen-and-hashable fields — deviation_axes: frozenset[str] and axis_hedges: tuple[tuple[str, str, str, str], ...] (flattened quadruples for hashability). New helper _axis_specific_phrase(ctx) returns the lex-smallest matching axis's (strong, soft, qualifier) or None. _apply_hedge consults it before falling back to ADR-0028 generic phrases.
  • chat/runtime.py::ChatRuntime._build_surface_context: lifts identity_score.deviation_axes and prefs.axis_hedges into the constructed SurfaceContext.
  • packs/identity/*.json: three v1 packs gain axis_hedges blocks. Pack body changed → re-ratified.

Re-ratification

Adding axis_hedges to each pack changed the canonical body → new pack_source_sha → new MasteryReport. scripts/ratify_identity_packs.py handled it idempotently. Updated SHAs:

  • default_general_v12ab7d469013509ba5030313ca9a609a443d0716e3ddcc5596f59858ce054f5d3
  • precision_first_v178aa1e6a68a35c2c8576b6196a52d421b94f6d11e006128986902a4fd08679af
  • generosity_first_v1511f1ce20edd4266239da61443bfc93473a5433f20bfee6692a25a03073dc933

Consequences

Positive

  • Hedges now name what's at issue. When the system hedges on a trajectory whose truthfulness axis is flagged, the user reads "Evidence is thin that…" — the refusal text is informative, not a generic disclaimer. This is meaningfully better epistemic communication.
  • Per-axis hedges are pack-tuned. A precision-first deployment hedges with evidential vocabulary; a generosity-first deployment hedges by attribution to "some". Same architecture, different voice.
  • Forward-compatible with Interpretation A. Dominance-driven phrasing (when a single axis leads rather than deviates) would slot in alongside axis_hedges without changing this ADR's shape.
  • No new scoring infrastructure. IdentityScore.deviation_axes already existed; this ADR is purely plumbing + a phrase table.
  • Backward compatible at every layer. Packs without axis_hedges fall through to ADR-0028 byte-for-byte. SurfaceContext() (no-args) carries deviation_axes=frozenset() and axis_hedges=(), so legacy callers see no behavioral change.

Negative / risks

  • English-only at v1. Depth languages still use the canonical _DEPTH_HEDGE_PHRASES from ADR-0030 regardless of which axis deviates. Closing this requires either a pack-schema bump (axis_hedges per language) or canonical depth-language axis hedges in surface.py. Both are tractable; neither belongs in this ADR.
  • Lex tie-break is operational, not semantic. When multiple axes deviate simultaneously, the chosen phrase is whichever axis_id sorts earliest — not necessarily the "most relevant" one. Deployments that need a different priority must use operational discipline (re-keying axis_ids) or wait for a follow-up ADR introducing per-pack axis priority.
  • Pack body grew. Three new phrases per axis × three axes = nine new strings per pack. The canonical JSON is still well under any practical size limit, and the ratification driver handled the change without issue.
  • SurfaceContext is bigger again. Two more fields. Both have safe defaults so direct SurfaceContext() construction in tests continues to work.

Scope limits (explicit non-goals for this ADR)

  • No per-language axis hedges. v1 axis_hedges are English-only.
  • No dominance-driven phrasing (Interpretation A). Phrasing changes only when the score reports deviation, not when a particular axis happens to lead.
  • No per-pack axis priority. Lex order is the tie-break.
  • No realizer-side use of deviation_axes beyond hedging (no rotor bias, no token selection shift, no separate refusal surface).

Verification

This ADR is satisfied when:

  • tests/test_identity_score_decomposition.py passes — 17 tests covering per-axis phrase selection, band gating still applies, pack-swap with deviation, lex tie-break, depth-language fallback, backward compatibility, and the contract that all three v1 packs ship axis_hedges for all three default axes.
  • Cognition (121), teaching (17), runtime (19), formation (182), smoke (67) suites green.
  • tests/test_identity_surface_divergence.py (ADR-0028) and tests/test_identity_surface_divergence_depth.py (ADR-0030) — both still passing (no regressions in the generic-phrase or depth-language paths).
  • All three v1 identity packs re-ratified with the new SHAs recorded above.