Two ADRs that unblock the remaining items from the 2026-05-20 audit that could not ship as direct PRs. ADR-0088 — Realizer-Grounded Authority (Finding 2 retry) ======================================================== The first-response audit remedy (wire ``ground_graph`` between ``runtime.chat`` and ``realize_semantic``) was empirically attempted on ``fix/ground-graph-wiring`` and reverted: the grounded realizer's template output (e.g. ``"Light is a visible medium that reveal truth"``) is grammatically and stylistically weaker than the runtime path's ADR-0085-polished pack-grounded surface, so the realizer wins the surface resolver (PR #76) and the user-visible surface regresses on 23 byte-identical tests + ``register_invariant_grounding``. ADR-0088 reframes Finding 2 as a two-phase rollout: * Phase A (no behavior change) — realizer fluency parity. Templates consult the same gloss source ADR-0085 wired into the CAUSE composer, emit 3sg verb agreement, and carry the same pack-provenance tag the runtime path emits. Byte-identical today because the realizer is still gated by ``_is_useful_surface``. * Phase B (substantive) — ground the graph and let the realizer compete. Surfaces change exactly once, with a per-case re-baseline justified by a "fluency ≥ pre-fix runtime surface" invariant. The audit's final-draft remedy (hot-path short-circuit only) is explicitly rejected — pure perf cleanup, no metric lift since ``core eval cognition`` is already at 100% groundedness. ADR-0089 — Compound-Intent Pipeline Dispatch (Finding 4) ======================================================== ``classify_compound_intent`` is implemented but never reaches ``CognitiveTurnPipeline.run()``. Compound inputs like *"What is X and how does it relate to Y?"* silently drop the second clause. Naive multi-node dispatch breaks every downstream stage: PropositionGraph (one root), plan_articulation (single-root), realize_semantic (one target), surface resolver (one surface per turn), compute_trace_hash (one intent_tag + articulation_surface), teaching loop (one correction-source proposal), register / anchor- lens telemetry (one variant per turn). ADR-0089 proposes a three-phase rollout: * Phase C1 (no behavior change) — call ``classify_compound_intent`` in step 1b, record dropped clauses on ``CognitiveTurnResult.dropped_compound_clauses`` for observability while routing the dominant clause through the existing single-intent path. * Phase C2 (opt-in substantive) — flag-gated multi-node graph dispatch with new ``CompoundEdge`` / ``ConjunctionRelation``, widened ``compute_trace_hash`` carrying a ``compound_clauses_hash``, and a ``multi_clause_surface`` field on the resolver. Flag-off preserves byte-identity. * Phase C3 — telemetry alignment + demo + docs. Each phase is independently shippable and preserves the existing null-lift / byte-identity invariants register and anchor-lens established (ADR-0072, ADR-0073d) as the project's pattern for substantive runtime behavior changes. Both ADRs are Proposed; ratification follows the existing pack / ADR review process. No code lands in this commit.
7.7 KiB
ADR-0089 — Compound-Intent Pipeline Dispatch (Finding 4)
Status: Proposed
Date: 2026-05-20
Author: Shay
Related: generate.intent.classify_compound_intent, ADR-0066 (thread context), PR #76 (surface authority resolver)
Context
The 2026-05-20 audit identified that classify_compound_intent() is fully implemented in generate/intent.py but never called by the cognitive pipeline. The two existing call sites are:
chat/runtime.py:961— inside_maybe_apply_discourse_planner, gated byRuntimeConfig.discourse_planner=False(default off).evals/compound_intent_decomposition/runner.pyandevals/cold_start_grounding/runner.py— eval-only.
CognitiveTurnPipeline.run() calls only the single-intent classify_intent() at line 110 and routes the result straight into graph_from_intent(). For an input like "What is X and how does it relate to Y?" the pipeline picks one intent by regex precedence (probably DEFINITION on X) and silently drops the CAUSE / TRANSITIVE_QUERY clause on Y. The graph never sees both subjects, the resolver never sees the second clause, and the trace hash records only the dominant clause.
Wiring compound intent into the pipeline is not a single small PR. Every downstream stage today assumes one intent + one subject:
PropositionGraphcarries one root node per turn.plan_articulationtopologically orders the nodes of a single-root graph.realize_semanticbuilds oneArticulationTargetper call.- The surface resolver (PR #76) picks one surface per turn.
compute_trace_hashrecords oneintent_tagand onearticulation_surfaceper turn.- The teaching loop binds one correction-source proposal per turn.
- The speculative-subject cache (PR #77) tracks subjects per proposal, but the marker decision assumes one subject set per surface.
- The register / anchor-lens telemetry (ADR-0072 / ADR-0073d) emits one
(register_id, register_variant_id)and one(anchor_lens_id, anchor_lens_mode_label)per turn.
Naive multi-node dispatch breaks every one of these assumptions.
Decision
ADR-0089 proposes a three-phase rollout that lands compound intent without breaking any existing invariant. Each phase is independently shippable.
Phase C1 — Substrate (no behavior change)
- Add
CompoundIntentplumbing toCognitiveTurnPipeline.run():- Call
classify_compound_intent(text)at step 1b alongside the existingclassify_intent(text). - When the compound classifier returns a single-clause result, the pipeline takes the existing single-intent path byte-identically.
- When it returns multiple clauses, the pipeline still routes the dominant clause through the existing path and records the other clauses on a new
CognitiveTurnResult.dropped_compound_clauses: tuple[DialogueIntent, ...] = ()field for observability.
- Call
- Add a CI invariant
tests/test_compound_intent_substrate.py::test_compound_dropped_clauses_recordedthat asserts: for the four canonical compound shapes (AND,BUT,BECAUSE, comma-joined), the dropped clauses appear in the result and the dominant-clause surface is byte-identical to today.
Phase C1 is byte-identical at the surface and trace_hash level. It surfaces the second-clause loss as observable telemetry instead of silent drop.
Phase C2 — Multi-node graph (opt-in)
- Add
RuntimeConfig.compound_intent_dispatch: bool = False. - When enabled and the compound classifier returns multiple clauses,
graph_from_intentis widened to accept atuple[DialogueIntent, ...]and emits one node per clause with explicitCompoundEdge(source=p0, target=p1, relation=ConjunctionRelation.AND|BUT|BECAUSE)edges. plan_articulationwalks the multi-node graph in topological order; each node yields anArticulationStepwithmove=RhetoricalMove.CONTINUEfor non-root nodes.realize_semanticconsumes the multi-step target and emits one surface per clause joined by the appropriate conjunction (and,but,because).- The surface resolver (PR #76) is widened to accept a
multi_clause_surfacefield that, when non-empty, wins over the single-clause runtime surface. Walk / compose folds append to the multi-clause surface as before. compute_trace_hashis widened with acompound_clauses_hash: str = ""field — a deterministic hash of the dropped (Phase C1) or routed (Phase C2) clauses' intent tags + subjects. Empty string preserves pre-ADR-0089 trace hashes byte-identically when the dispatch is off.
Phase C2 is opt-in substantive. Default off preserves every existing invariant.
Phase C3 — Telemetry alignment
- Widen
TurnEventandChatResponsewithcompound_clause_count: int = 0andcompound_relation_chain: tuple[str, ...] = ()so operators can see compound dispatch outcomes without parsing surfaces. - Document the new fields in
docs/runtime_contracts.mdunder a new "compound intent" subsection. - Add a
core demo compound-tourwalking four prompts (AND,BUT,BECAUSE, comma-joined) × two modes (flag off, flag on) and asserting:- flag-off: byte-identical surfaces and trace_hashes (null-lift invariant).
- flag-on: distinct surfaces, distinct trace_hashes, all clauses represented in the surface, all clauses recorded in the new telemetry fields.
Consequences
- Pipeline becomes compositionally complete — input shape (compound) maps to output shape (multi-clause) deterministically.
- The teaching loop will need to learn which clause a correction binds to. Today a CORRECTION turn binds to the prior turn's single proposal; with multi-clause prior turns, the correction-binding rule must specify which clause is being corrected. Out of scope for ADR-0089 itself; a follow-up ADR handles correction routing under multi-clause priors.
- Speculative-subject cache (PR #77) gains multi-subject inserts. No code change required —
_remember_speculative_subjectis per-subject and idempotent; multi-clause proposals call it once per clause. - Register / anchor-lens dispatch is unaffected — both axes apply to the realizer output as a whole, not per clause. ADR-0072 + ADR-0073d invariants remain pinned.
Rejected alternatives
- Land compound intent as a single unified PR. The blast radius (graph_planner + plan_articulation + realize_semantic + surface resolver + trace_hash + telemetry + tests) is too large for a load-bearing PR per CLAUDE.md. Rejected.
- Route compound intent through
_maybe_apply_discourse_planneronly. That path is already wired but defaults off and produces multi-clause output via a different code path (the discourse planner builds clauses from aGroundingBundle, not from a multi-node graph). Doubling up the paths is structural drift. Rejected — Phase C2 supersedes the discourse planner's compound dispatch. - Auto-enable compound dispatch by default. Compound prompts are a minority of input shapes; flag-off byte-identity is load-bearing for re-baseline confidence. Rejected.
Validation gate
Phase C1 merge:
- Compound classifier output appears on
CognitiveTurnResult.dropped_compound_clausesfor every multi-clause input. - All existing tests pass byte-identically.
- New invariant test on the four canonical compound shapes.
Phase C2 merge (separate PR):
- Flag-off byte-identity invariant (
test_compound_intent_null_lift). - Flag-on multi-clause surface invariant (
test_compound_clauses_in_surface). - Flag-on distinct-trace-hash invariant (analogous to register / anchor-lens patterns).
core eval cognitionbyte-identical with the flag off; flag-on is exercised via a new laneevals/compound_intent_pipeline/.
Phase C3 merge (separate PR):
core demo compound-tourships; both flag-on and flag-off assertions in CI.docs/runtime_contracts.mdupdated.