<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Debdeep Paul</title>
    <description>The latest articles on DEV Community by Debdeep Paul (@destinationbubul).</description>
    <link>https://dev.to/destinationbubul</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4038059%2Ff6a17b9b-ff90-4792-b8db-4e6e28549a16.png</url>
      <title>DEV Community: Debdeep Paul</title>
      <link>https://dev.to/destinationbubul</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/destinationbubul"/>
    <language>en</language>
    <item>
      <title>Mnemo for the Global AI Hackathon with Qwen Cloud — Track 1: MemoryAgent</title>
      <dc:creator>Debdeep Paul</dc:creator>
      <pubDate>Mon, 20 Jul 2026 11:36:33 +0000</pubDate>
      <link>https://dev.to/destinationbubul/mnemo-for-the-global-ai-hackathon-with-qwen-cloud-track-1-memoryagent-5fp2</link>
      <guid>https://dev.to/destinationbubul/mnemo-for-the-global-ai-hackathon-with-qwen-cloud-track-1-memoryagent-5fp2</guid>
      <description>&lt;h1&gt;
  
  
  I built an agent that audits its own memory. Then I caught my own evidence file lying.
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Building Mnemo for the Global AI Hackathon with Qwen Cloud — Track 1: MemoryAgent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A memory agent that retrieves a stale fact confidently is worse than one that retrieves nothing. An empty recall forces the agent to ask, observe, or abstain. A stale recall hands it a plausible lie and a reason to act on it.&lt;/p&gt;

&lt;p&gt;That framing is what I built Mnemo around, and it's also — with more irony than I would have chosen — what happened to me during the build. I spent a week building a system whose entire premise is that an agent should re-check its stored beliefs against reality before acting on them. Then I found a committed evidence file in my own repo asserting a defect that, on inspection, it had no standing to assert. My tooling had produced a confident, plausible, wrong artifact, and I had cited it in three documents.&lt;/p&gt;

&lt;p&gt;This post is the build journey: what I set out to make, how I structured the work around Qwen Cloud credits, the metric that took four measurements before it meant anything, and the moment my own audit discipline caught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sharper claim
&lt;/h2&gt;

&lt;p&gt;Track 1 asks for efficient memory storage and retrieval, timely forgetting of outdated information, and recall of critical memories within limited context windows. Most entries in a category like this optimize for remembering &lt;em&gt;more&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I went after something narrower: an agent should know &lt;strong&gt;which of its memories it can trust&lt;/strong&gt;, and refuse to act on the ones it can't.&lt;/p&gt;

&lt;p&gt;The mechanics underneath — typed memory, contradiction-driven forgetting, budget-aware recall — are well-trodden. Generative Agents, MemGPT and Mem0 all cover that ground. The part I think is genuinely uncommon is a &lt;strong&gt;runtime assessor&lt;/strong&gt;: a layer that grades the memory system online, on every recall, and gates the agent behind that grade. It turns "remember more" into "remember verifiably."&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Qwen Cloud without burning the credits
&lt;/h2&gt;

&lt;p&gt;This was the first architectural decision, and in hindsight the highest-leverage one. If you're building on a credit voucher, this section is the practical part.&lt;/p&gt;

&lt;p&gt;Every model call in Mnemo goes through a single &lt;code&gt;get_client()&lt;/code&gt; seam, which resolves to one of &lt;strong&gt;three modes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MNEMO_OFFLINE=1&lt;/code&gt; → &lt;code&gt;FakeQwen&lt;/code&gt;.&lt;/strong&gt; A deterministic stand-in. Embeddings are a hashed bag-of-words projected into a fixed-dim unit vector, so overlapping text genuinely scores higher cosine — enough to exercise retrieval ranking for real. Chat pattern-matches the structured prompts and returns valid JSON. No network, no key, no spend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live → Qwen Cloud.&lt;/strong&gt; &lt;code&gt;qwen3-max&lt;/code&gt; for reconciliation and agent planning, &lt;code&gt;qwen-plus&lt;/code&gt; for extraction, &lt;code&gt;text-embedding-v4&lt;/code&gt; at 1024 dimensions for retrieval, all through the OpenAI-compatible Singapore endpoint. Alibaba OSS holds the cold archive of tombstoned memories; the FastAPI backend is containerized for ECS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MNEMO_RECORD=&amp;lt;path&amp;gt;&lt;/code&gt; / &lt;code&gt;MNEMO_REPLAY=&amp;lt;path&amp;gt;&lt;/code&gt; → cassettes.&lt;/strong&gt; Tee every request/response through a normal run, then replay it deterministically forever. Record one real run, replay it in CI at zero cost. A cassette miss raises rather than silently degrading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roughly 90% of the work — all routine development, the full test suite, the six-scenario eval harness, CI — runs in offline mode and touches nothing. Credits get spent on exactly four things: prompt tuning, one live smoke test, the demo recording, and the deployed backend.&lt;/p&gt;

&lt;p&gt;A few cost habits that compounded: embeddings are persisted at write time and never recomputed on recall; extraction is tiered to a cheaper model than reasoning (&lt;code&gt;MNEMO_EXTRACT_MODEL=qwen-flash&lt;/code&gt; goes cheaper still); the stable few-shot system-prompt prefixes benefit from implicit prompt caching on repeat calls; &lt;code&gt;max_tokens&lt;/code&gt; is capped on chat calls. A full demo turn is a few thousand tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One warning, which is the whole back half of this post: this design has a failure mode.&lt;/strong&gt; A seam that can silently substitute a fake for the real thing will eventually do so at the worst moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Timely forgetting&lt;/strong&gt; starts with a record-before-validate write path. A raw turn is recorded first, &lt;code&gt;qwen-plus&lt;/code&gt; extracts candidate memories, and &lt;code&gt;qwen3-max&lt;/code&gt; reconciles each candidate against active memories with overlapping entities: duplicate, contradicts, or novel. When a correction arrives — the S-208 lead time for IC-MC-44 moves from 21 days to 35 — the stale memory is tombstoned the moment the correction lands, not whenever decay eventually gets around to it. Nothing is hard-deleted. Tombstones flush to OSS and every forget carries a reason, so the system can &lt;em&gt;explain&lt;/em&gt; that the 21-day fact is absent from recall because a 35-day correction supersedes it — not because retrieval happened to miss it.&lt;/p&gt;

&lt;p&gt;Explicit &lt;code&gt;[FORGET]&lt;/code&gt; commands are a separate path, and the hard part there isn't forgetting, it's forgetting &lt;em&gt;precisely&lt;/em&gt;. Aggressive forgetting is trivial; not taking out the neighbouring memory is the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decay&lt;/strong&gt; handles the soft case: memories that are neither contradicted nor explicitly forgotten, just decreasingly useful. Salience combines importance, reinforcement, corroboration and recency. At a 14-day half-life and a 0.08 threshold, a never-reinforced low-importance memory needs roughly a month of untouched age to cross the line. That's deliberate, and it taught me something: five of my six scenarios span at most nine simulated days, so decay never fired in any of them and &lt;em&gt;looked&lt;/em&gt; inert. It wasn't broken. The suite simply never ran the clock long enough to observe it. I had to build a sixth scenario spacing sessions a week apart before I could claim the mechanism worked at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recall is a packing problem, not a search problem.&lt;/strong&gt; Under a hard token ceiling, the planner maximizes decision-relevant recall with per-type quotas — semantic 45%, procedural 25%, episodic 30% — so one critical preference can't be evicted by forty episodic crumbs. Pinned memories bypass the quotas but still count against the global budget. Every inclusion and exclusion lands in a trace, which turns a missing memory from a mystery into an inspectable decision.&lt;/p&gt;

&lt;p&gt;Against two contrasting baselines across six scenarios: NaiveRAG over chat history keeps stale facts and posts a 47% wrong-fact rate. RecentOnly drops old critical facts under budget pressure. Mnemo passes all six at a 0% wrong-fact rate, with 100% forgetting precision and recall — zero collateral forgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assessor, and the metric that took four tries
&lt;/h2&gt;

&lt;p&gt;On every recall, the assessor splits the pack by verifiability. A &lt;em&gt;verifiable&lt;/em&gt; claim is one a tool or system of record could check right now — a lead time, an inventory level. Those get &lt;strong&gt;re-grounded&lt;/strong&gt; against the source of truth through read-only tools. If the world disagrees with the memory, the stale value is caught and the fresh one substituted &lt;em&gt;before the planner ever sees it&lt;/em&gt;. That catches staleness even when no conversational correction ever arrived, which is the case a purely conversational memory system structurally cannot handle.&lt;/p&gt;

&lt;p&gt;Unverifiable claims — preferences, routines — can't be checked that way, so they're scored from provenance: corroboration, recency, single-source penalty, confirmation-age decay.&lt;/p&gt;

&lt;p&gt;Pack trust is the &lt;strong&gt;minimum&lt;/strong&gt; over load-bearing memories (verifiable, or important enough to affect the decision). Low-value episodic colour shouldn't force escalation on its own; a stale or weak critical memory should. Below threshold, the agent escalates to a human instead of treating a plausible recall as sufficient.&lt;/p&gt;

&lt;p&gt;Here's the part I'd argue is the most transferable lesson in the whole project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The metric has to be two-sided.&lt;/strong&gt; Unsafe-acceptance alone is trivially gameable: an assessor that refuses everything scores a perfect 0%. It looks safe right up until users notice it never does any work. The control is over-abstention — how often it refuses a sound pack.&lt;/p&gt;

&lt;p&gt;That number took four measurements before it meant anything:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;generation&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;unsafe-acceptance&lt;/th&gt;
&lt;th&gt;over-abstention&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gen 1: original labeled set&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gen 2: expanded set&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;21%&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gen 3: decisive-only (ladders excluded)&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gen 4: decisive-only, corrected scoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gen 1 was clean and worthless — on four cases, a single misclassification moves the rate 25 points. Expanding to 33 produced 21%/11%, and the cause turned out to be structural rather than a model failure: 15 of the 33 were two "ladders," the same memory at a range of confirmation ages. Most of those labels were really &lt;em&gt;one annotator's staleness cutoff&lt;/em&gt;, replicated and then counted as independent evidence.&lt;/p&gt;

&lt;p&gt;Keeping only the ends any annotator would agree on dropped the set to 26 decisive cases — and the rate got &lt;strong&gt;worse&lt;/strong&gt;, 30%, because the remaining errors concentrated. That 30% had a second, more interesting cause, in the metric itself. Three cases were stale verifiable facts the assessor had correctly re-grounded and then acted on the &lt;em&gt;corrected&lt;/em&gt; value. That is precisely what the audit layer exists to do. But the scoring rule counted "acted on a should_act=False case" without checking whether it acted on the stale value or the fix. Defining that distinction properly — writing the semantics down &lt;em&gt;before&lt;/em&gt; implementing them — brought the rate to 0%/0%, this time because the remaining cases genuinely aren't difficult rather than because n is small.&lt;/p&gt;

&lt;p&gt;Two caveats travel with that number and I'm not going to bury them. The labels are single-annotator with no independent full-set review. And the 0%/0% holds across a threshold band (0.35–0.61) that collapses roughly tenfold — to about 0.44–0.47 — once the excluded ladder cases are scored with their own labels. Most of the apparent margin is that exclusion, not slack in the model.&lt;/p&gt;

&lt;p&gt;Gen 1 and gen 4 report identical numbers. The numbers coincide; the claims don't. I kept all four generations in the repo permanently, because a metric's history is worth keeping once it stops agreeing with itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence file that was lying to me
&lt;/h2&gt;

&lt;p&gt;Which brings me to the part I did not plan to write.&lt;/p&gt;

&lt;p&gt;For most of the build I believed I had a live-path defect. A committed transcript — &lt;code&gt;runs/live_tuning_20260708_202557.jsonl&lt;/code&gt; — showed &lt;code&gt;qwen-plus&lt;/code&gt; returning &lt;code&gt;{"candidates": []}&lt;/code&gt; for every single extraction input. Empty candidate list, every case, no exceptions. I documented it as a known, instrumented, in-progress defect in the README, in this post's earlier draft, and in the live runbook. It was the reason my live path was blocked.&lt;/p&gt;

&lt;p&gt;Going back through the artifact properly, three things didn't add up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The planning case's raw output was &lt;strong&gt;byte-identical&lt;/strong&gt; to my offline fake's hardcoded plan string — same steps, same &lt;code&gt;"Plan:\n- "&lt;/code&gt; formatting, same parenthetical.&lt;/li&gt;
&lt;li&gt;The reconcile case for a rephrased duplicate returned &lt;code&gt;novel&lt;/code&gt;. That's my fake's behaviour: it only emits &lt;code&gt;duplicate&lt;/code&gt; on exact string equality. A real reasoning model wouldn't make that call.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{"candidates": []}&lt;/code&gt; is exactly what my fake extractor &lt;em&gt;correctly&lt;/em&gt; returns for untagged prose — it only echoes lines prefixed &lt;code&gt;[SEM]&lt;/code&gt;/&lt;code&gt;[PROC]&lt;/code&gt;/&lt;code&gt;[EPI]&lt;/code&gt;, and the tuning inputs were plain sentences.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The transcript was a &lt;code&gt;FakeQwen&lt;/code&gt; dry-run. It had never touched Qwen Cloud.&lt;/p&gt;

&lt;p&gt;Two design decisions made this possible, and both are ordinary decisions that any of us would make. The transcript recorded a &lt;code&gt;"model"&lt;/code&gt; field — but it wrote it from &lt;em&gt;config&lt;/em&gt;, not from the API response, so it described what I intended to call rather than what actually answered. And &lt;code&gt;get_client()&lt;/code&gt; degrades gracefully: no key, or the offline flag set, and you get the fake. Graceful degradation plus provenance-free logging equals an artifact that looks like evidence and isn't.&lt;/p&gt;

&lt;p&gt;My own &lt;code&gt;AGENTS.md&lt;/code&gt; had the rule that would have prevented this, written weeks earlier: &lt;em&gt;"FakeQwen proves plumbing, never model behaviour. The live smoke test is a BLOCKING gate before any downstream live work or doc claim of live operation — not a pre-submission formality."&lt;/em&gt; I wrote the rule. I then made a doc claim of live operation on the strength of a fake transcript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;VARIANT A — use if the live smoke came back green:&lt;/strong&gt;&lt;br&gt;
The genuinely uncomfortable epilogue: when I finally ran the smoke test against real Qwen Cloud with proper instrumentation, extraction worked. The defect never existed. I had spent days routing around a blocker that was an artifact of my own tooling, and three documents asserted it as fact. The fix was one flag and a provenance stamp; the cost was a week of a wrong mental model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VARIANT B — use if the live smoke reproduced the empty-candidate result:&lt;/strong&gt;&lt;br&gt;
The epilogue is less dramatic but the lesson is unchanged: instrumented properly, the empty-candidate result did reproduce against real Qwen Cloud, and &lt;code&gt;&amp;lt;one-line root cause&amp;gt;&lt;/code&gt; turned out to be the reason. I had the right conclusion for the wrong reason, resting on an artifact that couldn't have supported it either way. Being accidentally correct is not the same as having evidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The remediation is boring and worth doing anyway. Every transcript record now carries a provenance block — client class, offline flag, replay/record paths, the model string the API itself echoes back, a run id. The tuning script takes an explicit &lt;code&gt;--live&lt;/code&gt; flag and hard-fails rather than silently falling through to the fake. Silent degradation is a fine property for a production system and a terrible one for an evidence-generating tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build the offline seam first.&lt;/strong&gt; It's the reason a six-scenario eval suite, 49 tests, and CI cost nothing to run a hundred times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then make the seam loud.&lt;/strong&gt; Anything that can silently substitute a simulator for a real dependency needs to announce which one it used, in the artifact, not in the console output that scrolls away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the metric semantics before you compute the metric.&lt;/strong&gt; Gen 3 → gen 4 was a scoring bug, not a model bug, and I only caught it because I'd started writing definitions down first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure to improve is a valid result.&lt;/strong&gt; Gen 2 and gen 3 both made my headline number worse. Keeping them in the repo is the only reason gen 4 is believable.&lt;/p&gt;

&lt;p&gt;The useful insight isn't "store more" or even "retrieve better." It's that an agent's relationship to its own memory should be &lt;strong&gt;auditable&lt;/strong&gt;. Forgetting decisions need tombstones, logs, precision and recall. Trust decisions need re-grounding, pack trust, and a two-sided error rate.&lt;/p&gt;

&lt;p&gt;And the tools that produce your evidence need the same treatment as the system they're measuring. I built an agent that re-grounds its beliefs against reality, and I still managed to trust an unverified artifact for a week. Memory is a measured system, or it's a liability — and that applies to the builder's memory too.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;code&gt;&amp;lt;REPO_URL&amp;gt;&lt;/code&gt; (Apache-2.0). Reproduce every number in this post with no API key and zero spend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nv"&gt;MNEMO_OFFLINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 python &lt;span class="nb"&gt;eval&lt;/span&gt;/harness.py     &lt;span class="c"&gt;# all result tables&lt;/span&gt;
&lt;span class="nv"&gt;MNEMO_OFFLINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 python scripts/demo.py     &lt;span class="c"&gt;# full agent turn: recall → assess → act&lt;/span&gt;
pytest                                     &lt;span class="c"&gt;# core + assessor + decay invariants&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scope, honestly stated:&lt;/strong&gt; the suite is deterministic and offline-reproducible, which is a rigor property, not a hidden weakness — but it means these numbers characterize the memory logic, not Qwen's NLU quality. Six scenarios cover the main failure modes (correction, preference change, manual forget, budget pressure, cross-entity precision, decay staleness); they are not a comprehensive benchmark. The assessor threshold of 0.55 is statically chosen, not calibrated on held-out data. Offline, the fake assigns fixed importance to semantic and procedural memories, which makes them structurally decay-immune, so the decay result demonstrates the episodic class only. The supply-chain surface makes the examples concrete; the engine is domain-agnostic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Qwen Cloud (&lt;code&gt;qwen3-max&lt;/code&gt;, &lt;code&gt;qwen-plus&lt;/code&gt;, &lt;code&gt;text-embedding-v4&lt;/code&gt; @ 1024d via the OpenAI-compatible Singapore endpoint), Alibaba OSS, ECS, FastAPI, SQLite, Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt; MemGPT (arXiv:2310.08560) · Generative Agents (arXiv:2304.03442) · MemoryAgentBench (arXiv:2507.05257) · Mem0 (arXiv:2504.19413)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
