Spec: Confidence Decay & Score Fusion — Mathematical Semantics¶
File: .ai/specs/core-engine/2026-07-02-confidence-decay.md Status: Approved (F7 math review complete — see Changelog; conditions incorporated in v0.2) Owners: Storage lane; reviewers: planner lane (fusion feeds evidence scoring in PR-050) Related: PR-022 (decay), PR-040 (fusion), traversal-semantics spec, memory-planner spec (Phase 3 consumer)
1. Overview¶
Defines the exact mathematics of multi-hop confidence decay, per-edge-type weighting, floor cutoff, and the fusion of decay scores with vector similarity into the final ranking score. This is one of the three F7 algorithm families whose math must be peer-reviewed before implementation.
2. Problem Statement¶
Graph expansion returns neighbors; ranking needs to express "a fact two hops away through weak relationships matters less than a direct fact." Without pinned math: scores are irreproducible across versions; fusion with vector similarity is vibes; and Phase 3's evidence budgeting (score-per-token packing) inherits undefined inputs.
3. Proposed Solution¶
Multiplicative per-hop decay with per-edge-type weights, shortest-path-first assignment (BFS guarantees it), a floor that prunes negligible paths, and multiplicative score fusion with vector similarity under a tunable balance exponent. All parameters live in config with spec'd defaults; all scores are in (0, 1].
4. Architecture¶
Traversal (traversal-semantics spec) attaches decay(n) as it expands; the vector stage (PR-040) attaches sim(n); the final ranking stage computes S(n) and orders results. The planner (Phase 3) consumes S(n) unchanged as evidence weight.
5. Data Models¶
Decay. For a node n reached from root r via path p = (e₁ … e_d):
decay_p(n) = ∏ᵢ w(type(eᵢ)) with w: EdgeType → (0,1], default w = 0.7
decay(n) = max over all discovered paths ≤ depth D of decay_p(n)
BFS + visited-set yields the max without path enumeration provided hop-count order approximates weight order; where custom weights invert that (a 2-hop strong path outscoring a 1-hop weak path), v1 accepts the BFS approximation and records it (OQ-1). Roots: decay(r) = 1. Floor: paths are not expanded past decay < ε (default ε = 0.05); with default weights, depth 3 ⇒ 0.343 ≥ ε, so the floor binds only under custom low weights.
Fusion. With vector similarity sim(n) ∈ [0,1] (cosine, clamped):
S(n) = sim(n)^α · decay(n)^(1−α) α ∈ [0,1], default α = 0.6
Boundary behavior (normative): no vector clause ⇒ fusion bypassed ⇒ S = decay; node lacking an embedding in a vector query ⇒ sim treated as minScore (participates, cannot outrank true matches); vector-only queries (no expand) ⇒ fusion bypassed (equivalently α forced to 1.0) ⇒ S = sim — note that substituting decay = 1 into the formula would yield S = sim^α ≠ sim, artificially inflating weak matches, so the bypass is required, not sugar (v0.2 correction). Numeric guards (normative): if α == 0.0 { return decay } and if α == 1.0 { return sim } fast-paths precede the pow evaluation, so 0⁰ is never evaluated and no reliance on language-level pow(0,0) conventions exists; sim is additionally clamped to [0,1] before fusion. Geometric-mean form chosen because a zero-ish factor should crater the combined score (a semantically perfect but graph-distant node must not dominate), and exponents give one interpretable balance knob.
6. API Contracts¶
Weights: decay.default_weight (0.7), decay.weights.{EDGE_TYPE} overrides, decay.floor (0.05), fusion.alpha (0.6) — all per-server config; per-query override of alpha only ("vector": {"alpha": 0.4}), bounded [0,1]. Scores surface in results as score, with score_parts: {sim, decay} when trace: true.
7. UI/UX¶
Trace viewer (Phase 3) renders score_parts per evidence span so operators can see why something ranked. No other surface.
8. Configuration¶
As §6. Changing defaults is config; changing the formulas is a spec amendment + version note in trace output (score_model: "v1").
9. Alternatives Considered¶
Additive decay (score − c per hop; rejected: not scale-free, goes negative, composes badly with multiplicative weights). Exponential-of-sum e^(−λd) (rejected: ignores edge-type semantics unless λ varies per type, at which point it is the multiplicative model in log space with worse ergonomics). Weighted-sum fusion αs+(1−α)d (rejected: lets one factor fully compensate the other; a decay≈0 node could still rank top on similarity alone, violating the product's graph-grounding premise). Learned fusion (rejected for v1: no training signal yet; eval harness in PR-057 will generate one — revisit then). Reciprocal Rank Fusion (rejected: rank-based, discards magnitudes the planner's token-budget packing needs).
10. Implementation Approach¶
PR-022 implements decay inside the traversal attach point; PR-040 implements sim + fusion (including the α∈{0,1} fast-paths from §5 — the pow expression is never evaluated at the exponent boundaries). Both stages are pure functions over (path weights | similarity, params) with exhaustive unit tables. Gate: this spec's Status must be Approved with named math reviewers in the Changelog before either commit lands (F7). — Satisfied 2026-07-03, see Changelog.
11. Migration Path¶
Greenfield. Formula changes version the score model (score_model tag in traces); persisted traces are never re-scored — they record the model that produced them.
12. Success Metrics¶
Property tests: S ∈ (0,1]; monotonicity (increasing any w, sim, or shortening a path never lowers S); α=1 ⇒ S=sim, α=0 ⇒ S=decay; floor prunes exactly paths below ε. Golden ranking fixtures: hand-computed scores on a 12-node reference graph, byte-stable. Determinism across platforms (no float-order sensitivity: scores computed in fixed evaluation order).
13. Open Questions¶
- OQ-1: BFS max-approximation — should v1 do weight-ordered (Dijkstra-style, −log w) expansion instead, guaranteeing true max-decay paths under custom weights? Stance: no for v1 — BFS is what the F6 latency gate is built on; measure the discrepancy on real weight configs via a debug flag, revisit if material.
- OQ-2: Should α auto-tune from the eval harness (PR-057) once labeled data exists? Stance: yes in principle, Phase 4; config-only until then.
14. Changelog¶
- 2026-07-03 — v0.2: F7 math review sign-off — J. Porter (jp@densops.com), approved with conditions, both incorporated: (1) §5 vector-only boundary corrected — decay=1 substitution yields S=sim^α ≠ sim, so vector-only queries bypass fusion (α forced to 1.0) instead; (2) pow(0,0) guard — explicit α∈{0,1} fast-paths precede the pow evaluation, no reliance on language pow conventions. Reviewer validated: floor arithmetic (0.7³=0.343≥ε), BFS max-approximation stance on OQ-1 (retained for v1 latency), geometric-mean fusion over arithmetic (correctly craters on either factor). Approval covers the decay commit in PR-022 and the fusion commit in PR-040.
- 2026-07-03 — v0.3 (PR-040 as built): fusion implemented in
query/fusion.rsexactly per §5 — α∈{0,1} fast-paths precede pow, sim clamped to [0,1], unit table + monotonicity/bounds property tests. Primaries are NEVER fused: S_primary = sim (ordering per query-language spec §5); fusion applies to related (expanded) nodes only, so vector-only queries bypass fusion structurally. Nodes without an embedding take sim = minScore (default 0).fusion.alphalives asdecay.fusion_alphain config (0.6); per-query overridevector.alphabounded [0,1] as specced.score_partsdeferred to the trace-viewer lane; scores surface asscoreon records and related nodes now. - 2026-07-02 — v0.1: initial draft, awaiting F7 math review sign-off.