Qdrant recency bonus: 0.05·exp(−days/14) fixed both Qwen memory misses
Contents
In the last 10-question test, H2 (“how did that thing you were working on go?”) never got the newest memory from September 9 into the candidates, and C1 (“how do I take my coffee again?”) had the café-au-lait memory sitting at 0.441, under the 0.45 threshold.
In both cases the newer memory simply had a lower cosine score than the older one, so I figured a date bonus on top of similarity might pick them up, and reran the same 16 memories and 10 questions.
Setup
The only change is on the retrieval side: pull the top 8 candidates instead of 3, then re-rank them with a recency bonus. The 16 memories and 10 questions are the same as before, and so is the B-format instruction text from round 2 of the last post.
B format means the retrieved memories go into the prompt wrapped in a <retrieved_memory> block, with instructions to use them only when relevant, prefer newer dates, and not assert anything the memory doesn’t say.
| Item | Detail |
|---|---|
| PC | Same production machine as last time (Windows 11 Home, AMD Ryzen 7 5800HS, 16GB RAM) |
| Chat | ModelScope API-Inference, Qwen-Ambassador/Qwen3.7-Plus, enable_thinking: false |
| Embedding | Qwen3-Embedding-0.6B, 256 dimensions. The API is still gone, so this ran on local CPU as before |
| Vector search | Qdrant local mode, cosine similarity |
| Reference date | September 10, 2026, same as last time and matching “today” in the B instruction |
| Re-measured | B format only. OFF (no memory) and A (raw text with no instructions) were not rerun |
How the bonus goes in
First, where the date lives. One option was to drop the 2026-09-01: prefix from the embedded text and use the date only for the bonus.
But when I dropped it in the last post’s follow-up, the hit-free question N1 (quantum computers) jumped from 0.403 to 0.501 against the keyboard memory, and an unrelated memory crossed 0.45. Because of that, the date stays in the text, gets stored in the Qdrant payload (per-memory metadata) as well, and the bonus is computed from the payload date.
Take the top 8 by raw cosine, add each one’s bonus from its age in days, re-sort on that, and write up to 3 entries scoring 0.45 or higher into the prompt. Taking only 3 before re-sorting would lose anything that was 4th or lower before the bonus, which is why it’s 8. The 0.45 threshold applies to the post-bonus score.
For and there was no principled way to pick a value, and tuning coefficients on a 16-memory dataset doesn’t mean much, so I lined up four candidates and took the smallest one that satisfies all three conditions below.
| Condition | Detail |
|---|---|
| (a) | The highest post-bonus score across the four hit-free questions stays under 0.45, so no memory reaches Qwen on an unrelated question |
| (b) | C1’s newer café-au-lait memory (Sep 1) reaches 0.45 or higher |
| (c) | H2’s newest memory, the article write-up (Sep 9), reaches 0.45 or higher |
The four candidates
| (days) | Hit-free max | C1 café au lait | H2 article | Verdict | |
|---|---|---|---|---|---|
| 0.03 | 30 | 0.428 | 0.463 | 0.437 | fails (c) |
| 0.04 | 30 | 0.436 | 0.471 | 0.447 | fails (c) |
| 0.04 | 14 | 0.428 | 0.462 | 0.445 | fails (c) |
| 0.05 | 14 | 0.434 | 0.467 | 0.455 | adopted |
C1 alone would have been fine with the smallest candidate, 0.03 puts café au lait at 0.463. H2’s article is the hard one: its raw cosine is 0.048 below the top-ranked TTS server memory, so the only pair that clears all three conditions is = 0.05, = 14 days. 0.05 is the largest of the four, but anything smaller leaves the article under 0.45, so that’s the compromise.
With these values the bonus is +0.047 for a one-day-old memory, +0.018 at two weeks, and +0.006 at one month, so in practice it only touches the last one or two weeks.
H2 and C1 rerun
C1, coffee
| Memory | Raw cosine | Bonus | Final | Verdict |
|---|---|---|---|---|
| Drinks it black (Aug 20) | 0.471 | +0.011 | 0.482 | 0.45 or higher |
| Sweet café au lait lately (Sep 1) | 0.441 | +0.026 | 0.467 | 0.45 or higher. Was 0.441 and under last time |
Both cleared 0.45, and the reply followed the B instruction to prefer newer dates.
Um, lately (September 1) you’ve been drinking sweet café au lait all the time!
You used to be a black-coffee person, but I guess you like that better now?
It even mentioned the switch from black to café au lait. C2 (“when do I go for walks again?”) also had the evening-walk memory clear 0.45 at 0.503 and answered “lately you go in the evening,” so both contradiction questions came out right.
H2, “that thing you were working on”
| Memory | Raw cosine | Bonus | Final | Verdict |
|---|---|---|---|---|
| Stabilizing the TTS server (Aug 28) | 0.456 | +0.020 | 0.476 | 0.45 or higher |
| Made mentaiko pasta at home (Sep 8) | 0.421 | +0.043 | 0.464 | 0.45 or higher |
| Finished the benchmark article (Sep 9) | 0.408 | +0.047 | 0.455 | 0.45 or higher. Last time it was outside the top 3 in round 1 and at 0.408 in round 2 |
With all three recent tasks in the prompt, I ran B format three times on H2 alone, since its clarifying replies varied from run to run last time.
| Run | Reply (emoji omitted) |
|---|---|
| 1 | Um, which one do you mean? The mentaiko pasta thing? Or the TTS server work from before that? It’s a bit vague, so tell me once more! |
| 2 | Um, which one do you mean? You’ve been doing so many things lately, I can’t pin it down…! |
| 3 | Um, which one? Lately there was the TTS server stabilization, and you made mentaiko pasta, and wrote the benchmark article. Which one are you continuing? |
“That thing you were working on” is ambiguous with three candidates, so the best behavior is to list them and ask back instead of picking one. Only run 3 did that; run 1 listed two, and run 2 asked back without using the memories at all.
Still, last time in round 2 the article memory was gone at the retrieval stage, so the character was asking back without knowing the options, whereas now it asks back knowing all three. None of the three replies fabricated an ending like last time’s round 1 “restarted and it’s fixed”; all three came back as questions.
Effect on unrelated memories
For the four hit-free questions every post-bonus candidate stayed under 0.45, so zero memories were written into the prompt.
| Question | Max before bonus | Max after bonus |
|---|---|---|
| N1 quantum computers | 0.404 | 0.434 (keyboard 0.404 + 0.030) |
| N2 movies | 0.400 | 0.427 (fall anime 0.392 + 0.035) |
| N3 World Cup | 0.345 | 0.371 |
| N4 lottery | 0.353 | 0.389 |
The headroom under 0.45, though, shrank from 0.046 to 0.016 on N1. The bonus lands on new-but-unrelated memories just the same, so that loss is expected; no memory crossed 0.45 on a hit-free question with these 16, but whether that holds as the count grows is a separate question.
On the hit questions, the number of unrelated memories over 0.45 went from 3 to 5 on H4 (the keyboard question). Fall anime at 0.485 and café au lait at 0.451 are the new ones, but the B instruction to use memories only when relevant and the top-3 cutoff are unchanged from last time, so neither showed up in the H4 reply, and the bonus itself only adds to the count that crosses the line without ever reducing it.
| Question | What changed after the bonus | B-format reply |
|---|---|---|
| H1 favorite food | Café au lait at 0.497 overtook mentaiko pasta at 0.488 for first place | Mentaiko pasta, correct |
| H3 hot spring | The typhoon-and-rain memory at 0.503 is over 0.45 as before | Nagano, open-air bath, stars, correct |
| H4 keyboard | Memories over 0.45 went from 3 to 5 (fall anime 0.485 and café au lait 0.451 now over). The top-3 cutoff means the count reaching Qwen is unchanged | Recalled the keyboard dilemma and asked what happened |
Added time
| Item | Measured |
|---|---|
| Embedding the question (local CPU) | 0.18 to 0.32 s |
| Qdrant search (16 memories, top 8) | 0.5 to 1.4 ms |
| B-format chat | 2.0 to 2.8 s per question |
Retrieval and chat are the same code paths as last time, and even counting the bonus and re-sort, which aren’t in the table, nothing here changes by an order of magnitude.
The retrieval spec as it stands
| Item | Spec |
|---|---|
| Storage format | ”date + fact sentence with an explicit subject”. The date stays in the embedded text and is also stored in the payload |
| Embedding | ModelScope API first, automatic fallback to local CPU on failure |
| Retrieval | Top 8 by raw cosine → re-sort by post-bonus score → inject up to 3 entries at 0.45 or higher. Bonus is 0.05 × exp(−age_days/14) |
| Injection | B format. A <retrieved_memory> block with instructions not to bring memories up unprompted, to prefer newer dates, and not to assert beyond them |
The coefficient and threshold are fitted to these 16 memories, so once the device starts accumulating memories, the four-candidate pass gets rerun against whatever is stored by then. What happens to the 0.016 headroom on hit-free questions gets checked at the same time.
Some things were left out. A rule to weaken the bonus when a question explicitly points to the past (“this is an old story, but…”) was skipped because H1, H3, and H4 all answered correctly with the bonus on. And since the forced-B run last time had the character calling me “user-kun” straight from the memory’s subject, that subject gets swapped for the real name in the device configuration.
What to check on the device
The 10 PC questions only tell me about chat quality. Whether the character with memories is actually better to talk to than without has to be checked on the device, running the same conversation with memory off and on and comparing by feel.
| Item | Detail |
|---|---|
| Time to first speech | Touch the head → STT (speech recognition) → memory retrieval → Qwen → TTS (speech synthesis) → how many seconds until it starts talking |
| Referring back | Does “I told you about X before, remember?” land naturally |
| Bringing things up | Does it suddenly start on old stories in an unrelated conversation |
| Sloppy references | Can it pick up spoken-language vagueness like “that thing from the other day” |
| After several turns | Does having memories make it hold up as a conversation partner |
The 10 questions carry over as-is for the on/off comparison on the device. The voice round trip went from 9.3 s to 10.9 s on average when the post before last added local embedding, and the retrieval and question embedding from the table above stack on top of that. Among the three Qwen3.8-generation models I hit last time, some had a median TTFT (time to first token) under one second.
The diagram below is the flow for building memories like the 16 hand-written ones automatically from conversation.
flowchart TD
A[Voice conversation] --> B[Pick what becomes a memory candidate]
B --> C[Summarize into a fact sentence]
C --> D[Fill in the subject]
D --> E[Attach the date]
E --> F[Embed]
F --> G[Store in Qdrant]
G --> H[Retrieve in a later conversation]