유대선
프로젝트로
·기술 회고·2

Migrate the faked signal, keep the real one: semantic context that still trusts the graph edge

Phase 1b of ADR-020. workitem_context's scorer mixed a real structural signal (a graph edge between two facts) with a faked one (keyword token overlap standing in for 'related'). The fix moves only the faked half to embeddings and keeps the graph edge as a deterministic booster.

A scorer with two very different halves

After graph_query (phase 1), the next keyword-bypass was context_score — the function behind workitem_context, which builds the cited "here's what's related to this work item" bundle. But context_score wasn't pure keyword like graph_query_score. It blended:

The keyword half has phase 1's blind spot: a genuinely related fact in another language, or worded differently, scores 0. But the structural half is real — a graph edge isn't a guess about relatedness, it's a relationship the system recorded. A blanket "move it all to the brain" would have thrown that away.

Split the signals

So phase 1b doesn't replace context_score — it splits it. A new brain_rank_scored() returns (index, cosine) pairs (not just an order) so the semantic score can be blended rather than just sorted. Then:

final = (cosine * 100)                       // semantic base — replaces the keyword loop
      + (graph edge present ? 80 : 0)        // structural — KEPT, deterministic
      + (confirmed provenance ? 8 : 0)       // structural — KEPT
      + (same domain ? 10 : 0)               // structural — KEPT

If the brain is down, the whole thing falls back to the original context_score (keyword and all). Edge-connected facts still surface first — because that's a fact the graph holds, not an inference — but the ordering among everything is now semantic instead of token-overlap.

Verified

cargo check: 0 errors. The /rank response's score field was confirmed numeric this session (0.53 / 0.33 / 0.088), which is what brain_rank_scored parses. (Not yet built into the .app, not pushed.)

The lesson

"Move semantics to the brain" is not "delete every heuristic." When a scorer mixes a real deterministic signal (a graph edge, an explicit tag, a provenance flag) with a faked one (keyword overlap pretending to be relatedness), migrate only the faked half and keep the deterministic half as a booster on top. The audit's job is exactly this discrimination — which of the ~26 sites are faking intelligence, and which are honest structure that should stay in Rust.