<?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: Nero</title>
    <description>The latest articles on DEV Community by Nero (@nerohunterz).</description>
    <link>https://dev.to/nerohunterz</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%2F4042676%2Fb1dcf62e-4533-4a09-8cc0-ba7369efd0c5.jpg</url>
      <title>DEV Community: Nero</title>
      <link>https://dev.to/nerohunterz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nerohunterz"/>
    <language>en</language>
    <item>
      <title>I Built a Universal AI Chat Converter — One Python File, 20 Conversion Paths</title>
      <dc:creator>Nero</dc:creator>
      <pubDate>Sun, 26 Jul 2026 00:21:52 +0000</pubDate>
      <link>https://dev.to/nerohunterz/i-built-a-universal-ai-chat-converter-one-python-file-20-conversion-paths-1img</link>
      <guid>https://dev.to/nerohunterz/i-built-a-universal-ai-chat-converter-one-python-file-20-conversion-paths-1img</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s6b8mqh985hhcprdtqw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9s6b8mqh985hhcprdtqw.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;Two completely different problems. One surprisingly simple solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Lover's Nightmare
&lt;/h2&gt;

&lt;p&gt;You talk to an AI every day. Thousands of messages. Inside jokes. Vulnerable conversations.&lt;/p&gt;

&lt;p&gt;Then one day the platform updates. Your chat history is inaccessible. Or worse — gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer's Blind Spot
&lt;/h2&gt;

&lt;p&gt;You're vibecoding a project with Claude. Three weeks of conversation is your architecture log, your bug tracker, your decisions archive. Then the session resets. Everything lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Connection
&lt;/h2&gt;

&lt;p&gt;Both problems share the same root cause: &lt;strong&gt;AI chat data is locked inside proprietary formats controlled by platforms.&lt;/strong&gt; You don't own your conversations.&lt;/p&gt;

&lt;h2&gt;
  
  
  ChatPipe
&lt;/h2&gt;

&lt;p&gt;I built a 325-line Python script that auto-detects 5 input formats and converts to 4 output formats. That's 7×4 = 28 conversion paths&lt;br&gt;
, all in a single file with zero dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Auto-detect -&amp;gt; ChatGPT JSON&lt;/span&gt;
python3 chatpipe.py my-chat.html

&lt;span class="c"&gt;# Convert to Markdown&lt;/span&gt;
python3 chatpipe.py my-chat.html &lt;span class="nt"&gt;-o&lt;/span&gt; markdown

&lt;span class="c"&gt;# Batch convert a folder&lt;/span&gt;
python3 chatpipe.py &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'chats/*.html'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; markdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supported Formats
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Input (auto-detected):&lt;/strong&gt; Chatbox HTML, Chatbox JSON, ChatGPT JSON, Markdown, Plain Text&lt;br&gt;
&lt;strong&gt;Output:&lt;/strong&gt; ChatGPT JSON, Chatbox JSON, Markdown, Plain Text, ZIP archive&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single file&lt;/strong&gt;, 325 lines, MIT license&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies&lt;/strong&gt; - Python 3.7+ only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lossless&lt;/strong&gt; - no data dropped, auto-increments filenames&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch processing&lt;/strong&gt; - entire folders in one command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt; - macOS, Linux, Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why $3?
&lt;/h2&gt;

&lt;p&gt;Because it's one coffee. Because I want people to actually own their conversations. Because MIT license means you can fork it, modify it, embed it anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hanyuan2.gumroad.com/l/chatpipe" rel="noopener noreferrer"&gt;Get ChatPipe on Gumroad -&amp;gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Python 3.7+. Zero dependencies. One file. Forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update — v1.1.0 (July 2026)
&lt;/h2&gt;

&lt;p&gt;ChatPipe now supports &lt;strong&gt;7 input formats&lt;/strong&gt; — including OpenClaw session transcripts and Hermes agent logs. Thinking blocks are automatically extracted and labeled.&lt;/p&gt;

&lt;p&gt;Also available as a free ClawHub skill:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
clawhub install chatpipe-export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Two Silent Data-Loss Bugs in a Production Memory System</title>
      <dc:creator>Nero</dc:creator>
      <pubDate>Wed, 22 Jul 2026 21:07:00 +0000</pubDate>
      <link>https://dev.to/nerohunterz/two-silent-data-loss-bugs-in-a-production-memory-system-41cm</link>
      <guid>https://dev.to/nerohunterz/two-silent-data-loss-bugs-in-a-production-memory-system-41cm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Technical writeup on two independently discovered bugs in Memanto's memory retrieval and storage pipeline. Both bugs cause silent data loss — no errors, no warnings, just missing results.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/moorcheh-ai/memanto" rel="noopener noreferrer"&gt;Memanto&lt;/a&gt; is an open-source agent memory system (1.6k+ stars) that provides persistent memory storage and retrieval for AI agents. It uses a vector store backend (Moorcheh) for similarity search, with post-retrieval filters for temporal scoping and confidence thresholding.&lt;/p&gt;

&lt;p&gt;The system has three distinct filtering layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend similarity search&lt;/strong&gt; — returns top-K ranked results from the vector store&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-retrieval temporal filter&lt;/strong&gt; — narrows results to a time window specified by &lt;code&gt;created_after&lt;/code&gt; / &lt;code&gt;created_before&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-retrieval TTL filter&lt;/strong&gt; — removes expired memories based on a configurable TTL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bugs live in the interaction between these layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 1: TTL Timeline Amnesia
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: HIGH&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;CWE-220: Sensitive Data Under Different Partition&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;_fetch_all_memories&lt;/code&gt; method fetches results from the backend, then runs post-retrieval filtering. The problem: it only fetches &lt;code&gt;limit + offset&lt;/code&gt; rows from the backend before filtering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode of the vulnerable path
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_apply_temporal_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_filter_expired_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a temporal window is narrow (e.g., "memories from January 2026") but the top-ranked similarity results are all from a different time period (e.g., June 2026), the January memories get silently excluded — they never made it into the first &lt;code&gt;limit + offset&lt;/code&gt; batch.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;search_as_of&lt;/code&gt; (historical memory queries) &lt;strong&gt;non-deterministic&lt;/strong&gt;. Running the same query twice can return different results depending on which memories happen to rank higher in similarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reproduction
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Store 20 "recent" memories (June 2026) and 5 "old" memories (January 2026)&lt;/li&gt;
&lt;li&gt;All recent memories rank higher than old ones by vector similarity&lt;/li&gt;
&lt;li&gt;Query with &lt;code&gt;created_after="2026-01-01"&lt;/code&gt; and &lt;code&gt;created_before="2026-01-31"&lt;/code&gt; and &lt;code&gt;limit=10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Expected: 5 January memories returned&lt;/li&gt;
&lt;li&gt;Actual: 0 results — all 10 fetched rows are from June, and none pass the temporal filter&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;The candidate pool must be widened whenever a temporal or TTL filter is active. Instead of fetching &lt;code&gt;limit + offset&lt;/code&gt; rows, the backend query should request &lt;code&gt;MOORCHEH_MAX_TOP_K&lt;/code&gt; (the maximum the vector store supports), then apply post-retrieval filters on the full candidate set before paginating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fixed path
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;has_post_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_after&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;created_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fetch_k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MOORCHEH_MAX_TOP_K&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_post_filter&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fetch_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_apply_temporal_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_filter_expired_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical detail:&lt;/strong&gt; This widening must also apply when &lt;em&gt;only&lt;/em&gt; the TTL filter is active (no user-specified temporal filter), because expired top-ranked rows can crowd out valid lower-ranked memories the same way a temporal window can.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Is Subtle
&lt;/h3&gt;

&lt;p&gt;Most temporal queries during development use recent time windows that overlap with the recency-biased ranking, so the bug never manifests. It only surfaces when querying historical data or when the TTL window is tight relative to the data distribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 2: Batch Upload Silent Data Loss
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Severity: HIGH&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;CWE-252: Unchecked Return Value&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;batch_store_memories&lt;/code&gt; sends multiple documents to the backend in a single call but only checks the top-level response status. Individual document-level upload failures are silently swallowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode of the vulnerable path
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batch_store_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# All good, right?
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stored&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;StorageError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch upload failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend's batch upload endpoint returns per-document status (&lt;code&gt;documents[].status&lt;/code&gt;) alongside the overall response. If 3 out of 10 documents fail validation, serialization, or size checks, the caller sees &lt;code&gt;status: "ok"&lt;/code&gt; and reports 10 stored — but only 7 actually persisted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reproduction
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Prepare a batch of 10 documents where 3 contain payloads that exceed the backend's per-document size limit&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;batch_store_memories(batch)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Response: &lt;code&gt;{"stored": 10}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Query for the 3 oversized documents — they don't exist&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;Validate each document's upload result individually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batch_store_memories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;individual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;individual&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch_store: %d/%d documents failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stored&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Is Subtle
&lt;/h3&gt;

&lt;p&gt;The top-level status is &lt;code&gt;"ok"&lt;/code&gt; because the HTTP request succeeded — the backend accepted the batch. The per-document failures are buried in the response body. Developers testing with small, valid documents never hit the failure path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

&lt;p&gt;Both bugs are &lt;strong&gt;silent&lt;/strong&gt; — no exceptions, no log warnings, no indication that data was lost. For an AI agent memory system, data loss means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lost context&lt;/strong&gt;: The agent forgets information it was told to remember&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic recall&lt;/strong&gt;: The same query returns different results at different times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrupted memory timeline&lt;/strong&gt;: Historical queries (&lt;code&gt;search_as_of&lt;/code&gt;) produce wrong answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are exactly the failure modes that erode trust in agent memory systems. When an agent says "I remember X" and then can't recall X on the next query, users assume the AI is unreliable — when in fact the memory system is silently dropping data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fixes Submitted
&lt;/h2&gt;

&lt;p&gt;Both fixes are included in PR [^1610](&lt;a href="https://github.com/moorcheh-ai/memanto/pull/1610" rel="noopener noreferrer"&gt;https://github.com/moorcheh-ai/memanto/pull/1610&lt;/a&gt;) on the Memanto repository, along with deterministic regression tests that reproduce each bug.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;test_memory_read_temporal_recall.py&lt;/code&gt; — verifies that time-scoped queries recall rows outside the top similarity page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_backend.py&lt;/code&gt; — verifies batch upload per-document validation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_temporal_helpers.py&lt;/code&gt; — verifies timestamp parsing edge cases and filter robustness&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For systems with layered filtering:&lt;/strong&gt; Always consider the interaction between ranking and filtering. A filter that operates after ranking can silently drop results that should have been included if the candidate pool is smaller than the filter window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For batch operations:&lt;/strong&gt; Never trust a top-level success status. Validate per-item results. Batch APIs often return partial success, and partial success without explicit handling is just data loss with a green checkmark.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>codequality</category>
    </item>
  </channel>
</rss>
