KS78: Relative supersession demotion — multiplicative 0.40 (#11)#22
KS78: Relative supersession demotion — multiplicative 0.40 (#11)#22
Conversation
- Change supersession demotion from absolute penalty to multiplicative factor: score *= (1 - factor)^count instead of score -= constant - Default changed from 0.15 (absolute) to 0.40 (multiplicative, retains 60%) - Combines direct Supersedes edges and parent supersession into one factor - Fixes case where old memory with high cosine sim still outranked new memory despite being superseded (e.g., 0.85 - 0.15 = 0.70 > 0.65) - Updated tests to verify multiplicative math Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR replaces the absolute supersession demotion ( Key changes:
One issue found: Confidence Score: 4/5Safe to merge after adding a The multiplicative demotion logic in
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Top-K candidates\n(idx, cosine_score)"] --> B["Hebbian pass\ncompute boost + superseded_count\nper direct Supersedes edge"]
A --> C["Parent demotion pass\ncheck children for Supersedes edges\nstore count=1 if any found"]
B --> D["total_superseded =\ndirect_superseded_count\n+ parent_demotions.get(idx)"]
C --> D
D --> E{"total_superseded > 0?"}
E -- Yes --> F["retain = 1.0 - supersedes_demotion\nfinal_score *= retain.powi(total_superseded)"]
E -- No --> G["final_score unchanged"]
F --> H["Continue scoring pipeline\n(child penalty, confidence, soft-invalidation)"]
G --> H
Reviews (2): Last reviewed commit: "fix: declare superseded_count in both te..." | Re-trigger Greptile |
The hebbian_boosts closure referenced `superseded_count` but only declared `demotion: f64` — renamed variable + fixed Vec<f64> to Vec<(f64, u32)> to match downstream destructuring at line 1507. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| #[serde(default)] | ||
| pub supersedes_demotion: f32, |
There was a problem hiding this comment.
Serde default mismatch —
0.40 only takes effect when no config file exists
#[serde(default)] resolves to f32::default() = 0.0, not 0.40. The struct Default impl (line 481) correctly sets 0.40, but that only applies when EchoConfig::default() is called programmatically (i.e. no config file). Any user with a config file that omits supersedes_demotion will get 0.0 from serde — meaning no multiplicative demotion at all, silently defeating the entire purpose of this PR.
Compare how recency_weight is handled: it uses #[serde(default = "default_recency_weight")] backed by a named function. supersedes_demotion needs the same treatment.
fn default_supersedes_demotion() -> f32 {
0.40
}Then update the field annotation:
/// Supersession demotion factor (multiplicative). 0.40 = retain 60% of score.
/// Applied as `score *= (1 - factor)^count` for each supersession edge.
#[serde(default = "default_supersedes_demotion")]
pub supersedes_demotion: f32,This aligns serde deserialization with the struct Default impl so partial config files get 0.40 instead of 0.0.
Summary
score -= 0.15) to multiplicative (score *= 0.6^count)supersedes_demotionchanged:0.15→0.40(retain 60% of score per supersession edge)Root cause
Absolute demotion
-0.15insufficient when old memory has >0.15 higher cosine similarity than the new memory. Old superseded facts would still rank above their replacements.Changes
crates/shrimpk-core/src/config.rs: Updated default + doc comments (+3/-3)crates/shrimpk-memory/src/echo.rs: Multiplicative scoring + test updates (+31/-25)Test plan
supersession_multiplicative_demotion_closes_gappassessupersession_demotion_no_op_without_superseded_childpassescargo test -p shrimpk-memory— all non-ignored tests passcargo check --workspace— cleanCloses #11
🤖 Generated with Claude Code