ADR 0020: Observability Feedback Loop Boundaries
Status: Accepted
Date: 2026-10-12
Context: Issue #4289 — “feat(observability): close the feedback loop — fitness→adaptive-thresholds (currently shadow-only) + optional IFeedbackCoordinator” (promoted from Findings catalog #3160 / #4271).
Ratification: consensus_vote (live panel: Architect, Security Engineer, Scope Steward), APPROVED 3/0 (100% approval, 2/3 majority on Option B).
1. Context & Problem Statement
Findings catalog #3160 flagged two medium-severity findings regarding the observability feedback loop:
- Fitness signals are shadow-only:
signal.fitness_declinedis emitted byimprovement_review_signalsand consumed bytune-stage, but only asflag_tech_debtin shadow mode (“Non-routing signals (fitness/vote) stay shadow-logged”). No routing/threshold/observability-window mutation results;adaptive-thresholds.tsnever reads fitness. - No unifying feedback coordinator: An
IFeedbackCoordinator/SignalBushad been suggested to coordinate the four feedback state-holders (FeedbackIntegration,OutcomeFeedbackCollector,StrategyDistiller,RoutingMemory).
Issue #4289 asked to:
- Decide whether fitness dimensions should influence
adaptive-thresholds/ observability-window sizing out of shadow mode with a bounded, audited adjustment, or remain strictly for tech-debt/remediation. - Evaluate whether a thin
IFeedbackCoordinator/SignalBusis required, or explicitly decide the emergent typed EventBus is sufficient.
2. Decision
The consensus panel evaluated three architectural options:
- Option A: Unified
IFeedbackCoordinator+ Bounded fitness conservatism on cold-start (shadow-by-default). - Option B (Selected): EventBus-only (reject new coordinator) + Fitness strictly for tech-debt/remediation (no threshold mutation).
- Option C: Thin
IFeedbackCoordinatorfor status/query only + EventBus event conduit + Bounded fitness-dampened thresholds.
The panel approved Option B:
A. Rejection of IFeedbackCoordinator / SignalBus (Anti-Sprawl & DRY)
- Bus A (
IEventBus) is the canonical signal bus: The typedPipelineEventdiscriminated union on Bus A (packages/nexus-agents/src/pipeline/event-types.ts) withsignal.*events (signal.fitness_declined,signal.swarm_unhealthy,signal.vote_rejected) already serves the exact SignalBus requirement established in Epic #3143 P2 / #3147. - Existing composition: The four feedback APIs are not uncoordinated peers:
FeedbackIntegrationalready composesOutcomeFeedbackCollectorby construction (feedback-integration.ts:169-172).CompositeRouteris the single consumer that reaches theStrategyDistillerandRoutingMemorysingletons.
- Precedent against coordinator sprawl: Two earlier coordinator-shaped abstractions were previously removed for lack of a consumer:
learning.threshold_updated/learning.trend_detectedevents were deleted in #3022 (“emit helpers and subscribers both never landed”).- The server-wide
feedback-subscribersingleton was removed in #5003 because it fabricated CLI attribution.
- Anti-sprawl rule: Introducing a third coordination surface violates anti-sprawl and YAGNI without a named consumer.
B. Fitness Strictly Scoped to Tech Debt & Remediation (Domain Separation)
- Category error between static health and runtime capability:
- Codebase fitness (
fitness-score.ts) measures static repository structure:canonicalPaths,configSimplicity,layerSeparation,governanceIntegration. computeAdaptiveThresholds(adaptive-thresholds.ts) calculates dynamic success-rate baselines and max bonus caps for task routing based on empiricalTaskOutcomehistory fromOutcomeStore.- Code duplication or layer separation does not causally dictate which CLI has a higher probability of succeeding on code generation or refactoring tasks. Conflating static code metrics with execution success baselines injects uncorrelated noise into the learner.
- Codebase fitness (
- Uniform scalar limitation: Fitness is a single scalar per audit. Any dampening factor would apply uniformly across all arms, scaling all
maxBonusvalues identically without altering relative rankings. - No measurable outcome: Under the Mission’s capability-bias clause, changes that cannot be measured against real outcome metrics are rejected (“anything that cannot be measured”). Mutating
computeAdaptiveThresholds(whose only production consumer is the diagnosticweather-report.ts) would shadow a no-op with zero effect on routing. - Appropriate consumer:
signal.fitness_declinedis correctly routed toflag_tech_debtremediation issue-filing and review paths (Epic #3653), not routing demotions or threshold dampening.
3. Consequences
- Invariants pinned: Negative invariant tests (
tune-stage-invariants.test.ts) guarantee thatsignal.fitness_declinednever callsTuneAdjustmentStore.demote, never mutates routing multipliers, and never emits audit demotion logs, even underenabled: true(enforce mode). - Domain decoupling:
computeAdaptiveThresholdsremains a pure function ofOutcomeStoretask history. - Documentation clarified:
EVENT_BUS_BOUNDARIES.mdandtune-stage.tsexplicitly reference this decision, documenting that non-routing signals stay shadow-logged because they belong to remediation/tech-debt, not routing state.