<?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: Carloshperc</title>
    <description>The latest articles on DEV Community by Carloshperc (@carlosperc).</description>
    <link>https://dev.to/carlosperc</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%2F671471%2Fbd59aff3-b2dd-45ea-8562-53067045479f.jpeg</url>
      <title>DEV Community: Carloshperc</title>
      <link>https://dev.to/carlosperc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlosperc"/>
    <language>en</language>
    <item>
      <title>AI agent memory you verify, not just trust</title>
      <dc:creator>Carloshperc</dc:creator>
      <pubDate>Fri, 03 Jul 2026 18:34:48 +0000</pubDate>
      <link>https://dev.to/carlosperc/ai-agent-memory-you-verify-not-just-trust-2144</link>
      <guid>https://dev.to/carlosperc/ai-agent-memory-you-verify-not-just-trust-2144</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; solves static knowledge: commands, conventions, preferences and rules. What it does not solve is episodic memory: yesterday's investigated bug, the approach that already failed, the decision that explains why a PR was abandoned. That is why I built &lt;a href="https://github.com/carloshpdoc/agent-memory-hub" rel="noopener noreferrer"&gt;&lt;strong&gt;agent-memory-hub&lt;/strong&gt;&lt;/a&gt;. The turn came later: I discovered the memory itself could fail silently. This article is the story of how persistent memory became verifiable memory, and how a product idea became an open-source tool I actually use every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this article is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Devs using Claude Code, Codex or Cursor who feel the agent knows the rules but forgets the story.&lt;/li&gt;
&lt;li&gt;Anyone maintaining &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; or persistent memory who wants to separate static knowledge from episodic memory.&lt;/li&gt;
&lt;li&gt;Anyone who enjoys an honest post-mortem: a silent pipeline bug, observability and a deliberate product call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The whole story in one line
&lt;/h2&gt;

&lt;p&gt;If I told this in the most obvious way, the summary would be: I built a memory, found bugs, added observability. But that summary starts in the wrong place. The story did not become interesting because a bug appeared. It became interesting because the bug showed I was trusting a memory without a receipt.&lt;/p&gt;

&lt;p&gt;That order changes the weight of everything. agent-memory-hub started as a continuity layer for coding agents. Then it became a practical lesson about ingestion: anything that captures data automatically also needs to prove that it captured it. Without that, memory only looks like memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pain wasn't intelligence. It was memory.
&lt;/h2&gt;

&lt;p&gt;Over the last few months I practically stopped coding without AI agents. Claude Code, Codex, Cursor: when you give them enough context, each tool gets very good at entering a project and producing.&lt;/p&gt;

&lt;p&gt;So I did what almost everyone does: I wrote a good &lt;code&gt;CLAUDE.md&lt;/code&gt;. I put in build commands, conventions, how I like tests written, how I organize modules, how I open PRs. It worked very well, until I noticed a curious split: the agent knew &lt;strong&gt;how I wanted it to work&lt;/strong&gt;, but it did not remember &lt;strong&gt;what had already happened&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It knew how to compile. But it did not remember that yesterday we had already tried another approach. It knew how to run tests. But it forgot that a bug had already been investigated for two hours. It knew the rules. But it did not know the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The habit that masked the problem
&lt;/h2&gt;

&lt;p&gt;For a while I compensated in the most human way possible: by repeating myself. I would open a new session and dump context. "Yesterday we tried this." "Do not touch that layer because it has odd coupling." "This test fails when it runs in parallel." "The previous PR decision was to take another path."&lt;/p&gt;

&lt;p&gt;The curious thing is that this repetition felt like a natural part of using agents. Since the answer improved after I explained everything again, I did not see it as a system failure. It felt like an entry ritual: before working, recap. The problem is that manual ritual charges interest when you move across projects, machines and tools.&lt;/p&gt;

&lt;p&gt;That was the first important insight: the agent did not only need better instructions. It needed continuity. And continuity is not a long prompt. It is the ability to carry consequences from one session to the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are two kinds of knowledge
&lt;/h2&gt;

&lt;p&gt;Once I separated the problem, it became clear that I was calling two different things "context". One is static: how to compile, how to deploy, how to structure a module, how to write commits.&lt;/p&gt;

&lt;p&gt;That knowledge belongs in &lt;code&gt;CLAUDE.md&lt;/code&gt;. The other part changes every day: yesterday's investigated bug, the decision made in the last session, the experiment that failed, the reason a PR was abandoned. That does not belong in &lt;code&gt;CLAUDE.md&lt;/code&gt;, because the file would need to be rewritten constantly. It is memory. Episodic, living, automatic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CLAUDE.md / AGENTS.md&lt;/th&gt;
&lt;th&gt;agent-memory-hub&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How to work&lt;/td&gt;
&lt;td&gt;What already happened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static&lt;/td&gt;
&lt;td&gt;Dynamic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Written by you&lt;/td&gt;
&lt;td&gt;Captured automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rules&lt;/td&gt;
&lt;td&gt;History&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is where &lt;strong&gt;agent-memory-hub&lt;/strong&gt; was born. Not to replace &lt;code&gt;CLAUDE.md&lt;/code&gt;, but to solve exactly what it was never meant to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted the memory to remember
&lt;/h2&gt;

&lt;p&gt;When you use agents occasionally, repeating context feels like an annoyance. When they enter the daily workflow, it becomes operational cost. The problem was not storing "preferences"; it was preserving continuity between sessions that, to the tool, looked independent.&lt;/p&gt;

&lt;p&gt;In practice, I wanted memory to keep five kinds of episodes: decisions made, hypotheses discarded, bugs already investigated, PR state of mind and small project patterns that only show up after several conversations.&lt;/p&gt;

&lt;p&gt;That boundary protected the project from becoming a dump of everything. If everything is memory, nothing is memory. The value was in capturing what I would actually forget between sessions, especially when I moved across machines, tools and contexts.&lt;/p&gt;

&lt;p&gt;From there, the question stopped being "which memory tool exists?" and became something else: what kind of system could capture those episodes without trapping my history inside a single tool?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it myself
&lt;/h2&gt;

&lt;p&gt;I could have stopped there and adopted an existing tool. There are good options for agent memory, and I was not trying to reinvent vector search for sport. But my problem had very personal requirements: work across Claude Code and Codex, cross machines, remain mine when I changed tools, and be auditable with a simple SQL query.&lt;/p&gt;

&lt;p&gt;Choosing Postgres via Supabase was less about technical glamour and more about predictability. I wanted data ownership, &lt;code&gt;pg_dump&lt;/code&gt;, direct inspection, a table I could open without asking any product for permission. For personal work memory, that matters. My session history is part of my process, not just a tool cache.&lt;/p&gt;

&lt;p&gt;That decision raised the stakes for what came next. When you buy a tool, you accept part of the black box as the package. When you build the pipeline, responsibility moves: if it fails silently, it is not the fault of some distant platform. It is the architecture you chose to tolerate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first version looked perfect
&lt;/h2&gt;

&lt;p&gt;The architecture was simple: every session was captured automatically and, at the start of the next conversation, the agent received a digest of relevant sessions. Underneath it, hybrid search (keyword + &lt;code&gt;pgvector&lt;/code&gt;) and an optional facts layer. Everything was saved to my own Postgres (via Supabase), with no SaaS, no lock-in, and the ability to run &lt;code&gt;pg_dump&lt;/code&gt; at any time.&lt;/p&gt;

&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%2Frddshbu9t1vrdml577wc.png" 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%2Frddshbu9t1vrdml577wc.png" alt="Recorte da arte de capa: a promessa visual parece linear: agente, memória, proteção, auditoria. O artigo existe porque, na prática, a parte invisível é justamente provar que cada etapa aconteceu." width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design: three lifecycle hooks write to and read from the same Postgres table. Capture is idempotent (upsert by &lt;code&gt;session_id&lt;/code&gt;); the &lt;code&gt;Stop&lt;/code&gt; checkpoint keeps the session alive even through an abrupt kill.&lt;/p&gt;

&lt;p&gt;It worked across machines. It worked across tools. It worked so well that I stopped thinking about it. That was the danger: the automation had disappeared from my attention.&lt;/p&gt;

&lt;p&gt;You stop verifying because the system looks reliable, and the system looks reliable precisely because failures do not surface. Until the day I decided to check one specific session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day of the ghost session
&lt;/h2&gt;

&lt;p&gt;The session that made me pull this thread did not look special. It was just another long conversation, the kind where you stack context, small decisions, command output and course corrections. At some point, a simple question landed: "did this get saved?".&lt;/p&gt;

&lt;p&gt;The question was almost bureaucratic. I was not hunting for a bug. I only wanted to confirm that memory had done its job. I checked the log. Nothing. I checked the database. Nothing. The session that had just happened did not exist for the system that was supposed to remember it.&lt;/p&gt;

&lt;p&gt;That kind of moment changes your relationship with a tool. Until then, I thought of memory as convenience. After that, I started seeing it as infrastructure. And infrastructure that fails without signaling is not infrastructure; it is a silent bet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error was hidden in &lt;code&gt;exit 0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When I followed the trace, the pattern became more uncomfortable than one missing session. There were intermittent errors: session yes, session no. Some conversations reached the database; others disappeared on the way.&lt;/p&gt;

&lt;p&gt;The capture hook had an &lt;code&gt;except&lt;/code&gt; that swallowed any error and moved on with &lt;code&gt;exit 0&lt;/code&gt;. From Claude Code's point of view, all good. From the database's point of view, whole sessions were missing.&lt;/p&gt;

&lt;p&gt;And that's worse than a crash. A crash you see: it stops, it screams, it shows up. Silence you don't see. The agent simply "forgets" things it should know, and you chalk it up to a model limitation, not a broken pipeline. The memory looked like it worked because nobody had to prove that it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why silent failure is so deceptive
&lt;/h2&gt;

&lt;p&gt;Silent failure does not compete with your system. It competes with your interpretation of the system. When the agent forgets something, plausible explanations remain: the model did not weigh it enough, the summary was weak, the initial prompt did not include context, semantic search did not find the right session.&lt;/p&gt;

&lt;p&gt;All of those explanations can be true. That is exactly why they are dangerous. They give you a comfortable story for a problem that may sit one step earlier: the information was never captured. You keep tuning recall, embeddings and prompts while the hole is in ingestion.&lt;/p&gt;

&lt;p&gt;That is when the word "trust" started to feel wrong. Trust, in that context, was just the absence of evidence against. What I needed was evidence for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation: follow the data, not intuition
&lt;/h2&gt;

&lt;p&gt;The initial temptation was to fix the first visible error and declare victory. But ingestion pipelines rarely break in only one place. The data crossed shell, Python parser, sanitization, Postgres insert and logs. Any one of those layers could be lying by omission.&lt;/p&gt;

&lt;p&gt;So I followed the payload like a trail: was the original input valid? Did it arrive unchanged in Python? Did the parser accept it? Did the database reject it? Did the log record the failure? Did the hook return an error to the tool?&lt;/p&gt;

&lt;p&gt;That changed the tone of the investigation. The bug stopped being "why did one session disappear?" and became "how many sessions could disappear without me noticing?". The second question is much more uncomfortable, and much more useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three bugs, all silent
&lt;/h2&gt;

&lt;p&gt;What looked like one problem turned into three, at different layers of the same pipeline. The detail is worth it because this pattern shows up in any ingestion system that seems too "simple" to fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: control characters
&lt;/h3&gt;

&lt;p&gt;Each session payload is JSON. Python's strict parser rejected any payload with unescaped control characters, and the content of a terminal session is full of them (command output, box-drawing, ANSI sequences). The result was a &lt;code&gt;JSONDecodeError&lt;/code&gt; the &lt;code&gt;except&lt;/code&gt; swallowed.&lt;/p&gt;

&lt;p&gt;The fix is one line, but it only works because the error was in the parse:&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# accept control chars inside strings
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug 2: the NUL byte
&lt;/h3&gt;

&lt;p&gt;With a tolerant parser, another session failed, now at the database. Postgres refuses the NUL byte in a &lt;code&gt;text&lt;/code&gt; column, with the error &lt;code&gt;22P05: unsupported Unicode escape sequence&lt;/code&gt;. So: the parse passed, but the &lt;code&gt;INSERT&lt;/code&gt; was rejected. The fix was to sanitize the content, recursively, before uploading:&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;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&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;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&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="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&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="nf"&gt;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&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;obj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug 3: the &lt;code&gt;echo&lt;/code&gt; that corrupted the JSON
&lt;/h3&gt;

&lt;p&gt;This one was the subtlest, and the most instructive. The hook command captured the payload and handed it to Python like this:&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="nv"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | python3 capture_session.py ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is &lt;code&gt;echo&lt;/code&gt;. In &lt;code&gt;sh&lt;/code&gt;/&lt;code&gt;dash&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt; &lt;strong&gt;interprets backslash sequences&lt;/strong&gt;. Any payload with &lt;code&gt;\&lt;/code&gt; (and sessions have many: ANSI sequences, regex, &lt;code&gt;\u&lt;/code&gt;) reached the parser corrupted, with the error &lt;code&gt;Invalid \escape&lt;/code&gt;. That's why it alternated: a session with a backslash failed, a session without one passed. The parser never had a chance; the data arrived already broken.&lt;/p&gt;

&lt;p&gt;The fix is to swap &lt;code&gt;echo&lt;/code&gt; for &lt;code&gt;printf '%s'&lt;/code&gt;, which doesn't interpret escapes. The side-by-side test proves the point, with the same valid JSON input:&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;# same valid input; via echo it breaks, via printf it passes&lt;/span&gt;
sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'p=$(cat in.json); echo "$p"'&lt;/span&gt;        | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json,sys; json.load(sys.stdin)'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; JSONDecodeError: Invalid \escape&lt;/span&gt;

sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'p=$(cat in.json); printf "%s" "$p"'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json,sys; json.load(sys.stdin)'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cross-cutting lesson: every layer (parser, database, shell) had a different trap for the same data. A "simple" three-stage pipeline hid three independent failure modes, all silent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the present was not enough
&lt;/h2&gt;

&lt;p&gt;After fixing the hook, one annoying question remained: what about the past? The sessions that failed would not magically return just because the new code was better. If memory is history, old gaps matter too. Sometimes they matter more, because that is where the decisions and attempts I did not want to repeat were stored.&lt;/p&gt;

&lt;p&gt;Backfill became part of the fix. I had to reconcile local transcripts, identify sessions that existed on the filesystem but not in the database, sanitize old payloads, attach subagent sessions and deal with a second account that also had relevant history. It was not glamorous. It was infrastructure cleanup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  L["Local transcripts"] --&amp;gt; C["Classify valid sessions"]
  C --&amp;gt; M["Find missing in DB"]
  M --&amp;gt; S["Sanitize old payload"]
  S --&amp;gt; U["Idempotent upsert"]
  U --&amp;gt; H["health confirms coverage"]
  H --&amp;gt; R["History whole again"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That detail is easy to underestimate. Many pipeline fixes stop at "from now on it is fine". But for memory, the past is the product. If you only fix the future, you leave the most valuable part of the tool with invisible gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot: the problem isn't remembering, it's verifying
&lt;/h2&gt;

&lt;p&gt;Fixing the bugs was the easy part. What stuck was the question they forced: I spent weeks trusting something broken and never knew. The problem was not only technical. It was epistemological: why did I think that memory deserved trust?&lt;/p&gt;

&lt;p&gt;Because the metric the whole category optimizes is "remember better": smarter recall, better embeddings, denser summaries. That matters. But the pain that caught me was one step earlier: &lt;strong&gt;proof that capture happened.&lt;/strong&gt; Nobody tells you when memory fails. There's no receipt, no health check, no gap detection. You find out when the agent forgets, if you find out.&lt;/p&gt;

&lt;p&gt;So I stopped treating trust as a feeling and started building verification. Two pieces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A coverage health check.&lt;/strong&gt; A command that reconciles local transcripts (from every tool) against what's in the database, ignores empty sessions, and watches the capture error rate over the last 24 hours. The output is direct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent-memory-hub · health

✓ coverage    ██████████████████████ 86/86 local sessions saved
✓ capture     last 24h: 51 ok, 0 errors
✓ subagents   13/13 sessions with subagents attached
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&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%2Fm3s8oam9civfu0i70jcc.png" 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%2Fm3s8oam9civfu0i70jcc.png" alt="Recorte da arte de capa: privacidade, assinatura e auditoria são símbolos fortes. Mas o primeiro recibo de confiança era mais básico: a sessão chegou ao banco ou não?" width="800" height="514"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  L["Local transcripts&amp;lt;br/&amp;gt;(all tools)"] --&amp;gt; R{"health: reconcile"}
  B[("DB · public.sessions")] --&amp;gt; R
  R --&amp;gt;|"gap or capture error"| ALERT["surfaced: coverage under 100%"]
  R --&amp;gt;|"all matched"| OKN["86/86 · 0 errors / 24h"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When something breaks, it surfaces, instead of going unnoticed. It's the difference between memory you &lt;em&gt;trust&lt;/em&gt; and memory you &lt;em&gt;verify&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An MCP server for on-demand recall.&lt;/strong&gt; The default recall is passive: it injects a digest at the start of the session, before you even say what you want to do. I exposed the memory as MCP tools (pure stdlib, no dependencies) so the agent queries the memory &lt;em&gt;with the task in hand&lt;/em&gt;: &lt;code&gt;recall_relevant&lt;/code&gt;, &lt;code&gt;recent_sessions&lt;/code&gt;, &lt;code&gt;get_facts&lt;/code&gt;, &lt;code&gt;get_session&lt;/code&gt;. The idea was simple: remember at the right time, not only at the beginning of the conversation. The heart of the server is a minimal JSON-RPC handler:&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;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&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;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="nf"&gt;_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}]})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&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%2F2scxwyr7dix2cvjzs4va.png" 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%2F2scxwyr7dix2cvjzs4va.png" alt="Recorte da arte de capa: a interface visual é conceitual, mas captura bem o contrato que eu queria do sistema: salvar, recuperar e verificar." width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The numbers, after closing everything: 148 sessions, 36 projects, local↔database coverage reconciled, with a second account's sessions and subagent transcripts finally included in the backfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system changed personality
&lt;/h2&gt;

&lt;p&gt;Before, memory was an optimistic promise: "relax, I saved it". Afterward, it became a system with a more mature posture: "I saved these sessions, I did not save those, and here is the difference between what exists locally and what reached the database".&lt;/p&gt;

&lt;p&gt;That also changed how I use agents. When recall feels poor, there is now a question before model quality: was the memory available? Was the relevant session captured? Did the agent query the right history? Without that layer, every failure looks like "the AI's fault". With it, forgetting becomes something you can investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the project does today
&lt;/h2&gt;

&lt;p&gt;After that sequence of fixes, agent-memory-hub stopped being just a pair of hooks. It became a small personal memory platform for agents: capture, query, verification, console, adapters, backup, profile and quality evaluation. It did not start that way; it became that every time a failure asked for a better surface.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it solves today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Capture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code hooks save sessions; adapters pull history from Codex and Cursor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Session-start recall, on-demand MCP and the &lt;code&gt;mem&lt;/code&gt; console to search, inspect and summarize.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;health&lt;/code&gt; reconciles local transcripts with the database; &lt;code&gt;log&lt;/code&gt; shows what happened.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pytest suite, CI and a recall evaluation harness with hit@k/MRR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The cross-project profile proposes rules; you approve before they become behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The commits tell that evolution well. First came the capture bugs. Then health and log. Then MCP, standup, adapters for other tools, packaging for the &lt;code&gt;mem&lt;/code&gt; command, CI tests and recall evaluation. Even ranking tuning entered the loop: a recency bias was tested and rejected by data when it hurt recall.&lt;/p&gt;

&lt;p&gt;That changes the reading of the project. It is not an idea abandoned after market research. It is a living, used tool, with commits stacking real learning. The care was different: not confusing open-source traction and daily usefulness with an obligation to turn everything into a company immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before turning it into a startup, I stress-tested the thesis
&lt;/h2&gt;

&lt;p&gt;The seductive version of the story would stop here and say: I found a real pain, built a solution, now there is an opportunity. And for a few hours, I wanted to believe that. "Verifiable memory for agents" sounds like a category. It seems to have a name, urgency and a clear angle.&lt;/p&gt;

&lt;p&gt;But a real pain is not automatically a company. So I changed the question. Instead of looking for confirmation, I looked for friction: who is already close to this? What can incumbents copy quickly? What is deep pain and what is an adjacent feature? If I were my own competitor, how long would it take to neutralize this slice?&lt;/p&gt;

&lt;p&gt;That research did not reduce the value of the project. On the contrary: it made the value clearer. As a personal tool, it saves me time and reduces repetition every day. As an open-source project, it has already started getting stars and signaling that the pain resonates outside my own machine. As a startup, it might still need a broader thesis than "proof of capture". And that is fine. Not every useful project needs to become a company before it finishes learning its own shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked: daily use and real signals
&lt;/h2&gt;

&lt;p&gt;The part I am proudest of is that the project did not stay in idea-land. It is in my daily workflow. It captures sessions, shows up at the start of new conversations, helps recover old decisions and reduces the annoying work of re-explaining context to the agent.&lt;/p&gt;

&lt;p&gt;It also did not stay invisible to other people. The repo has already received stars, and that matters as a small but honest signal: not market validation, but proof that the pain is recognizable. The adversarial research was not there to abandon the project; it was there to calibrate ambition. The result was sober:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it already covers&lt;/th&gt;
&lt;th&gt;The gap that still remains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MemGuard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Datadog for agent memory": health and trust-scoring.&lt;/td&gt;
&lt;td&gt;Proof of capture, silent-gap detection, "memory receipts". Real, but thin, and copyable by incumbents in weeks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise governance: SOC 2 Type II, HIPAA, access audit.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;MemTrust&lt;/strong&gt; (paper, 2026)&lt;/td&gt;
&lt;td&gt;Crypto-chained audit and memory attestation.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;mem0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An audit Events API already built in.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "observe/trust the memory" theme is filling up fast. The exact slice still open (proof of capture, silent-gap detection, "memory receipts") is real, but it's thin, and copyable by incumbents in weeks (&lt;code&gt;mem0&lt;/code&gt;, for instance, already has an audit Events API).&lt;/p&gt;

&lt;p&gt;So the decision was to keep using it, keep opening it, keep improving it and not force a startup narrative before its time. Today the value is clear: real dogfood, open-source, public signs of interest, and useful enough to stay in my daily workflow.&lt;/p&gt;

&lt;p&gt;One honest technical caveat, for anyone replicating: the hook fix only applies to new sessions, because hooks load at the start of the session. The safety net is the session-end event, which saves the whole transcript cleanly, so even with the intermediate checkpoints failing, the final state isn't lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you run agents with memory, do one thing this week: check that it's actually capturing. Don't assume. Look for the equivalent of my "checked the log, checked the database". The most dangerous failure mode of an ingestion pipeline isn't the one that screams, it's the one that fails quietly and leaves you confident.&lt;/p&gt;

&lt;p&gt;The principle generalizes beyond AI agents: &lt;strong&gt;for any system that ingests data and can fail silently, observability beats optimization.&lt;/strong&gt; Verifying is more important than trusting. And the product meta-lesson, maybe the most useful: running adversarial research to calibrate your own ambition is a superpower, not a defeat. Sometimes the best outcome of an investigation is understanding that the project should stay alive, just in the right shape.&lt;/p&gt;

&lt;p&gt;The project is &lt;a href="https://github.com/carloshpdoc/agent-memory-hub" rel="noopener noreferrer"&gt;open-source and self-hosted&lt;/a&gt;. If it saves you a single session re-explaining your project to the agent, it was worth it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://buildcomcarlos.com/articles/memoria-agente-ia-verificavel.html?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=memoria-agente" rel="noopener noreferrer"&gt;buildcomcarlos.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Memória de agente de IA que você verifica, não só confia</title>
      <dc:creator>Carloshperc</dc:creator>
      <pubDate>Fri, 03 Jul 2026 18:30:56 +0000</pubDate>
      <link>https://dev.to/carlosperc/memoria-de-agente-de-ia-que-voce-verifica-nao-so-confia-4a4a</link>
      <guid>https://dev.to/carlosperc/memoria-de-agente-de-ia-que-voce-verifica-nao-so-confia-4a4a</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Um &lt;code&gt;CLAUDE.md&lt;/code&gt; resolve conhecimento estático: comandos, convenções, preferências e regras. O que ele não resolve é memória episódica: o bug investigado ontem, a abordagem que já falhou, a decisão que explica por que um PR foi abandonado. Foi para isso que construí o &lt;a href="https://github.com/carloshpdoc/agent-memory-hub" rel="noopener noreferrer"&gt;&lt;strong&gt;agent-memory-hub&lt;/strong&gt;&lt;/a&gt;. A virada veio depois: eu descobri que a própria memória podia falhar em silêncio. Este artigo é a história de como uma memória persistente virou memória verificável, e de como uma ideia de produto virou uma ferramenta open-source que eu uso de verdade no dia a dia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Para quem é este artigo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Devs que usam Claude Code, Codex ou Cursor e sentem que o agente sabe as regras, mas esquece a história.&lt;/li&gt;
&lt;li&gt;Quem mantém &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; ou memória persistente e quer separar conhecimento estático de memória episódica.&lt;/li&gt;
&lt;li&gt;Quem gosta de pós-morte honesto: bug silencioso de pipeline, observabilidade e uma decisão de produto deliberada.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A história inteira em uma linha
&lt;/h2&gt;

&lt;p&gt;Se eu contasse isso do jeito mais óbvio, o resumo seria: construí uma memória, encontrei bugs, adicionei observabilidade. Só que esse resumo começa pelo lugar errado. A história não ficou interessante porque um bug apareceu. Ficou interessante porque o bug mostrou que eu estava confiando em uma memória sem recibo.&lt;/p&gt;

&lt;p&gt;Essa ordem muda o peso de tudo. O agent-memory-hub começou como uma solução de continuidade para agentes de código. Depois virou uma aula prática de ingestão: qualquer coisa que captura dados automaticamente também precisa provar que capturou. Sem isso, a memória só parece memória.&lt;/p&gt;

&lt;h2&gt;
  
  
  A dor não era inteligência. Era memória.
&lt;/h2&gt;

&lt;p&gt;Nos últimos meses eu praticamente parei de programar sem agentes de IA. Claude Code, Codex, Cursor: quando você dá contexto suficiente, cada ferramenta fica muito boa em entrar no projeto e produzir.&lt;/p&gt;

&lt;p&gt;Então fiz o que quase todo mundo faz: escrevi um bom &lt;code&gt;CLAUDE.md&lt;/code&gt;. Coloquei comandos de build, convenções, como gosto de escrever testes, como organizo módulos, como abro PRs. Funcionava muito bem, até eu perceber uma divisão curiosa: o agente sabia &lt;strong&gt;como eu queria que ele trabalhasse&lt;/strong&gt;, mas não lembrava &lt;strong&gt;o que já tinha acontecido&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sabia compilar. Mas não lembrava que ontem já tínhamos tentado outra abordagem. Sabia rodar os testes. Mas esquecia que aquele bug já tinha sido investigado durante duas horas. Sabia as regras. Mas não conhecia a história.&lt;/p&gt;

&lt;h2&gt;
  
  
  O hábito que mascarava o problema
&lt;/h2&gt;

&lt;p&gt;Por um tempo eu compensei isso do jeito mais humano possível: repetindo. Abria uma sessão nova e despejava contexto. "Ontem tentamos isso." "Não mexe nessa camada porque ela tem um acoplamento estranho." "Esse teste falha quando roda em paralelo." "A decisão do PR anterior foi seguir por outro caminho."&lt;/p&gt;

&lt;p&gt;O curioso é que essa repetição parecia parte natural do uso de agentes. Como a resposta melhorava depois que eu explicava tudo de novo, eu não enxergava aquilo como falha de sistema. Parecia só o ritual de entrada: antes de trabalhar, recapitular. O problema é que ritual manual cobra juros quando você alterna entre projetos, máquinas e ferramentas.&lt;/p&gt;

&lt;p&gt;Esse foi o primeiro insight importante: o agente não precisava apenas de instruções melhores. Ele precisava de continuidade. E continuidade não é prompt longo. É a capacidade de carregar consequência de uma sessão para outra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existem dois tipos de conhecimento
&lt;/h2&gt;

&lt;p&gt;Quando comecei a separar o problema, ficou claro que eu estava chamando duas coisas diferentes de "contexto". Uma é estática: como compilar, como fazer deploy, como estruturar um módulo, como escrever commits.&lt;/p&gt;

&lt;p&gt;Esse conhecimento pertence ao &lt;code&gt;CLAUDE.md&lt;/code&gt;. A outra parte muda todo dia: o bug investigado ontem, a decisão da última sessão, o experimento que deu errado, o motivo de um PR ter sido abandonado. Isso não pertence ao &lt;code&gt;CLAUDE.md&lt;/code&gt;, porque ele teria que ser reescrito o tempo inteiro. É memória. Episódica, viva, automática.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CLAUDE.md / AGENTS.md&lt;/th&gt;
&lt;th&gt;agent-memory-hub&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Como trabalhar&lt;/td&gt;
&lt;td&gt;O que já aconteceu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Estático&lt;/td&gt;
&lt;td&gt;Dinâmico&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escrito por você&lt;/td&gt;
&lt;td&gt;Capturado automaticamente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regras&lt;/td&gt;
&lt;td&gt;História&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Foi aí que nasceu o &lt;strong&gt;agent-memory-hub&lt;/strong&gt;. Não para substituir o &lt;code&gt;CLAUDE.md&lt;/code&gt;, mas para resolver justamente aquilo que ele nunca foi feito para resolver.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que eu queria que a memória lembrasse
&lt;/h2&gt;

&lt;p&gt;Quando você usa agentes de vez em quando, repetir contexto parece só um incômodo. Quando eles entram no fluxo diário, vira custo operacional. O problema não era guardar "preferências"; era preservar continuidade entre sessões que, para a ferramenta, pareciam independentes.&lt;/p&gt;

&lt;p&gt;Na prática, eu queria que a memória guardasse cinco tipos de episódio: decisões tomadas, hipóteses descartadas, bugs já investigados, estado mental de PRs e pequenos padrões de projeto que só aparecem depois de várias conversas.&lt;/p&gt;

&lt;p&gt;Esse recorte protegia o projeto de virar um "depósito de tudo". Se tudo é memória, nada é memória. O valor estava em capturar aquilo que eu realmente esqueceria entre uma sessão e outra, especialmente quando alternava entre máquinas, ferramentas e contextos.&lt;/p&gt;

&lt;p&gt;A partir daí, a pergunta deixou de ser "qual ferramenta de memória existe?" e virou outra: que tipo de sistema conseguiria capturar esses episódios sem prender meu histórico em uma ferramenta só?&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que construir em casa
&lt;/h2&gt;

&lt;p&gt;Eu poderia ter parado aqui e adotado uma ferramenta pronta. Existem boas opções para memória de agente, e eu não queria reinventar busca vetorial por esporte. Mas meu problema tinha exigências muito pessoais: funcionar entre Claude Code e Codex, atravessar máquinas, continuar meu quando eu trocasse de ferramenta e ser auditável com uma consulta SQL simples.&lt;/p&gt;

&lt;p&gt;A escolha por Postgres via Supabase veio menos por glamour técnico e mais por previsibilidade. Eu queria posse dos dados, &lt;code&gt;pg_dump&lt;/code&gt;, inspeção direta, uma tabela que eu pudesse abrir sem pedir licença para produto nenhum. Para memória pessoal de trabalho, isso pesa. O histórico das minhas sessões é parte do meu processo, não só cache de uma ferramenta.&lt;/p&gt;

&lt;p&gt;Essa decisão aumentou o peso do que viria depois. Quando você compra uma ferramenta, aceita parte da caixa-preta como pacote. Quando constrói o pipeline, a responsabilidade muda de lugar: se ele falha em silêncio, a culpa não é de uma plataforma distante. É da arquitetura que você escolheu tolerar.&lt;/p&gt;

&lt;h2&gt;
  
  
  A primeira versão parecia perfeita
&lt;/h2&gt;

&lt;p&gt;A arquitetura era simples: cada sessão era capturada automaticamente e, no início da próxima conversa, o agente recebia um resumo das sessões relevantes. Por baixo, busca híbrida (palavra-chave + &lt;code&gt;pgvector&lt;/code&gt;) e uma camada opcional de fatos. Tudo salvo no meu próprio Postgres (via Supabase), sem SaaS, sem lock-in, com a capacidade de rodar &lt;code&gt;pg_dump&lt;/code&gt; a qualquer momento.&lt;/p&gt;

&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%2Frddshbu9t1vrdml577wc.png" 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%2Frddshbu9t1vrdml577wc.png" alt="Recorte da arte de capa: a promessa visual parece linear: agente, memória, proteção, auditoria. O artigo existe porque, na prática, a parte invisível é justamente provar que cada etapa aconteceu." width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O desenho: três hooks de ciclo de vida escrevem e leem a mesma tabela Postgres. Captura idempotente (upsert por &lt;code&gt;session_id&lt;/code&gt;); o checkpoint do &lt;code&gt;Stop&lt;/code&gt; preserva a sessão até um kill abrupto.&lt;/p&gt;

&lt;p&gt;Funcionava entre máquinas. Funcionava entre ferramentas. Funcionava tão bem que eu parei de pensar nela. Esse foi o perigo: a automação tinha sumido da minha atenção.&lt;/p&gt;

&lt;p&gt;Você deixa de verificar porque o sistema parece confiável, e o sistema parece confiável justamente porque as falhas não aparecem na superfície. Até o dia em que eu resolvi conferir uma sessão específica.&lt;/p&gt;

&lt;h2&gt;
  
  
  O dia da sessão fantasma
&lt;/h2&gt;

&lt;p&gt;A sessão que me fez puxar esse fio não parecia especial. Era só mais uma conversa longa, dessas em que você empilha contexto, decisões pequenas, saídas de comando e ajustes de rota. Em algum momento, bateu uma dúvida simples: "será que isso foi salvo?".&lt;/p&gt;

&lt;p&gt;A pergunta era quase burocrática. Eu não estava caçando bug. Só queria confirmar que a memória tinha feito o trabalho dela. Fui no log. Nada. Fui no banco. Nada. A sessão que tinha acabado de acontecer não existia para o sistema que deveria lembrar dela.&lt;/p&gt;

&lt;p&gt;Esse tipo de momento muda sua relação com uma ferramenta. Até ali, eu pensava na memória como comodidade. Depois disso, passei a enxergar como infraestrutura. E infraestrutura que falha sem sinalização não é infraestrutura: é uma aposta silenciosa.&lt;/p&gt;

&lt;h2&gt;
  
  
  O erro estava escondido no &lt;code&gt;exit 0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Quando segui o rastro, o padrão ficou mais desconfortável do que uma sessão perdida. Havia erros intermitentes: sessão sim, sessão não. Algumas conversas chegavam ao banco; outras desapareciam no caminho.&lt;/p&gt;

&lt;p&gt;O hook de captura tinha um &lt;code&gt;except&lt;/code&gt; que engolia qualquer erro e seguia em frente com &lt;code&gt;exit 0&lt;/code&gt;. Do ponto de vista do Claude Code, tudo certo. Do ponto de vista do banco, faltavam sessões inteiras.&lt;/p&gt;

&lt;p&gt;E isso é pior do que um crash. Crash você vê: ele para, grita, aparece. Silêncio você não vê. O agente simplesmente "esquece" coisas que deveria saber, e você atribui isso a uma limitação do modelo, não a um pipeline quebrado. A memória parecia funcionar porque ninguém era obrigado a provar que ela tinha funcionado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que falha silenciosa é tão traiçoeira
&lt;/h2&gt;

&lt;p&gt;A falha silenciosa não compete com o seu sistema. Ela compete com a sua interpretação do sistema. Quando o agente esquece algo, sobram explicações plausíveis: o modelo não deu peso suficiente, o resumo ficou ruim, o prompt inicial não trouxe contexto, a busca semântica não encontrou a sessão certa.&lt;/p&gt;

&lt;p&gt;Todas essas explicações podem ser verdadeiras. E justamente por isso são perigosas. Elas dão uma narrativa confortável para um problema que talvez esteja um passo antes: a informação nunca foi capturada. Você fica ajustando recall, embeddings e prompt, enquanto o buraco está na ingestão.&lt;/p&gt;

&lt;p&gt;Foi aí que a palavra "confiança" começou a parecer errada. Confiança, naquele contexto, era só ausência de evidência contra. O que eu precisava era evidência a favor.&lt;/p&gt;

&lt;h2&gt;
  
  
  A investigação: seguir o dado, não a intuição
&lt;/h2&gt;

&lt;p&gt;A tentação inicial era corrigir o primeiro erro visível e declarar vitória. Mas pipeline de ingestão raramente quebra em um lugar só. O dado atravessava shell, parser Python, sanitização, insert no Postgres e logs. Qualquer uma dessas camadas podia estar mentindo por omissão.&lt;/p&gt;

&lt;p&gt;Então eu fui seguindo o payload como quem segue uma trilha: a entrada original era válida? Ela chegava igual ao Python? O parser aceitava? O banco recusava? O log registrava a falha? O hook devolvia erro para a ferramenta?&lt;/p&gt;

&lt;p&gt;Foi isso que mudou o tom da investigação. O bug deixou de ser "por que uma sessão sumiu?" e virou "quantas sessões poderiam sumir sem eu perceber?". Essa segunda pergunta é muito mais desconfortável, e muito mais útil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Três bugs, todos silenciosos
&lt;/h2&gt;

&lt;p&gt;O que parecia um problema virou três, em camadas diferentes do mesmo pipeline. Vale entrar no detalhe porque esse padrão aparece em qualquer sistema de ingestão que parece "simples" demais para falhar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: caracteres de controle
&lt;/h3&gt;

&lt;p&gt;O payload de cada sessão é JSON. O parser estrito do Python rejeitava qualquer payload com caracteres de controle não-escapados, e o conteúdo de uma sessão de terminal está cheio deles (output de comandos, box-drawing, sequências ANSI). O resultado era um &lt;code&gt;JSONDecodeError&lt;/code&gt; que o &lt;code&gt;except&lt;/code&gt; engolia.&lt;/p&gt;

&lt;p&gt;A correção é de uma linha, mas só funciona porque o erro estava no parse:&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# aceita control chars dentro das strings
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug 2: o byte nulo
&lt;/h3&gt;

&lt;p&gt;Com o parser tolerante, outra sessão falhou, agora no banco. O Postgres recusa o byte nulo em coluna &lt;code&gt;text&lt;/code&gt;, com o erro &lt;code&gt;22P05: unsupported Unicode escape sequence&lt;/code&gt;. Ou seja: o parse passava, mas o &lt;code&gt;INSERT&lt;/code&gt; era rejeitado. A correção foi sanitizar o conteúdo, recursivamente, antes de subir:&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;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&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;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&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="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&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="nf"&gt;strip_nul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&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;obj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bug 3: o &lt;code&gt;echo&lt;/code&gt; que corrompia o JSON
&lt;/h3&gt;

&lt;p&gt;Este foi o mais sutil, e o mais instrutivo. O comando do hook capturava o payload e o repassava ao Python assim:&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="nv"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | python3 capture_session.py ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema é o &lt;code&gt;echo&lt;/code&gt;. Em &lt;code&gt;sh&lt;/code&gt;/&lt;code&gt;dash&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt; &lt;strong&gt;interpreta sequências de barra invertida&lt;/strong&gt;. Qualquer payload com &lt;code&gt;\&lt;/code&gt; (e sessões têm muitos: sequências ANSI, regex, &lt;code&gt;\u&lt;/code&gt;) chegava corrompido ao parser, com o erro &lt;code&gt;Invalid \escape&lt;/code&gt;. Por isso alternava: sessão com barra invertida falhava, sessão sem barra passava. O parser nunca teve chance; o dado já chegava quebrado.&lt;/p&gt;

&lt;p&gt;A correção é trocar &lt;code&gt;echo&lt;/code&gt; por &lt;code&gt;printf '%s'&lt;/code&gt;, que não interpreta escapes. O teste lado a lado prova o ponto, com a mesma entrada JSON válida:&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;# mesma entrada válida; via echo quebra, via printf passa&lt;/span&gt;
sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'p=$(cat in.json); echo "$p"'&lt;/span&gt;        | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json,sys; json.load(sys.stdin)'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; JSONDecodeError: Invalid \escape&lt;/span&gt;

sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'p=$(cat in.json); printf "%s" "$p"'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json,sys; json.load(sys.stdin)'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lição transversal: cada camada (parser, banco, shell) tinha uma armadilha diferente para o mesmo dado. Um pipeline "simples" de três etapas escondia três modos de falha independentes, todos silenciosos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consertar o presente não bastava
&lt;/h2&gt;

&lt;p&gt;Depois de corrigir o hook, ainda sobrava uma pergunta chata: e o passado? As sessões que falharam não voltariam magicamente só porque o código novo estava melhor. Se a memória é história, buraco antigo também importa. Às vezes importa mais, porque ali estavam decisões e tentativas que eu não queria repetir.&lt;/p&gt;

&lt;p&gt;O backfill virou parte da correção. Eu precisei reconciliar transcripts locais, identificar sessões que existiam no filesystem mas não no banco, sanitizar payloads antigos, anexar sessões de subagentes e lidar com uma segunda conta que também tinha histórico relevante. Não era glamouroso. Era faxina de infraestrutura.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  L["Transcripts locais"] --&amp;gt; C["Classificar sessões válidas"]
  C --&amp;gt; M["Encontrar ausentes no banco"]
  M --&amp;gt; S["Sanitizar payload antigo"]
  S --&amp;gt; U["Upsert idempotente"]
  U --&amp;gt; H["health confirma cobertura"]
  H --&amp;gt; R["História volta a ficar inteira"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse detalhe é fácil de subestimar. Muita correção de pipeline para no "daqui para frente está bom". Só que, para memória, o passado é o produto. Se você corrige apenas o futuro, deixa a parte mais valiosa da ferramenta com lacunas invisíveis.&lt;/p&gt;

&lt;h2&gt;
  
  
  A virada: o problema não é lembrar, é verificar
&lt;/h2&gt;

&lt;p&gt;Consertar os bugs foi o de menos. O que ficou foi a pergunta que eles forçaram: eu passei semanas confiando numa coisa quebrada e não soube. O problema não era só técnico. Era epistemológico: por que eu achava que aquela memória merecia confiança?&lt;/p&gt;

&lt;p&gt;Porque a métrica que a categoria inteira otimiza é "lembrar melhor": recall mais esperto, embeddings melhores, resumos mais densos. Isso importa. Mas a dor que me pegou estava um passo antes: &lt;strong&gt;prova de que a captura aconteceu.&lt;/strong&gt; Ninguém te avisa quando a memória falha. Não há recibo, não há health check, não há detecção de gap. Você descobre quando o agente esquece, se descobrir.&lt;/p&gt;

&lt;p&gt;Então parei de tratar confiança como sentimento e passei a construir verificação. Duas peças:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Um health check de cobertura.&lt;/strong&gt; Um comando que reconcilia os transcripts locais (de todas as ferramentas) contra o que está no banco, ignora sessões vazias, e vigia a taxa de erro da captura nas últimas 24 horas. A saída é direta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent-memory-hub · health

✓ cobertura   ██████████████████████ 86/86 sessões locais salvas
✓ captura     últimas 24h: 51 ok, 0 erros
✓ subagentes  13/13 sessões com subagentes anexados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&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%2Fm3s8oam9civfu0i70jcc.png" 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%2Fm3s8oam9civfu0i70jcc.png" alt="Recorte da arte de capa: privacidade, assinatura e auditoria são símbolos fortes. Mas o primeiro recibo de confiança era mais básico: a sessão chegou ao banco ou não?" width="800" height="514"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  L["Transcripts locais&amp;lt;br/&amp;gt;(todas as ferramentas)"] --&amp;gt; R{"health: reconcilia"}
  B[("DB · public.sessions")] --&amp;gt; R
  R --&amp;gt;|"gap ou erro de captura"| ALERT["aparece: cobertura abaixo de 100%"]
  R --&amp;gt;|"tudo bateu"| OKN["86/86 · 0 erros / 24h"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando algo quebra, isso aparece, em vez de passar batido. É a diferença entre memória que você &lt;em&gt;confia&lt;/em&gt; e memória que você &lt;em&gt;verifica&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Um servidor MCP para recall sob demanda.&lt;/strong&gt; O recall padrão é passivo: injeta um resumo no início da sessão, antes de você sequer dizer o que quer fazer. Expus a memória como ferramentas MCP (em stdlib puro, sem dependências) para que o agente consulte a memória &lt;em&gt;com o contexto da tarefa em mãos&lt;/em&gt;: &lt;code&gt;recall_relevant&lt;/code&gt;, &lt;code&gt;recent_sessions&lt;/code&gt;, &lt;code&gt;get_facts&lt;/code&gt;, &lt;code&gt;get_session&lt;/code&gt;. A ideia era simples: lembrar na hora certa, não só no começo da conversa. O coração do servidor é um handler JSON-RPC minimalista:&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;elif&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&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;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="nf"&gt;_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}]})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&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%2F2scxwyr7dix2cvjzs4va.png" 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%2F2scxwyr7dix2cvjzs4va.png" alt="Recorte da arte de capa: a interface visual é conceitual, mas captura bem o contrato que eu queria do sistema: salvar, recuperar e verificar." width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Os números, depois de fechar tudo: 148 sessões, 36 projetos, cobertura local↔banco reconciliada, com as sessões de uma segunda conta e os transcripts de subagentes finalmente incluídos no backfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  O sistema mudou de personalidade
&lt;/h2&gt;

&lt;p&gt;Antes, a memória era uma promessa otimista: "fica tranquilo, eu salvei". Depois, ela virou um sistema com postura mais adulta: "eu salvei estas sessões, não salvei aquelas, e aqui está a diferença entre o que existe localmente e o que chegou ao banco".&lt;/p&gt;

&lt;p&gt;Isso também mudou como eu uso agentes. Quando o recall parece ruim, agora existe uma pergunta anterior à qualidade do modelo: a memória estava disponível? A sessão relevante foi capturada? O agente consultou a história certa? Sem essa camada, qualquer falha parece "culpa da IA". Com ela, o esquecimento vira algo que dá para investigar.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que o projeto faz hoje
&lt;/h2&gt;

&lt;p&gt;Depois dessa sequência de correções, o agent-memory-hub deixou de ser só um par de hooks. Virou uma pequena plataforma pessoal de memória para agentes: captura, consulta, verificação, console, adapters, backup, perfil e avaliação de qualidade. Não nasceu assim; foi ficando assim cada vez que uma falha pedia uma superfície melhor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Camada&lt;/th&gt;
&lt;th&gt;O que resolve hoje&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Captura&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hooks do Claude Code salvam sessões; adapters puxam histórico de Codex e Cursor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consulta&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Recall no início da sessão, MCP sob demanda e console &lt;code&gt;mem&lt;/code&gt; para buscar, inspecionar e resumir.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observabilidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;health&lt;/code&gt; reconcilia transcripts locais com o banco; &lt;code&gt;log&lt;/code&gt; mostra o que aconteceu.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qualidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Suite pytest, CI e harness de avaliação de recall com hit@k/MRR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evolução&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Perfil cross-projeto propõe regras; você aprova antes de virar comportamento.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Os commits contam bem essa evolução. Primeiro vieram os bugs de captura. Depois health e log. Em seguida MCP, standup, adapters para outras ferramentas, empacotamento do comando &lt;code&gt;mem&lt;/code&gt;, testes em CI e avaliação de recall. Até tuning de ranking entrou no fluxo: a ideia de aplicar viés de recência foi testada e rejeitada por dados quando piorou o recall.&lt;/p&gt;

&lt;p&gt;Isso muda a leitura do projeto. Ele não é uma ideia abandonada depois de uma pesquisa de mercado. É uma ferramenta viva, usada, com commits empilhando aprendizado real. O cuidado foi outro: não confundir tração open-source e utilidade diária com a obrigação de transformar tudo em empresa imediatamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de virar startup, eu testei a tese
&lt;/h2&gt;

&lt;p&gt;A parte sedutora da história seria parar aqui e dizer: encontrei uma dor real, construí uma solução, agora existe uma oportunidade. E, por algumas horas, eu quis acreditar nisso. "Memória verificável para agentes" soa como categoria. Parece ter nome, urgência e um ângulo claro.&lt;/p&gt;

&lt;p&gt;Mas uma dor real não é automaticamente uma empresa. Então eu mudei a pergunta. Em vez de procurar confirmação, procurei atrito: quem já está perto disso? O que incumbentes conseguem copiar rápido? O que é dor profunda e o que é feature adjacente? Se eu fosse concorrente de mim mesmo, quanto tempo levaria para neutralizar esse recorte?&lt;/p&gt;

&lt;p&gt;Essa pesquisa não diminuiu o valor do projeto. Pelo contrário: deixou o valor mais claro. Como ferramenta pessoal, ele me economiza tempo e reduz repetição todo dia. Como projeto open-source, já começou a receber stars e sinalizar que a dor ressoa fora da minha máquina. Como startup, talvez ainda precisasse de uma tese mais larga do que "prova de captura". E tudo bem. Nem todo projeto útil precisa virar empresa antes de terminar de aprender o próprio formato.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que funcionou: uso diário e sinais reais
&lt;/h2&gt;

&lt;p&gt;A parte que mais me orgulho é que o projeto não ficou no campo da ideia. Ele está no meu fluxo diário. Captura sessões, aparece no começo de conversas novas, ajuda a recuperar decisões antigas e reduz aquele trabalho chato de reexplicar contexto para o agente.&lt;/p&gt;

&lt;p&gt;Também não ficou invisível para outras pessoas. O repo já recebeu stars, e isso importa como sinal pequeno, mas honesto: não é validação de mercado, mas mostra que a dor é reconhecível. A pesquisa adversarial não serviu para abandonar o projeto; serviu para calibrar a ambição. O resultado foi sóbrio:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ferramenta&lt;/th&gt;
&lt;th&gt;O que já cobre&lt;/th&gt;
&lt;th&gt;O recorte que ainda sobra&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MemGuard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Datadog para memória de agente": health e trust-scoring.&lt;/td&gt;
&lt;td&gt;Prova de captura, detecção de gap silencioso, "recibos de memória". Real, mas fino, e copiável pelos incumbentes em semanas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governança enterprise: SOC 2 Type II, HIPAA, audit de acesso.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;MemTrust&lt;/strong&gt; (paper, 2026)&lt;/td&gt;
&lt;td&gt;Audit cripto-encadeado e attestation da memória.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;mem0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Events API de auditoria já embutida.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O tema "observar/confiar na memória" está enchendo rápido. O recorte exato que sobra aberto (prova de captura, detecção de gap silencioso, "recibos de memória") é real, mas é fino, e copiável pelos incumbentes em semanas (o &lt;code&gt;mem0&lt;/code&gt;, por exemplo, já tem um Events API de auditoria).&lt;/p&gt;

&lt;p&gt;Então a decisão foi continuar usando, continuar abrindo, continuar melhorando e não forçar uma narrativa de startup antes da hora. Hoje o valor está bem claro: é dogfood real, open-source, com sinais públicos de interesse, e útil o suficiente para permanecer no meu dia a dia.&lt;/p&gt;

&lt;p&gt;Um limite técnico honesto, para quem for replicar: a correção do hook só vale em sessões novas, porque hooks são carregados no início da sessão. A rede de segurança é o evento de fim de sessão, que salva o transcript inteiro de forma limpa, então mesmo com os checkpoints intermediários falhando, o estado final não se perde.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Se você roda agentes com memória, faça uma coisa esta semana: cheque se ela está capturando de verdade. Não assuma. Procure o equivalente do meu "fui no log, fui no banco". O modo de falha mais perigoso de um pipeline de ingestão não é o que grita, é o que falha calado e te deixa confiante.&lt;/p&gt;

&lt;p&gt;O princípio generaliza para além de agentes de IA: &lt;strong&gt;para qualquer sistema que ingere dados e pode falhar em silêncio, observabilidade vale mais que otimização.&lt;/strong&gt; Verificar é mais importante do que confiar. E o meta-aprendizado de produto, talvez o mais útil: rodar pesquisa adversarial para calibrar a própria ambição é um superpoder, não uma derrota. Às vezes o melhor resultado de uma investigação é entender que o projeto deve continuar vivo, só que no formato certo.&lt;/p&gt;

&lt;p&gt;O projeto é &lt;a href="https://github.com/carloshpdoc/agent-memory-hub" rel="noopener noreferrer"&gt;open-source e self-hosted&lt;/a&gt;. Se te economizar uma única sessão re-explicando seu projeto ao agente, já valeu.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Publicado originalmente em &lt;a href="https://buildcomcarlos.com/articles/memoria-agente-ia-verificavel.html?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=memoria-agente" rel="noopener noreferrer"&gt;buildcomcarlos.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>An afternoon of iOS perf work, with Claude in the loop</title>
      <dc:creator>Carloshperc</dc:creator>
      <pubDate>Fri, 08 May 2026 17:02:29 +0000</pubDate>
      <link>https://dev.to/carlosperc/an-afternoon-of-ios-perf-work-with-claude-in-the-loop-42eg</link>
      <guid>https://dev.to/carlosperc/an-afternoon-of-ios-perf-work-with-claude-in-the-loop-42eg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Subtitle: An iOS performance investigation I actually did, end-to-end through Claude Code, with MCP-driven simulator control, &lt;code&gt;xctrace&lt;/code&gt; Time Profiler, and the Memory Graph CLI. What the AI handled, what it didn't, and how my workflow changed.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey folks, let me walk you through an afternoon I had debugging an iOS perf ticket I'd been postponing for three weeks. Motivations, obstacles, dead ends, and wins included.&lt;/p&gt;

&lt;p&gt;Quick setup. Working on an app that uses SwiftUI quite a bit. The SavedItems tab was getting slow after about 15 location detail screens. Probably a memory leak. Probably something related to SwiftUI navigation. Probably "easy" after I stopped to analyze it. Spoiler: None of those hypotheses were correct.&lt;/p&gt;

&lt;p&gt;The investigation took an entire afternoon and resulted in three independent corrections and three pull requests. And most of my time at the keyboard was spent reviewing what Claude had just done, not typing.&lt;/p&gt;

&lt;p&gt;This text is not an account of how AI changed someone's life (please don't). It's a description of a workflow that I believe many mobile application engineers haven't yet experienced, with its friction points and parts that didn't work. If you work with iOS at a company, as an indie developer, or as a freelancer, this could be a valuable investment of a few hours (or minutes) in tools that will yield benefits when something on the device malfunctions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow, drawn out
&lt;/h2&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fml45ezfnz2dc3muzfbkl.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fml45ezfnz2dc3muzfbkl.png" alt=" " width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The unusual feature of this diagram is that &lt;strong&gt;everything except the Developer node and the artifacts is accessible from a single chat session&lt;/strong&gt;. Claude reads &lt;code&gt;.memgraph&lt;/code&gt; files via &lt;code&gt;leaks&lt;/code&gt;, controls the simulator through XcodeBuildMCP, and opens PRs via &lt;code&gt;gh&lt;/code&gt;. My job is to (a) capture artifacts that require physical access to a device, (b) review proposed changes, and (c) ensure the integrity of the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Three tools, all pre-configured:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code in the terminal&lt;/strong&gt;, pointing to the iOS repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XcodeBuildMCP&lt;/strong&gt;, a Sentry Model Context Protocol server that exposes approximately 60 Apple development tools to LLM (build workspace, run in simulator, touch UI elements, capture screenshots, attach LLDB, stream logs). Installed via npm, with a single line of MCP client configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;xctrace&lt;/code&gt;&lt;/strong&gt;, the CLI for Instruments. Logs Time Profiler traces on a physical device and exports them to XML, which LLM can parse.
Plus the things macOS gives you for free: &lt;code&gt;leaks&lt;/code&gt;, &lt;code&gt;heap&lt;/code&gt;, &lt;code&gt;vmmap&lt;/code&gt;, &lt;code&gt;atos&lt;/code&gt;, &lt;code&gt;xcrun simctl&lt;/code&gt;. The CLI tooling for iOS performance is a lot better than people think.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How it actually went
&lt;/h2&gt;

&lt;p&gt;I'm going to describe what happened more or less in the order in which it occurred. Not a polished retrospective. The genuine story, including the dead end where I got lost for two hours before getting back on track.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1. Reframing the problem (5 minutes)
&lt;/h3&gt;

&lt;p&gt;I started the conversation with a short sentence: "Opening the SaveItems location details is slow after about 15 cycles." Claude consulted existing memory entries from previous sessions (project-specific information about the source code, file layout, and naming conventions) and immediately questioned the explanation: "Slow how? Memory leak, instance buildup? Or, in real time, does each opening seem to take longer?"&lt;/p&gt;

&lt;p&gt;This is a useful first step. "Slow" is a symptom; the problem lies in retention (objects accumulating) or computation (task taking too long). There are different tools for each case. I described the symptom in observable terms: "after 15 openings, the next one takes about 6 seconds, and the Memory Graph shows 12 active instances of &lt;code&gt;DetailViewModel&lt;/code&gt; after 12 cycles". And the path forked: investigate the leak first, as it is the simplest problem, and we have the ground truth (the Memory Graph has already shown us that the instances are not being released).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2. Reproducing on the simulator, hands-off (10 minutes)
&lt;/h3&gt;

&lt;p&gt;This is where MCP earns its keep. Claude built and ran the app on the iOS Simulator via a single tool called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mcp__xcodebuildmcp__build_run_sim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I used the app: I tapped the Saved Items tab using the accessibility-adapted touch commands, tapped a location card, tapped back, tapped another card, and repeated the process. I captured screenshots between steps so the app could check (and so I could see in the chat history) what state it was in. There were about 12 opening/closing cycles without my interference.&lt;/p&gt;

&lt;p&gt;With MCP running, I monitored the Xcode console and copied the relevant output to the chat. It's a bit worse than being fully automated, but it works.&lt;br&gt;
By minute 30, we had a confirmed reproduction and ROOT CYCLE candidates from a Memory Graph file I'd exported and dropped on the Desktop.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3. &lt;code&gt;leaks ~/Desktop/x.memgraph&lt;/code&gt; (10 minutes that ended the leak)
&lt;/h3&gt;

&lt;p&gt;Here's the part that ended a &lt;em&gt;lot&lt;/em&gt; of speculation in two minutes. I exported a &lt;code&gt;.memgraph&lt;/code&gt; from Xcode (Debug ➜ View Debugging ➜ Capture View Hierarchy ➜ Memory Graph ➜ File ➜ Export Memory Graph). Saved it to &lt;code&gt;~/Desktop/example-leaks.memgraph&lt;/code&gt;. Sent the path to Claude.&lt;/p&gt;

&lt;p&gt;It ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;leaks ~/Desktop/example-leaks.memgraph 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"ROOT CYCLE|DetailViewModel"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And produced the chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROOT CYCLE: SwiftUI._DictionaryStorage&amp;lt;AnyHashable, WeakBox&amp;lt;AnyLocationBase&amp;gt;&amp;gt;
  → TagIndexProjection&amp;lt;Int&amp;gt;
    → ForEachState&amp;lt;MediaGalleryItem...&amp;gt;
      → Closure context (.onImageSliderTap)
        → ._viewModel.wrappedValue → DetailViewModel
        → ._coordinator → DetailsCoordinator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a SwiftUI internal observation graph that holds a closure capturing &lt;code&gt;self&lt;/code&gt; from inside a photo carousel &lt;code&gt;ForEach&lt;/code&gt; with a &lt;code&gt;.tag(Int)&lt;/code&gt; modifier on items. The closure was &lt;code&gt;onImageSliderTap&lt;/code&gt;, passed into &lt;code&gt;MediaCarouselHeaderView&lt;/code&gt;. It captured &lt;code&gt;self&lt;/code&gt; strongly, which retained the entire view's &lt;code&gt;@ObservedObject viewModel&lt;/code&gt; and &lt;code&gt;@State coordinator&lt;/code&gt; backings forever.&lt;/p&gt;

&lt;p&gt;I'd never seen &lt;code&gt;TagIndexProjection&amp;lt;Int&amp;gt;&lt;/code&gt; before. Wouldn't have guessed &lt;code&gt;.tag()&lt;/code&gt; caused this. The CLI told me directly. Without &lt;code&gt;leaks&lt;/code&gt;, I would have spent another four hours auditing closures.&lt;/p&gt;

&lt;p&gt;The fix was 15 lines. I hoisted &lt;code&gt;handlePhotoTap&lt;/code&gt; to &lt;code&gt;static&lt;/code&gt;, captured &lt;code&gt;[weak viewModel, weak coord = self.coordinator]&lt;/code&gt; instead of relying on implicit &lt;code&gt;self&lt;/code&gt;. Re-captured a fresh &lt;code&gt;.memgraph&lt;/code&gt;. Zero ROOT CYCLEs containing my classes. &lt;strong&gt;Done.&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4. The dead end I'd have walked into anyway (90 minutes. Pause here.)
&lt;/h3&gt;

&lt;p&gt;After the leak fix, the screen still felt slow. The next obvious hypothesis: &lt;code&gt;fullScreenCover&lt;/code&gt; tears down the SavedItems' SwiftUI tree on dismiss, the parent re-renders a 50-cell grid with &lt;code&gt;AsyncImage&lt;/code&gt;s, that's why the next open is laggy.&lt;/p&gt;

&lt;p&gt;Easy test: swap &lt;code&gt;.fullScreenCover(item:)&lt;/code&gt; for &lt;code&gt;.sheet(item:)&lt;/code&gt;. One-line change. Ran it on the simulator, captured a fresh Memory Graph. Same slowness. Comparable counts. Hypothesis rejected.&lt;/p&gt;

&lt;p&gt;This is where my workflow provided me with something I want to highlight specifically: &lt;strong&gt;my hypothesis was wrong, and the test cost 10 minutes instead of half a day.&lt;/strong&gt; I changed one line, Claude recompiled, ran the simulator, captured the artifact, performed the analysis, and provided me with the results.&lt;/p&gt;

&lt;p&gt;Reverting the change was easy (Git is wonderful). Total cost of the error: less than fifteen minutes. With a manual workflow, this experiment would have required an hour of work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5. Time Profiler, comparison-first (45 minutes)
&lt;/h3&gt;

&lt;p&gt;The pivot: if presentation isn't the bottleneck, the cost has to be in the &lt;em&gt;work&lt;/em&gt; each open does. I needed CPU samples.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xctrace&lt;/code&gt; Time Profiler against my iPhone, attached to the running app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun xctrace record &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt; &lt;span class="s1"&gt;'Time Profiler'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--device&lt;/span&gt; &amp;lt;UDID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--attach&lt;/span&gt; DemoApp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--time-limit&lt;/span&gt; 90s &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; ~/Desktop/saveditems-tti-device.trace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I drove the device manually for 90 seconds. Same flow, six places opened and closed, plus scrolling. Then a second pass on Browse (the fluid baseline) for comparison.&lt;/p&gt;

&lt;p&gt;I exported the &lt;code&gt;time-profile&lt;/code&gt; schema of each &lt;code&gt;.trace&lt;/code&gt; (this part of &lt;code&gt;xctrace&lt;/code&gt; works via &lt;code&gt;--xpath&lt;/code&gt;, unlike the Leaks data) to Claude, and it wrote a small analyzer in Python to count the frames that include the main thread and generated this side-by-side comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Frame in &lt;code&gt;DemoApp&lt;/code&gt; binary&lt;/th&gt;
&lt;th&gt;Browse&lt;/th&gt;
&lt;th&gt;SavedItems&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GraphQLClient.init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NetworkConnectivityChecker.init&lt;/code&gt; ➜ &lt;code&gt;CTTelephonyNetworkInfo.init&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*Grid.body.getter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ActionsFactory.SavedItemsContext.make&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;According to the &lt;code&gt;potential-freezes&lt;/code&gt; schema, SavedItems experienced 35 freezes lasting more than 250 ms in 90 seconds, totaling 21.97 seconds of freezing. Browse (another part of the application that uses the same components and some of the same structures; I used it for comparison) experienced 6 freezes, totaling 2.87 seconds. The main thread of SavedItems was hangs for 24% of the time during normal use.&lt;/p&gt;

&lt;p&gt;The stack told a clean story. Every &lt;code&gt;GridItemView&lt;/code&gt; body recompute was building fresh &lt;code&gt;ItemActionsViewModel&lt;/code&gt; instances per cell, each one allocating a fresh &lt;code&gt;GraphQLClient&lt;/code&gt;, each one allocating a fresh &lt;code&gt;CTTelephonyNetworkInfo&lt;/code&gt; (a &lt;code&gt;CoreTelephony&lt;/code&gt; class with a documented 30 to 100ms allocation cost on iOS). Multiply by N visible cells × every recompute. Main thread freezes everywhere.&lt;/p&gt;

&lt;p&gt;Claude grepped the relevant factory file and found the smoking gun: four out of five &lt;code&gt;*Context.make&lt;/code&gt; enums in &lt;code&gt;ActionsFactory.swift&lt;/code&gt; use &lt;code&gt;ViewModelCache.shared.getOrCreateViewModel(...)&lt;/code&gt;. The fifth, &lt;code&gt;SavedItemsContext.make&lt;/code&gt;, bypasses the cache and creates new VMs unconditionally.&lt;/p&gt;

&lt;p&gt;Fix: A 50 lines mirroring the existing cache pattern in &lt;code&gt;BrowseContext.make&lt;/code&gt;. This allowed me to recapture a trace using Time Profiler.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hangs &amp;gt;250ms&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total hang time&lt;/td&gt;
&lt;td&gt;21.97s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GraphQLClient.init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;23.7%&lt;/td&gt;
&lt;td&gt;7.9% (parity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CTTelephonyNetworkInfo.init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18.5%&lt;/td&gt;
&lt;td&gt;6.1% (parity)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6. Stacked PRs and app-wide cleanup (30 minutes)
&lt;/h3&gt;

&lt;p&gt;By this point, three independent fixes had emerged: the leak (a PR), the cache parity (a second PR added to the first), and a third that encompassed the entire application. Even after parity, both SavedItems and Browse were consuming about 6% of the main thread in &lt;code&gt;CTTelephonyNetworkInfo.init&lt;/code&gt;, because the convenience init of &lt;code&gt;GraphQLClient&lt;/code&gt; created a new &lt;code&gt;NetworkConnectivityChecker&lt;/code&gt; each time. This &lt;code&gt;NetworkConnectivityChecker&lt;/code&gt; should be a singleton across the entire application, not just in SavedItems.&lt;/p&gt;

&lt;p&gt;Claude:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;code&gt;gh pr create --base feature/leak-fix-001&lt;/code&gt; to stack PR #4406 on top of PR #4405.&lt;/li&gt;
&lt;li&gt;Opened PR #4407 against &lt;code&gt;dev&lt;/code&gt; directly (independent change).&lt;/li&gt;
&lt;li&gt;Wrote each PR description with the before/after tables embedded. When the Time Profiler validation showed the cache fix alone was sufficient (the originally planned 5-step migration was no longer needed), it dropped the unnecessary steps from the PR scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I reviewed each PR and each commit message before it went out. The total typing I did on those was about 200 characters of confirmation. Everything was draft-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed for me
&lt;/h2&gt;

&lt;p&gt;A few things stand out, and they're not all positive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The not-so-good first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CLI for iOS perf investigation is great, but it's &lt;em&gt;brittle&lt;/em&gt;. &lt;code&gt;xctrace --template Leaks --attach&lt;/code&gt; silently produces empty data due to a &lt;code&gt;libmalloc not initialized&lt;/code&gt; error that you only see if you dig into a SQLite file inside the trace bundle. Some custom logging SDKs don't show in &lt;code&gt;simctl log stream&lt;/code&gt;. SourceKit gets confused after &lt;code&gt;tuist generate&lt;/code&gt; and reports false-positive errors. I had to know about all of these. Claude doesn't always. And the time savings depend on my catching the wrong path before going deep.&lt;/p&gt;

&lt;p&gt;On the other hand, a lot of intuition in mobile engineering is also wrong, and the LLM is faster than I am at testing wrong intuitions. My hypothesis of &lt;code&gt;.fullScreenCover ➜ .sheet&lt;/code&gt; was a wrong path that I would have followed even further without the cheap experimentation cycle. The retention cycle hypothesis I started with ("audit each closure looking for &lt;code&gt;[weak self]&lt;/code&gt;") was also wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Treating each artifact (&lt;code&gt;.memgraph&lt;/code&gt;, &lt;code&gt;.trace&lt;/code&gt;, screenshot) as a &lt;em&gt;programmable input&lt;/em&gt; changes the loop. Memory Graph isn't "open Xcode and stare at the sidebar". It's &lt;code&gt;leaks ~/path.memgraph 2&amp;gt;&amp;amp;1 | grep ROOT CYCLE&lt;/code&gt; piped through Python that an LLM can write inline. Time Profiler isn't "scrub through the timeline in Instruments GUI". It's &lt;code&gt;xctrace export --xpath '/trace-toc/run/data/table[@schema="time-profile"]'&lt;/code&gt; and a 30-line parser. Once the artifacts are in CLI form, the LLM is genuinely useful.&lt;/p&gt;

&lt;p&gt;The LLM is &lt;em&gt;especially&lt;/em&gt; good at the boring parts. Writing a Python parser to fold thousands of stack frames into a top-20 inclusive table is exactly the kind of task it's fast at. Producing a side-by-side comparison table for a PR description with consistent formatting? Same. The stuff that's not &lt;em&gt;intellectually&lt;/em&gt; hard but is &lt;em&gt;attention-tax&lt;/em&gt; hard.&lt;/p&gt;

&lt;p&gt;I keep memory entries in &lt;code&gt;~/.claude/projects/&amp;lt;repo&amp;gt;/memory/&lt;/code&gt; for project-specific facts: the &lt;code&gt;ViewModelCache&lt;/code&gt; pattern, the &lt;code&gt;CTTelephonyNetworkInfo&lt;/code&gt; allocation-cost trap, and the SwiftUI &lt;code&gt;TagIndexProjection&lt;/code&gt; pitfall. Next time someone (me or a colleague who picks up the workflow) starts a similar investigation, the LLM begins with that context instead of rediscovering it.&lt;/p&gt;

&lt;p&gt;I also wrote a slash command, &lt;code&gt;/perf-investigate&lt;/code&gt;, that captures the workflow as a checklist and rejects the natural temptations: don't propose architectural changes before a &lt;code&gt;.memgraph&lt;/code&gt; or &lt;code&gt;.trace&lt;/code&gt; exists, don't use &lt;code&gt;xctrace --template Leaks --attach&lt;/code&gt; because it doesn't work, weak-capture only the closure proven by the memgraph to be the cycle root (not all of them). The slash command is the discipline that keeps me out of dead ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;Three things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capture the "fluid" baseline first.&lt;/strong&gt; When the symptom is "X feels slow", capture Time Profiler on X &lt;em&gt;and&lt;/em&gt; on a sibling feature that's known to be fluid. The comparison is ten times more informative than the absolute numbers. I almost skipped the Browse baseline. That comparison was what made the cache-miss diagnosis irrefutable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resist sizing the ticket to the size of the original plan.&lt;/strong&gt; I scoped the migration as a five-step refactor up front. The Time Profiler showed that step 1 alone closed the gap, and steps 2 to 5 were dropped. If your plan is "do A, then B, then C, then validate", validate after A and re-plan. Don't let the size of the original plan anchor the actual scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Graph CLI is underused even by people who use the GUI version daily.&lt;/strong&gt; &lt;br&gt;
The Memory Graph debugger in Xcode is well known, but most devs never realize there are leaks, heap, and vmmap CLI tools that operate on .memgraph files and are fully scriptable. Combine that with an LLM in the loop, and you get a feedback cycle that most teams haven't tried.&lt;/p&gt;
&lt;h2&gt;
  
  
  I Packaged the Workflow
&lt;/h2&gt;

&lt;p&gt;After this investigation, I sat down and transformed the manual parts into an MCP server, &lt;code&gt;memorydetective&lt;/code&gt;. The first cut used 12 tools to cover the workflow above. By v1.8, it had grown to &lt;strong&gt;31 tools, 34 catalog resources, and 5 Investigation Prompts&lt;/strong&gt; covering the Instruments ecosystem.&lt;/p&gt;

&lt;p&gt;v1.8 in particular was born from a real regression. On macOS 26.x, &lt;code&gt;leaks --outputGraph&lt;/code&gt; aborts with &lt;code&gt;Failed to get DYLD info for task&lt;/code&gt; whenever the target was not launched with &lt;code&gt;MallocStackLogging=1&lt;/code&gt;. The new &lt;code&gt;bootAndLaunchForLeakInvestigation&lt;/code&gt; absorbs build + boot + install + launch with the pre-propagated env var for capture to work out of the box, and &lt;code&gt;captureMemgraph&lt;/code&gt; now returns a structured &lt;code&gt;workaroundNotice&lt;/code&gt; pointing to the &lt;code&gt;recordTimeProfile&lt;/code&gt; (Allocations) fallback when the regression hits anyway. The agent decides; The tools just stop lying about the failure mode.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memgraph analysis: &lt;code&gt;analyzeMemgraph&lt;/code&gt;, &lt;code&gt;findCycles&lt;/code&gt;, &lt;code&gt;classifyCycle&lt;/code&gt; (which would have gotten the &lt;code&gt;TagIndexProjection&lt;/code&gt; cycle in 30 seconds, with fix hint), &lt;code&gt;findRetainers&lt;/code&gt;, &lt;code&gt;diffMemgraphs&lt;/code&gt;, &lt;code&gt;countAlive&lt;/code&gt;, &lt;code&gt;reachableFromCycle&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;cycle-semantic CI gating: &lt;code&gt;verifyFix&lt;/code&gt;, &lt;code&gt;compareTracesByPattern&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;xctrace coverage: &lt;code&gt;analyzeHangs&lt;/code&gt;, &lt;code&gt;analyzeAnimationHitches&lt;/code&gt;, &lt;code&gt;analyzeAllocations&lt;/code&gt;, &lt;code&gt;analyzeAppLaunch&lt;/code&gt;, &lt;code&gt;analyzeTimeProfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;macOS unified logging: &lt;code&gt;logShow&lt;/code&gt;, &lt;code&gt;logStream&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;capture + boot/launch: &lt;code&gt;recordTimeProfile&lt;/code&gt;, &lt;code&gt;captureMemgraph&lt;/code&gt;, &lt;code&gt;bootAndLaunchForLeakInvestigation&lt;/code&gt; (single-call build + boot + launch with &lt;code&gt;MallocStackLogging=1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;verify-fix loop: &lt;code&gt;replayScenario&lt;/code&gt; (drives the simulator via tap/swipe/wait/type with a &lt;code&gt;repeat&lt;/code&gt; count for leaks that only appear after N iterations), &lt;code&gt;captureScenarioState&lt;/code&gt; (composite before/after snapshot: memgraph + screenshot + accessibility tree)&lt;/li&gt;
&lt;li&gt;discovery: &lt;code&gt;getInvestigationPlaybook&lt;/code&gt;, &lt;code&gt;listTraceDevices&lt;/code&gt;, &lt;code&gt;listTraceTemplates&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;retain-cycle visualization (Mermaid/DOT): &lt;code&gt;renderCycleGraph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;leak detection in XCUITest to run in CI: &lt;code&gt;detectLeaksInXCUITest&lt;/code&gt; (experimental)&lt;/li&gt;
&lt;li&gt;bridge with Swift source via SourceKit-LSP: &lt;code&gt;swiftGetSymbolDefinition&lt;/code&gt;, &lt;code&gt;swiftFindSymbolReferences&lt;/code&gt;, &lt;code&gt;swiftSearchPattern&lt;/code&gt;, &lt;code&gt;swiftGetSymbolsOverview&lt;/code&gt;, &lt;code&gt;swiftGetHoverInfo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cycle catalog covers SwiftUI (including Swift 6/&lt;code&gt;@Observable&lt;/code&gt;/ SwiftData/NavigationStack), Combine, Swift Concurrency (including AsyncSequence-on-self), UIKit, Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. Every classification carries a &lt;code&gt;staticAnalysisHint&lt;/code&gt; pointing to the SwiftLint rule that would catch it in the parsing, or an explicit gap warning when there is no static rule. And a &lt;code&gt;fixTemplate&lt;/code&gt; with a Swift before/after snippet that can be directly adapted.&lt;/p&gt;

&lt;p&gt;It's Apache 2.0, it's on npm (&lt;code&gt;memorydetective@1.8.0&lt;/code&gt;), and it works with Claude Code, Claude Desktop, Cursor, Cline, Kiro, and (experimentally) GitHub Copilot Agent mode.&lt;/p&gt;

&lt;p&gt;Two ways to install. The classic MCP path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; memorydetective
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/.claude/settings.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"memorydetective"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"memorydetective"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you're on Claude Code, the same workflow ships as a one-command plugin install (no JSON edit, no global npm):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add carloshpdoc/memorydetective-plugin
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;memorydetective@memorydetective-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin wraps the same MCP server &lt;code&gt;memorydetective@^1.8&lt;/code&gt; (pulled via &lt;code&gt;npx&lt;/code&gt; under the hood) and also includes a slash command &lt;code&gt;/perf-investigate&lt;/code&gt; with the built-in discipline checklist (don't propose architectural changes before memgraph or trace exists, don't trust &lt;code&gt;xctrace --template Leaks --attach&lt;/code&gt;, weak-capture only the closure proven as the root of the cycle, etc.). Same workflow, less typing.&lt;/p&gt;

&lt;p&gt;Then you ask the LLM something like &lt;em&gt;"run leaks on &lt;code&gt;~/Desktop/myapp.memgraph&lt;/code&gt; and tell me what's leaking"&lt;/em&gt;. The agent calls &lt;code&gt;analyzeMemgraph&lt;/code&gt; ➜ &lt;code&gt;classifyCycle&lt;/code&gt; and you receive a structured diagnosis with a fix hint. Or you can use it via the shell: &lt;code&gt;memorydetective analyze ~/Desktop/myapp.memgraph&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What honestly &lt;em&gt;isn't&lt;/em&gt; solid yet in v1.8.0: sample-level Time Profile analysis is still fragile (&lt;code&gt;xctrace export&lt;/code&gt; of the &lt;code&gt;time-profile&lt;/code&gt; schema crashes on heavy, non-symbolized traces; the tool surfaces a workaround). Hang, and animation-hitches analysis are rock-solid. The &lt;code&gt;leaks --outputGraph&lt;/code&gt; regression on macOS 26.x is mitigated via &lt;code&gt;bootAndLaunchForLeakInvestigation&lt;/code&gt;, but not 100% resolved (&lt;code&gt;task_for_pid&lt;/code&gt; down there needs a fix from Apple); when the workaround fails, &lt;code&gt;captureMemgraph&lt;/code&gt; surfaces a structured fallback for Allocations.&lt;/p&gt;

&lt;p&gt;Memory Graph capture works for Mac apps + iOS simulator but not on physical devices (limitation of &lt;code&gt;leaks(1)&lt;/code&gt;, I can't fix it). &lt;code&gt;replayScenario&lt;/code&gt; and the &lt;code&gt;captureScenarioState&lt;/code&gt; UI tree sub-capture have a soft dependency on &lt;a href="https://github.com/cameroncooke/AXe" rel="noopener noreferrer"&gt;axe&lt;/a&gt; (&lt;code&gt;brew install cameroncooke/axe/axe&lt;/code&gt;); the rest of the plugin works without it. &lt;code&gt;detectLeaksInXCUITest&lt;/code&gt; is shipped but marked as experimental until real production runs validate the orchestration. The CHANGELOG is honest about all of this. See &lt;a href="https://github.com/carloshpdoc/memorydetective/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;.&lt;br&gt;
GitHub: &lt;a href="https://github.com/carloshpdoc/memorydetective" rel="noopener noreferrer"&gt;github.com/carloshpdoc/memorydetective&lt;/a&gt;. PRs welcome, especially new cycle patterns from real production leaks you've found.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try this
&lt;/h2&gt;

&lt;p&gt;A short afternoon of setup gets you the whole workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install&lt;/strong&gt; either via npm + MCP config (&lt;code&gt;npm install -g memorydetective&lt;/code&gt; plus 1 line of JSON in your client) or, on Claude Code, via one-line plugin install: &lt;code&gt;/plugin marketplace add carloshpdoc/memorydetective-plugin&lt;/code&gt; then &lt;code&gt;/plugin install memorydetective@memorydetective-plugin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install Claude Code in the terminal if you haven't. Point it at one of your iOS projects.&lt;/li&gt;
&lt;li&gt;(Optional, but recommended) Install &lt;a href="https://github.com/getsentry/XcodeBuildMCP" rel="noopener noreferrer"&gt;XcodeBuildMCP&lt;/a&gt; for the simulator-driving parts. Pairs nicely.&lt;/li&gt;
&lt;li&gt;Spend 20 minutes learning Memory Graph + &lt;code&gt;leaks&lt;/code&gt; on a &lt;code&gt;.memgraph&lt;/code&gt; you generate yourself. Pick a known retain cycle in your codebase, or build a tiny &lt;code&gt;class A { var b: B }&lt;/code&gt; cycle in a playground and confirm &lt;code&gt;leaks&lt;/code&gt; finds it. Then run &lt;code&gt;memorydetective analyze&lt;/code&gt; on it and watch the cycle classified.&lt;/li&gt;
&lt;li&gt;Pick a real perf ticket. Don't reach for the Xcode UI first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The setup is small. The workflow is genuinely faster. The unfair advantage is that most of the iOS engineers I know haven't even tried this loop yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Looking back, the afternoon broke down like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three independent perf fixes shipped in one afternoon (one leak, one cache parity, one app-wide singleton).&lt;/li&gt;
&lt;li&gt;Cost of being wrong dropped from "half a day per hypothesis" to "10 minutes per hypothesis". That single shift mattered more than any individual fix.&lt;/li&gt;
&lt;li&gt;Memory Graph + &lt;code&gt;leaks&lt;/code&gt; CLI gave me a precise retain chain in seconds, instead of four hours of closure-auditing.&lt;/li&gt;
&lt;li&gt;PR housekeeping (descriptions, before/after tables, stack management) was off my plate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tradeoffs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CLI tooling around iOS perf is brittle. Some templates silently produce empty data (&lt;code&gt;xctrace --template Leaks --attach&lt;/code&gt; is the worst offender). You have to know the workarounds, and the LLM doesn't always.&lt;/li&gt;
&lt;li&gt;LLM accepts proposing wholesale refactors of &lt;code&gt;[weak self]&lt;/code&gt; that don't fix the leak (the real cycle is usually in only one closure, not all of them) and even introduce bugs that need fixing: closures that become silent no-ops, lost asynchronous work, races. In worse cases, re-strongification via guard let self recreates the cycle in a different way. Discipline (the slash command /perf-investigate) is what keeps you out of this hole: weak-capture only the closure proven by Memgraph as the root of the cycle, not all of them.&lt;/li&gt;
&lt;li&gt;Some custom logging SDKs route around &lt;code&gt;os_log&lt;/code&gt;, which means MCP-driven log capture won't see them. You fall back to pasting from Xcode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I'd repeat&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture a "fluid" sibling feature as a baseline before reading any absolute numbers.&lt;/li&gt;
&lt;li&gt;Validate after step 1 of any plan, then re-plan. Don't let the original size of a ticket anchor the real scope.&lt;/li&gt;
&lt;li&gt;Treat artifacts (&lt;code&gt;.memgraph&lt;/code&gt;, &lt;code&gt;.trace&lt;/code&gt;) as programmable inputs, not GUI-only files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you, what does your iOS perf investigation flow look like today? Are you using the Memory Graph CLI, or staring at the Xcode sidebar? I'd love to hear what tooling actually moves the needle for you.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and until next time. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory Graph CLI (&lt;code&gt;leaks&lt;/code&gt;, &lt;code&gt;heap&lt;/code&gt;, &lt;code&gt;vmmap&lt;/code&gt;, &lt;code&gt;malloc_history&lt;/code&gt;, and &lt;code&gt;.memgraph&lt;/code&gt; files)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/xcode/gathering-information-about-memory-use" rel="noopener noreferrer"&gt;Apple Developer: Gathering information about memory use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10180/" rel="noopener noreferrer"&gt;WWDC21: Detect and diagnose memory issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/forums/thread/125133" rel="noopener noreferrer"&gt;Apple Developer Forums: Generating Memgraph with leaks tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.manpagez.com/man/1/leaks/osx-10.12.6.php" rel="noopener noreferrer"&gt;man &lt;code&gt;leaks(1)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.halodoc.io/memgraph-detection-of-memory-issues-on-ios/" rel="noopener noreferrer"&gt;Halodoc: Memgraph, detection of memory issues on iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;[weak self]&lt;/code&gt; (mechanics, perf overhead, and re-strongification pitfalls)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.donnywals.com/when-to-use-weak-self-and-why/" rel="noopener noreferrer"&gt;Donny Wals: when to use &lt;code&gt;[weak self]&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.avanderlee.com/swift/weak-self/" rel="noopener noreferrer"&gt;SwiftLee: weak vs unowned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.jacobstechtavern.com/p/swift-reference-counting" rel="noopener noreferrer"&gt;Jacob Bartlett: bits &amp;amp; side tables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;A deep technical companion piece on the actual leak (a SwiftUI &lt;code&gt;TagIndexProjection&amp;lt;Int&amp;gt;&lt;/code&gt; cycle through &lt;code&gt;_DictionaryStorage&amp;lt;AnyHashable, WeakBox&amp;lt;AnyLocationBase&amp;gt;&amp;gt;&lt;/code&gt;, with the full retain chain and the wrong wholesale-&lt;code&gt;[weak self]&lt;/code&gt; refactor I tried first) is coming next week. Different audience: pure technical, no AI workflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>ai</category>
      <category>productivity</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
