<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ai-Agents on Damian Galarza | Software Engineering &amp; AI Consulting</title><link>https://www.damiangalarza.com/tags/ai-agents/</link><description>Recent posts from Damian Galarza | Software Engineering &amp; AI Consulting</description><generator>Hugo</generator><language>en-us</language><managingEditor>Damian Galarza</managingEditor><atom:link href="https://www.damiangalarza.com/tags/ai-agents/feed.xml" rel="self" type="application/rss+xml"/><item><title>Shrinking a Production Prompt by 28% With Autonomous Optimization</title><link>https://www.damiangalarza.com/posts/2026-04-06-autonomous-optimization-loops-with-autoresearch/</link><pubDate>Mon, 06 Apr 2026 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-04-06-autonomous-optimization-loops-with-autoresearch/</guid><description>How I used autoresearch to run 65 autonomous prompt optimization iterations on a production LLM agent, cutting it 28% while retaining 98% output quality.</description><content:encoded><![CDATA[<p>Every token in a production LLM prompt costs you latency, money, and <a href="/posts/understanding-claude-code-context-window/">context window</a> space. An agent I&rsquo;ve been building takes ~170 input categories and produces a detailed structured matrix as output. The system prompt includes a 421-line reference matrix as a few-shot example gallery so the model knows the expected output patterns.</p>
<p>The question was concrete: how much of this reference data does the model actually need? I used <a href="https://github.com/uditgoenka/autoresearch">uditgoenka/autoresearch</a>, a Claude Code skill based on <a href="https://github.com/karpathy/autoresearch">Andrej Karpathy&rsquo;s autoresearch</a>, to find out. After over 65 autonomous iterations, it cut the matrix to 303 lines (28% smaller) while maintaining 98.1% output quality.</p>
<p>Here&rsquo;s the prompt optimization pattern, the results, and what surprised me about how robust LLMs are to reference data reduction.</p>
<h2 id="the-autoresearch-pattern">The Autoresearch Pattern</h2>
<p>Andrej Karpathy&rsquo;s <a href="https://github.com/karpathy/autoresearch">autoresearch</a> introduced the core idea: give an AI agent a metric to optimize and let it loop. Modify, measure, keep or revert, repeat.</p>
<figure class="tweet-screenshot"><a href="https://x.com/karpathy/status/2030371219518931079"><img src="/images/posts/autoresearch/karpathy-tweet.png"
    alt="Andrej Karpathy announcing autoresearch on X"></a>
</figure>

<p>Udit Goenka built a <a href="https://github.com/uditgoenka/autoresearch">Claude Code skill</a> that brings this pattern to arbitrary optimization tasks, adding a dedicated guard command to prevent regressions.</p>
<p>You define six parameters:</p>
<ul>
<li><strong>Goal</strong>: what you want to improve</li>
<li><strong>Scope</strong>: which files the agent can modify</li>
<li><strong>Metric</strong>: a number extracted from a shell command (line count, test coverage, score)</li>
<li><strong>Direction</strong>: whether higher or lower is better</li>
<li><strong>Verify</strong>: the command that produces the metric</li>
<li><strong>Guard</strong>: a safety net command that must always pass</li>
</ul>
<p>Each iteration follows the same cycle: modify, commit to git, verify the metric, run the guard, keep or revert. Every experiment gets committed before verification, so rollbacks are clean. It tracks results in a TSV log and reads its own git history to avoid repeating failed approaches.</p>
<p>The separation between metric and guard is what makes this work. The metric tells autoresearch &ldquo;did we make progress?&rdquo; while the guard tells it &ldquo;did we break anything?&rdquo; Keeping those independent lets the loop optimize aggressively while the guard catches regressions.</p>
<h2 id="setting-up-the-prompt-optimization-experiment">Setting Up the Prompt Optimization Experiment</h2>
<h3 id="scope">Scope</h3>
<p>I scoped autoresearch to a single file — the reference matrix itself. I didn&rsquo;t want it touching the agent&rsquo;s prompt instructions, the recommendation library, or the eval infrastructure. Just the example data.</p>
<p>The alternative was to also let it modify the agent prompt, changing how the matrix is described. But I wanted to isolate the variable: same prompt instructions, same recommendation library, just less example data.</p>
<h3 id="metric">Metric</h3>
<p>For the metric, I used line count. It&rsquo;s simple, deterministic, and directly measures what we care about — how much data gets injected into the prompt. The metric doesn&rsquo;t measure quality at all. That&rsquo;s the guard&rsquo;s job.</p>
<h3 id="guard">Guard</h3>
<p>The quality gate was our existing golden-benchmark eval:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#f5e0dc">EVAL_QUIET</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#89dceb">true</span> npx vitest run --config vitest.evals.config.ts <span style="color:#89b4fa">\
</span></span></span><span style="display:flex;"><span><span style="color:#89b4fa"></span>  src/evals/matrix-generation/golden-benchmark.test.ts
</span></span></code></pre></div><p>This eval feeds all 166 input categories into the agent, runs the full matrix generation end-to-end via LLM, and compares the output against golden reference data across four dimensions:</p>
<ol>
<li><strong>Input category coverage</strong> (did it produce rows for every input category?)</li>
<li><strong>Output category accuracy</strong> (correct category assignment?)</li>
<li><strong>Recommendation overlap</strong> (right recommendations from the library?)</li>
<li><strong>Assignment accuracy</strong> (correct responsible party?)</li>
</ol>
<p>The guard must exit 0 (all Vitest assertions pass) for a change to be kept. Each guard run took 5-7 minutes because it makes a real LLM API call to generate the full matrix.</p>
<h2 id="the-65-iteration-run">The 65-Iteration Run</h2>
<p>I ran three rounds:</p>
<table>
  <thead>
      <tr>
          <th>Round</th>
          <th>Iterations</th>
          <th>Lines</th>
          <th>Focus</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>25</td>
          <td>421 → 337</td>
          <td>Easy wins: exact duplicates, severity level consolidation, frequency variants</td>
      </tr>
      <tr>
          <td>2</td>
          <td>25</td>
          <td>337 → 255</td>
          <td>Deeper cuts: multi-row category groups, shared high-severity rows</td>
      </tr>
      <tr>
          <td>3</td>
          <td>15</td>
          <td>255 → 197</td>
          <td>Aggressive: most multi-row groups reduced to 1-2 representatives</td>
      </tr>
  </tbody>
</table>
<p>Each iteration took 6-8 minutes (mostly the guard eval). Total wall time was roughly 7-8 hours across three rounds.</p>
<p>The agent&rsquo;s approach was systematic. In round 1, it found the free wins: 5 exact duplicate rows, frequency variants (7 rows for different weekly frequencies that could collapse to 2), and severity levels where lower severity was always a subset of moderate. In later rounds, it got more aggressive, reducing most multi-row category groups to single representative entries.</p>
<h2 id="results-after-fixing-the-eval-baseline">Results After Fixing the Eval Baseline</h2>
<p>During the run, I discovered that the original eval had a self-referencing bug. Both the agent prompt and the eval&rsquo;s golden comparison data imported from the same <code>REFERENCE_MATRIX_CSV</code> constant. Every time autoresearch shrank the reference matrix, it also shrank what the eval compared against. The eval was proving &ldquo;the model can reproduce a smaller matrix&rdquo; rather than &ldquo;the model handles all real-world input categories correctly.&rdquo;</p>
<p>The fix was straightforward. I split the data into two files:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// src/data/reference-matrix.ts — injected into the agent prompt (optimized)
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#cba6f7">export</span> <span style="color:#cba6f7">const</span> REFERENCE_MATRIX_CSV <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">`...`</span>; <span style="color:#6c7086;font-style:italic">// 303 lines after optimization
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// src/data/golden-reference-matrix.ts — used by eval (immutable)
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#cba6f7">export</span> <span style="color:#cba6f7">const</span> GOLDEN_REFERENCE_MATRIX_CSV <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">`...`</span>; <span style="color:#6c7086;font-style:italic">// original 421 lines, never changes
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// src/evals/matrix-generation/golden-benchmark.test.ts
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// Before: import { REFERENCE_MATRIX_CSV } from &#39;../../data/reference-matrix&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#cba6f7">import</span> { GOLDEN_REFERENCE_MATRIX_CSV } <span style="color:#cba6f7">from</span> <span style="color:#a6e3a1">&#39;../../data/golden-reference-matrix&#39;</span>;
</span></span></code></pre></div><p>With the fixed eval, I binary-searched through the git history to find the optimal size. Because autoresearch commits every experiment, the full optimization history was available to test against the corrected eval.</p>
<table>
  <thead>
      <tr>
          <th>Lines</th>
          <th>Reduction</th>
          <th>Overall Score</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>421</td>
          <td>0%</td>
          <td>~99.9%</td>
          <td>Baseline</td>
      </tr>
      <tr>
          <td>337</td>
          <td>-20%</td>
          <td>99.1%</td>
          <td>Above 98%</td>
      </tr>
      <tr>
          <td>308</td>
          <td>-27%</td>
          <td>98.5%</td>
          <td>Above 98%</td>
      </tr>
      <tr>
          <td>305</td>
          <td>-28%</td>
          <td>98.4%</td>
          <td>Above 98%</td>
      </tr>
      <tr>
          <td><strong>303</strong></td>
          <td><strong>-28%</strong></td>
          <td><strong>98.1%</strong></td>
          <td><strong>Sweet spot</strong></td>
      </tr>
      <tr>
          <td>297</td>
          <td>-29%</td>
          <td>97.7%</td>
          <td>Below 98%</td>
      </tr>
      <tr>
          <td>283</td>
          <td>-33%</td>
          <td>96.9%</td>
          <td>Below 98%</td>
      </tr>
      <tr>
          <td>255</td>
          <td>-39%</td>
          <td>~96%</td>
          <td>Below 98%</td>
      </tr>
      <tr>
          <td>197</td>
          <td>-53%</td>
          <td>95.5%</td>
          <td>Too aggressive</td>
      </tr>
  </tbody>
</table>
<p>The sweet spot is 303 lines: a 28% reduction maintaining 98%+ overall quality. The quality cliff appears around iteration 35, where the agent removed shared high-severity rows that contained unique recommendation mappings.</p>
<p>At 303 lines, the score breakdown:</p>
<ul>
<li><strong>Input category coverage:</strong> 100% (all 166 golden categories present)</li>
<li><strong>Output category accuracy:</strong> 100%</li>
<li><strong>Recommendation overlap:</strong> 90.5% (about 32 specific recommendations lost)</li>
<li><strong>Assignment accuracy:</strong> 99.7%</li>
<li><strong>Overall weighted score:</strong> 98.1%</li>
</ul>
<p>The main quality cost is recommendation overlap. The model still covers all input categories and assigns output categories correctly, but produces slightly fewer recommendation rows per category. For this use case, that&rsquo;s an acceptable tradeoff: 118 fewer lines in every prompt for a 1.9% quality reduction.</p>
<h2 id="what-this-reveals-about-llms-and-reference-data">What This Reveals About LLMs and Reference Data</h2>
<p>The most useful finding isn&rsquo;t the 28% number. It&rsquo;s the degradation curve.</p>
<p>Even at 197 lines (53% cut), the model still hit 95.5%. It correctly covered all input categories and most output categories. The recommendation library (a separate 337-entry file in the prompt) carries much of the mapping knowledge. The reference matrix turned out to be more &ldquo;example gallery&rdquo; than &ldquo;source of truth.&rdquo; The model uses it to learn output patterns, not to look up specific mappings.</p>
<p>This has implications for any system that injects large reference data into prompts. The model likely doesn&rsquo;t need all of it. But you need a correct eval to find the actual boundary, and the degradation is gradual, not a cliff. Without a quality gate, you won&rsquo;t know where that boundary is until users report problems.</p>
<h2 id="lessons-for-running-autonomous-optimization-loops">Lessons for Running Autonomous Optimization Loops</h2>
<h3 id="the-guard-is-what-makes-it-work">The guard is what makes it work</h3>
<p>Without a quality gate, autoresearch is a deletion loop. The guard is the only thing preventing it from removing everything. This sounds obvious until you see how easy it is to write a guard that doesn&rsquo;t actually guard.</p>
<h3 id="separate-your-optimization-target-from-your-eval-baseline">Separate your optimization target from your eval baseline</h3>
<p>If your golden data is the same data you&rsquo;re optimizing, you&rsquo;ll always pass. This is easy to do when the reference data serves dual purpose (prompt injection and eval comparison). Split them from the start. The optimization target is mutable. The eval baseline is immutable.</p>
<h3 id="git-as-memory-enables-post-hoc-analysis">Git-as-memory enables post-hoc analysis</h3>
<p>Autoresearch commits every experiment before verification. This is a form of <a href="/posts/how-ai-agents-remember-things/">agent memory</a> that pays off after the run ends: I was able to binary-search through the history after fixing the eval, finding the exact commit where quality degraded. Without that history, I would have had to re-run the entire optimization from scratch.</p>
<h3 id="guard-speed-determines-iteration-budget">Guard speed determines iteration budget</h3>
<p>Fast guards (line count, type checks, unit tests) enable hundreds of iterations overnight. Slow guards (LLM-based evals, end-to-end tests) limit you to 10-15 iterations per hour. Plan your guard complexity based on how many iterations you can afford.</p>
<h2 id="applying-this-pattern-to-other-prompt-components">Applying This Pattern to Other Prompt Components</h2>
<p>The recommendation library (a separate 337-entry reference file also injected into every prompt) is the next candidate for the same treatment. Same loop, same approach, but with the eval separation built in from the start.</p>
<p>The pattern generalizes to any prompt optimization problem: define the metric, build a correct guard, let the agent loop. The constraint is always the guard. A guard that looks correct but measures the wrong thing is worse than no guard at all.</p>
<p>I built a one-page scorecard based on the four layers of agent evaluation — component testing, trajectory visibility, outcome measurement, and production monitoring. It takes two minutes and shows you where your gaps are. <a href="/agent-eval-scorecard/">Get the Agent Eval Scorecard →</a></p>
<p>If you&rsquo;re past the scorecard stage and want hands-on help with eval design or prompt optimization, <a href="/ai-agents/">let&rsquo;s talk</a>.</p>
<h2 id="additional-reading">Additional Reading</h2>
<ul>
<li><a href="https://github.com/uditgoenka/autoresearch">autoresearch Claude Code skill</a> by Udit Goenka</li>
<li><a href="https://github.com/karpathy/autoresearch">autoresearch</a> by Andrej Karpathy</li>
</ul>
]]></content:encoded></item><item><title>Four Dimensions of Agent-Ready Codebase Design</title><link>https://www.damiangalarza.com/posts/2026-03-25-four-patterns-that-separate-agent-ready-codebases/</link><pubDate>Wed, 25 Mar 2026 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-03-25-four-patterns-that-separate-agent-ready-codebases/</guid><description>AI agents produce better output when the codebase is ready for them. Here are the four dimensions of codebase readiness that account for most of the gap.</description><content:encoded><![CDATA[<p>When an AI agent rewrites a file and the result doesn&rsquo;t match your conventions, the first move is usually to adjust the prompt. Try different instructions. Add more context to the message. Maybe switch models.</p>
<p>The model is rarely the bottleneck. The codebase is.</p>
<p>The same model, pointed at a codebase with strong tests, clear architecture, and good documentation, produces remarkably consistent output. Point it at a codebase with weak coverage, no architecture docs, and no linting, and you get drift. Not because the model is less capable, but because it has less to work with.</p>
<p>I built the <a href="/codebase-readiness/">Codebase Readiness Assessment</a> to make this measurable. It scores your repo across eight dimensions on a 0-100 scale. But you don&rsquo;t need to run the assessment to understand what separates high-scoring codebases from low-scoring ones. Four dimensions account for most of the gap.</p>
<h2 id="test-foundation">Test Foundation</h2>
<p>Test foundation carries the most weight in the assessment (25%) because it&rsquo;s the single biggest lever for agent output quality.</p>
<h3 id="what-a-low-score-looks-like">What a low score looks like</h3>
<p>An agent makes a change. There are no tests covering that area, so it moves on. The change compiles, maybe even runs, but it broke an assumption three modules away. Nobody finds out until a human reviews the PR, or worse, until production.</p>
<p>I&rsquo;ve seen this repeatedly: teams with 30-40% test coverage ask an agent to refactor a service object. The agent produces clean code that looks right. But there&rsquo;s no spec for the edge case where a nil association triggers a downstream error. The agent had no way to catch it because there&rsquo;s no test to fail.</p>
<p>The other failure mode is slow tests. If your suite takes 20 minutes, the agent can&rsquo;t iterate. It makes a change, waits, discovers the failure, tries again, waits again. In a fast suite, that feedback cycle takes seconds. In a slow one, the agent burns time and money waiting for results.</p>
<h3 id="what-a-high-score-looks-like">What a high score looks like</h3>
<p>Codebases that score well here share a few characteristics:</p>
<ul>
<li><strong>Coverage above 70% on critical paths.</strong> Not 100% everywhere, but thorough coverage on the code that matters: domain logic, service objects, API endpoints. The agent can make changes and get immediate confirmation that nothing broke.</li>
<li><strong>Suite runs in under 5 minutes.</strong> Fast enough that the agent can run tests after every meaningful change, not just at the end.</li>
<li><strong>Deterministic results.</strong> No flaky tests. When the suite says green, it means green. Agents can&rsquo;t distinguish between a flaky failure and a real one, so flaky tests teach agents to ignore failures.</li>
</ul>
<h3 id="dont-stop-at-unit-tests">Don&rsquo;t stop at unit tests</h3>
<p>Unit tests on service objects and models are the foundation, but they only verify isolated behavior. An agent that passes all unit tests can still break a user-facing workflow that spans multiple components.</p>
<p>End-to-end tests give agents confidence across entire flows. A system spec that signs a user in, submits a form, and checks the result tells the agent whether the <em>feature</em> works, not just whether a method returns the right value. This is especially valuable when agents make changes that touch controllers, views, and services in the same PR.</p>
<p>Here&rsquo;s a simplified system spec from one of my Rails projects. It covers the core user journey: signing in and submitting a video idea for validation.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># spec/system/idea_submission_spec.rb</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">RSpec</span><span style="color:#89dceb;font-weight:bold">.</span>describe <span style="color:#a6e3a1">&#34;Idea submission&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  it <span style="color:#a6e3a1">&#34;allows a signed-in user to submit a video idea&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    user <span style="color:#89dceb;font-weight:bold">=</span> create(<span style="color:#a6e3a1">:user</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    sign_in_as(user, <span style="color:#a6e3a1">path</span>: new_idea_path)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb">select</span> user<span style="color:#89dceb;font-weight:bold">.</span>channels<span style="color:#89dceb;font-weight:bold">.</span>first<span style="color:#89dceb;font-weight:bold">.</span>name, <span style="color:#a6e3a1">from</span>: <span style="color:#a6e3a1">&#34;Channel&#34;</span>
</span></span><span style="display:flex;"><span>    fill_in <span style="color:#a6e3a1">&#34;Title&#34;</span>, <span style="color:#a6e3a1">with</span>: <span style="color:#a6e3a1">&#34;Building a Rails AI Agent from Scratch&#34;</span>
</span></span><span style="display:flex;"><span>    fill_in <span style="color:#a6e3a1">&#34;Description&#34;</span>, <span style="color:#a6e3a1">with</span>: <span style="color:#a6e3a1">&#34;Step-by-step tutorial on building an AI agent&#34;</span>
</span></span><span style="display:flex;"><span>    fill_in <span style="color:#a6e3a1">&#34;Category&#34;</span>, <span style="color:#a6e3a1">with</span>: <span style="color:#a6e3a1">&#34;AI Coding&#34;</span>
</span></span><span style="display:flex;"><span>    click_button <span style="color:#a6e3a1">&#34;Validate Idea&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_content(<span style="color:#a6e3a1">&#34;Building a Rails AI Agent from Scratch&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>This test touches authentication, the form UI, the controller, the background job, and the results page. If an agent breaks any part of that chain, this spec catches it.</p>
<p>The tradeoff is speed. End-to-end tests are slower and more brittle than unit tests. You don&rsquo;t need full E2E coverage, but having system specs on your critical user journeys (signup, checkout, the core action your product is built around) gives agents a safety net that unit tests alone can&rsquo;t provide.</p>
<h3 id="the-smallest-change-that-moves-the-needle">The smallest change that moves the needle</h3>
<p>Add coverage to your critical paths first. Don&rsquo;t chase a coverage number. Instead, identify the three or four service objects or domain models where bugs would hurt the most, and write specs for those. Then add one or two system specs covering your most important user journeys end-to-end. If your suite is slow, add parallel test execution. In a Rails app, that might be as simple as adding the <code>parallel_tests</code> gem. A suite that goes from 15 minutes to 4 minutes fundamentally changes how an agent can work with your code. If you&rsquo;re running multiple agents in parallel, you&rsquo;ll also need <a href="/posts/2026-03-10-extending-claude-code-worktrees-for-true-database-isolation/">database isolation per worktree</a> to prevent test data collisions.</p>
<p>If you want to accelerate the process, tools like <a href="https://github.com/uditgoenka/autoresearch">autoresearch</a> apply this pattern as an autonomous loop: give the agent a measurable goal (like a coverage target), and it iterates, verifies, keeps what works, and discards what doesn&rsquo;t.</p>
<h2 id="documentation-as-code">Documentation as Code</h2>
<p>Documentation carries 15% of the assessment weight, but in practice it&rsquo;s the dimension where I see the biggest gap between teams that get good agent output and teams that don&rsquo;t.</p>
<h3 id="what-a-low-score-looks-like-1">What a low score looks like</h3>
<p>Without an agent-facing entry point (a <code>CLAUDE.md</code>, <code>AGENTS.md</code>, or equivalent), an agent has to reverse-engineer your conventions from the code itself. It reads your files, infers patterns, and guesses at intent. Sometimes it guesses right. Often it doesn&rsquo;t.</p>
<p>Here&rsquo;s a concrete example. A Rails app uses service objects for all business logic. Controllers call a service, the service does the work, and the result gets rendered. There&rsquo;s nothing enforcing this in the framework. It&rsquo;s a team convention. An agent that doesn&rsquo;t know about this convention puts the logic directly in the controller action. The code works. The tests pass. But it breaks the team&rsquo;s pattern, and now there&rsquo;s a 50-line controller action that should have been a service object.</p>
<p>The agent wasn&rsquo;t wrong. It had no way to know.</p>
<h3 id="what-a-high-score-looks-like-1">What a high score looks like</h3>
<p>The key insight is that this entry point file should be a map, not a manual. OpenAI&rsquo;s Harness Engineering team <a href="https://openai.com/index/harness-engineering/">learned this the hard way</a>: they tried a single large instruction file and it failed because &ldquo;context is a scarce resource&rdquo; and &ldquo;too much guidance becomes non-guidance.&rdquo; When everything is marked important, agents pattern-match locally instead of navigating intentionally.</p>
<p>Their solution: keep the entry file short (roughly 100 lines) and treat it as a table of contents that points to deeper sources of truth in a structured <code>docs/</code> directory. The entry file gives agents quick commands and a documentation map. The detail lives in dedicated files the agent reads when it needs them. Whether you call it <code>CLAUDE.md</code>, <code>AGENTS.md</code>, or <code>CURSOR.md</code>, the pattern is the same.</p>
<p>Here&rsquo;s what this looks like in practice from one of my Rails projects:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold">## Quick Commands
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>bin/dev                                # Start dev server
</span></span><span style="display:flex;"><span>bin/rails spec                         # All tests
</span></span><span style="display:flex;"><span>bin/ci                                 # Full CI: lint + security + tests
</span></span><span style="display:flex;"><span>bin/rubocop                            # Lint
</span></span><span style="display:flex;"><span>bin/brakeman                           # Security scan
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold">## Documentation Map
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>| Topic | Document |
</span></span><span style="display:flex;"><span>|-------|----------|
</span></span><span style="display:flex;"><span>| Stack, patterns, domain model | docs/ARCHITECTURE.md |
</span></span><span style="display:flex;"><span>| Testing patterns and stack | docs/TESTING.md |
</span></span><span style="display:flex;"><span>| Credentials, env vars, API keys | docs/CONFIGURATION.md |
</span></span><span style="display:flex;"><span>| Engineering principles | docs/design-docs/core-beliefs.md |
</span></span><span style="display:flex;"><span>| Architecture decision records | docs/design-docs/ |
</span></span></code></pre></div><p>The agent gets commands and a map up front. When it needs to understand the domain model or testing conventions, it follows the pointer. This is progressive disclosure: the agent starts with what it needs immediately and loads deeper context on demand.</p>
<p>Here&rsquo;s a trimmed excerpt from the <code>ARCHITECTURE.md</code> behind that pointer:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold">## Domain Model
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>CreatorSignal validates YouTube video ideas. The core flow:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">1.</span> User submits a video <span style="font-weight:bold">**Idea**</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">2.</span> A <span style="font-weight:bold">**Validation**</span> job is enqueued
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">3.</span> The <span style="font-weight:bold">**ResearchAgent**</span> runs tools against YouTube, Reddit, X, and HN
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">4.</span> Results are synthesized into a scored <span style="font-weight:bold">**Go / Refine / Kill**</span> verdict
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold">### Key Models
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>| Model | Responsibility |
</span></span><span style="display:flex;"><span>|-------|---------------|
</span></span><span style="display:flex;"><span>| <span style="color:#a6e3a1">`User`</span> | Authentication, subscription plan |
</span></span><span style="display:flex;"><span>| <span style="color:#a6e3a1">`Idea`</span> | A video idea submitted for validation |
</span></span><span style="display:flex;"><span>| <span style="color:#a6e3a1">`Validation`</span> | One run of the research agent against an idea |
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold">### Project Structure
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>app/
</span></span><span style="display:flex;"><span>├── components/       # ViewComponent components
</span></span><span style="display:flex;"><span>├── controllers/
</span></span><span style="display:flex;"><span>├── jobs/             # ActiveJob jobs (async validation)
</span></span><span style="display:flex;"><span>├── models/
</span></span><span style="display:flex;"><span>├── services/         # Research agent, tool orchestration
</span></span><span style="display:flex;"><span>└── views/            # Hotwire (Turbo frames/streams)
</span></span></code></pre></div><p>An agent reading this knows what an <code>Idea</code> is, that validation is async through a job, and that orchestration logic lives in <code>app/services/</code>. Those are the conventions that prevent drift.</p>
<p>ADRs (Architecture Decision Records) add a layer that documentation alone can&rsquo;t. An agent that understands <em>why</em> a particular pattern was chosen can make better decisions when extending it. If your ADR says &ldquo;we chose event sourcing for the billing domain because of auditability requirements,&rdquo; the agent won&rsquo;t try to refactor billing into simple CRUD.</p>
<h3 id="the-smallest-change-that-moves-the-needle-1">The smallest change that moves the needle</h3>
<p>Create an <code>AGENTS.md</code> in your project root with two things: commands (build, test, lint) and a documentation map pointing to deeper files. <a href="https://agents.md/"><code>AGENTS.md</code></a> is an emerging standard supported by Codex, Cursor, Gemini CLI, GitHub Copilot, Windsurf, Devin, and <a href="https://agents.md/">many others</a>. If you&rsquo;re using Claude Code, symlink <code>CLAUDE.md</code> to it so both resolve to the same file. Then create an <code>ARCHITECTURE.md</code> covering your stack, domain model, and key conventions. This can take an hour and the effect on agent output is immediate. If you want to automate the scaffolding, the <a href="https://github.com/dgalarza/claude-code-workflows">agent-ready plugin</a> generates a starting point based on your existing codebase.</p>
<h2 id="architecture-clarity">Architecture Clarity</h2>
<p>Architecture clarity carries 15% of the assessment weight. It measures whether an agent can understand where code belongs and how components relate to each other.</p>
<h3 id="what-a-low-score-looks-like-2">What a low score looks like</h3>
<p>Agents replicate patterns they find in the codebase. If your codebase has clear boundaries (controllers handle HTTP, services handle business logic, models handle persistence), the agent follows those boundaries. If your codebase mixes concerns, the agent mixes concerns.</p>
<p>The most common failure I see: a controller that does everything. It validates input, calls the database, sends emails, enqueues jobs. An agent asked to add a new feature looks at the existing controller, sees that&rsquo;s where logic goes, and adds more logic to the controller. The agent is doing exactly what the codebase taught it to do.</p>
<p>The subtler version is dependency direction. In a well-layered app, dependencies point inward: controllers depend on services, services depend on models. When that direction is inconsistent (models importing from controllers, services reaching into HTTP request objects), agents produce code with the same tangled dependencies.</p>
<h3 id="what-a-high-score-looks-like-2">What a high score looks like</h3>
<ul>
<li><strong>Clear layering.</strong> Each layer has a single responsibility, and the codebase is consistent about which layer owns what.</li>
<li><strong>Domain namespacing.</strong> Related functionality is grouped by business domain, not just by technical layer. Instead of a flat <code>app/services/</code> with 40 files, you have <code>app/services/billing/</code>, <code>app/services/onboarding/</code>, <code>app/services/research/</code>. When an agent needs to add billing logic, the namespace tells it exactly where to look and what patterns to follow.</li>
<li><strong>Predictable file organization.</strong> A new developer (or agent) can guess where a piece of code lives based on what it does.</li>
<li><strong>Dependency direction is consistent.</strong> Inner layers don&rsquo;t reach outward. You don&rsquo;t see models importing controller concerns.</li>
</ul>
<p>Domain namespacing is especially powerful for agents because it constrains the search space. An agent working on a billing feature only needs to understand the billing namespace, not the entire codebase. It finds the existing patterns in that namespace and replicates them. Without namespacing, the agent has to scan the whole codebase to figure out where billing logic lives, and it might find three different patterns in three different places.</p>
<h3 id="the-smallest-change-that-moves-the-needle-2">The smallest change that moves the needle</h3>
<p>If you have fat controllers, extract one. Pick your most complex controller action, pull the business logic into a service object, and write a spec for it. The agent will start using that service object pattern for new features. One well-structured example teaches the agent more than any documentation, because it&rsquo;s a pattern it can directly replicate.</p>
<p>If your codebase has grown past a handful of services, start namespacing by domain. Group related services, jobs, and models under a shared namespace. This compounds quickly: once you have three or four service objects under <code>Billing::</code>, agents start producing new billing code in the same namespace by default. The codebase becomes self-reinforcing.</p>
<h2 id="feedback-loops">Feedback Loops</h2>
<p>Feedback loops carry 10% of the assessment weight, but their impact is multiplicative. Good feedback loops make everything else work better. Poor ones make everything else work worse.</p>
<h3 id="what-a-low-score-looks-like-3">What a low score looks like</h3>
<p>Agents learn from the signals they get back. When the only signal is &ldquo;tests passed,&rdquo; the agent has no way to know it introduced a style violation, broke a naming convention, or used a deprecated API. It moves on, confident the change is correct.</p>
<p>Two things make feedback loops weak: <strong>narrow signals</strong> and <strong>slow signals</strong>.</p>
<p>Narrow signals mean the agent only hears from one source. Tests tell the agent whether the code works. They don&rsquo;t tell it whether the code follows your conventions, whether it introduced a security vulnerability, or whether the UI actually renders correctly. Each missing signal is a category of problems the agent can&rsquo;t self-correct.</p>
<p>Slow signals are just as damaging. If the agent has to wait 20 minutes for a CI run to discover a linting error, it&rsquo;s already moved on. It&rsquo;s built three more features on top of code that doesn&rsquo;t pass lint. Now you&rsquo;re unwinding multiple changes instead of catching the first one. The closer the feedback is to the moment of the change, the cheaper it is to fix.</p>
<p>There&rsquo;s also a hierarchy to how you enforce conventions. Anything that can be checked deterministically by a linter should be a lint rule, not a line in your <code>CLAUDE.md</code>. A lint rule catches every violation, every time. A documentation rule depends on the agent reading it and choosing to follow it. If your convention is &ldquo;methods must be under 20 lines&rdquo; or &ldquo;always use <code>frozen_string_literal</code>,&rdquo; encode it in RuboCop, ESLint, or whatever linter your stack uses. Save documentation for the things that can&rsquo;t be mechanically enforced: architectural decisions, domain context, workflow conventions.</p>
<h3 id="what-a-high-score-looks-like-3">What a high score looks like</h3>
<ul>
<li><strong>Pre-commit hooks for immediate feedback.</strong> The agent discovers formatting issues, type errors, or lint violations before it even commits.</li>
<li><strong>CI that runs in under 10 minutes.</strong> Fast enough that the agent can push, get feedback, and iterate without burning excessive context.</li>
<li><strong>Rich error messages.</strong> Linting output that says &ldquo;method too long (25 lines, max 20)&rdquo; is actionable. A generic &ldquo;style violation&rdquo; is not.</li>
</ul>
<p>Here&rsquo;s what a CI script looks like when it goes beyond just running tests. This is the <code>bin/ci</code> from the same Rails project:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config/ci.rb - run with bin/ci</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">CI</span><span style="color:#89dceb;font-weight:bold">.</span>run <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  step <span style="color:#a6e3a1">&#34;Setup&#34;</span>, <span style="color:#a6e3a1">&#34;bin/setup --skip-server&#34;</span>
</span></span><span style="display:flex;"><span>  step <span style="color:#a6e3a1">&#34;Style: Ruby&#34;</span>, <span style="color:#a6e3a1">&#34;bin/rubocop&#34;</span>
</span></span><span style="display:flex;"><span>  step <span style="color:#a6e3a1">&#34;Security: Gem audit&#34;</span>, <span style="color:#a6e3a1">&#34;bin/bundler-audit&#34;</span>
</span></span><span style="display:flex;"><span>  step <span style="color:#a6e3a1">&#34;Security: Importmap vulnerability audit&#34;</span>, <span style="color:#a6e3a1">&#34;bin/importmap audit&#34;</span>
</span></span><span style="display:flex;"><span>  step <span style="color:#a6e3a1">&#34;Security: Brakeman code analysis&#34;</span>, <span style="color:#a6e3a1">&#34;bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Five steps, each giving the agent a different kind of feedback. RuboCop catches style violations. Bundler-audit catches vulnerable gems. Brakeman catches security issues in the code itself. An agent that runs <code>bin/ci</code> gets five signals instead of one.</p>
<h3 id="browser-access-as-a-feedback-loop">Browser access as a feedback loop</h3>
<p>For web applications, there&rsquo;s a feedback loop that most teams overlook: giving agents the ability to see what they built.</p>
<p>An agent that can only run tests is working blind on anything visual. It can verify that a controller returns 200, but it can&rsquo;t tell whether the page actually renders correctly, whether a modal opens, or whether a form submits without errors. Cursor&rsquo;s team <a href="https://cursor.com/blog/agent-computer-use">wrote about this</a>: once they gave agents browser access via cloud sandboxes, agents could &ldquo;iterate until they&rsquo;ve validated their output rather than handing off the first attempt.&rdquo; More than 30% of their merged PRs are now created by agents operating autonomously in cloud sandboxes.</p>
<p>You don&rsquo;t need a full cloud sandbox to get value from this. Claude Code has <a href="https://code.claude.com/docs/en/chrome">built-in Chrome support</a> via <code>claude --chrome</code>, and tools like Playwright MCP give agents browser control locally. The agent can navigate to a page, take a snapshot of the DOM, fill in a form, and verify the result. That&rsquo;s a feedback loop that catches an entire class of issues that unit tests and linters never will.</p>
<h3 id="the-smallest-change-that-moves-the-needle-3">The smallest change that moves the needle</h3>
<p>Add a linter to your CI pipeline. For a Ruby project, that&rsquo;s RuboCop. For JavaScript/TypeScript, ESLint. For Python, Ruff. One config file, one CI step. The agent immediately starts getting feedback on style and conventions that it wouldn&rsquo;t otherwise know about.</p>
<p>If you want faster feedback, add pre-commit hooks. The agent runs into the linter before it even pushes, which means it fixes issues in the same context window where it created them. That&rsquo;s cheaper, faster, and produces cleaner commits.</p>
<p>For web projects, consider adding browser access through Playwright MCP or a similar tool. The agent starts verifying its own UI changes instead of relying on you to catch visual issues in review.</p>
<h2 id="where-to-start">Where to Start</h2>
<p>If you&rsquo;re looking at your codebase and wondering where to start, here&rsquo;s how I think about prioritization:</p>
<ol>
<li><strong>Fix your test foundation first.</strong> Without reliable tests, every other improvement is hard to verify. An agent can&rsquo;t confidently refactor your architecture if there&rsquo;s no test suite to catch regressions.</li>
<li><strong>Add an AGENTS.md.</strong> This is 30 minutes of work that immediately changes agent behavior. It&rsquo;s the highest-ROI improvement you can make.</li>
<li><strong>Add a linter to CI.</strong> This closes the feedback gap with minimal effort. The agent starts learning your conventions from automated feedback instead of guessing from code patterns.</li>
</ol>
<p>These three changes don&rsquo;t require a major initiative. They&rsquo;re individual tasks that compound. A codebase with strong tests, clear documentation, and fast feedback loops creates a reinforcing cycle: agents produce better code, which maintains the patterns, which makes future agent output even better.</p>
<p>If you want to see where your codebase stands across all eight dimensions, run the <a href="/codebase-readiness/">Codebase Readiness Assessment</a>. It takes 60 seconds and gives you a score, a per-dimension breakdown, and a prioritized roadmap.</p>
<p>If your team wants hands-on help closing these gaps, that&rsquo;s what the <a href="/services/ai-enablement/">AI Workflow Enablement program</a> is built for. Or if you just want to talk through your results, <a href="/pages/meet/">book a free intro call</a>.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="/codebase-readiness/">Codebase Readiness Assessment</a> - Run the free assessment on your repo</li>
<li><a href="https://openai.com/index/harness-engineering/">Harness Engineering: Leveraging Codex in an Agent-First World</a> - OpenAI&rsquo;s deep dive on building a million-line codebase entirely with agents</li>
<li><a href="https://cursor.com/blog/agent-computer-use">Agent Computer Use</a> - How Cursor gives agents browser access to verify their own work</li>
<li><a href="/posts/2025-11-25-how-i-use-claude-code/">How I Use Claude Code: My Complete Development Workflow</a> - How codebase structure impacts agent output quality</li>
<li><a href="/posts/2026-02-05-mcps-vs-agent-skills/">MCPs vs Agent Skills</a> - Architecture decisions that shape how agents interact with your codebase</li>
</ul>
]]></content:encoded></item><item><title>How AI Agents Remember Things</title><link>https://www.damiangalarza.com/posts/2026-02-17-how-ai-agents-remember-things/</link><pubDate>Tue, 17 Feb 2026 00:00:00 -0500</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-02-17-how-ai-agents-remember-things/</guid><description>AI agents are stateless by default. Here's how memory systems actually work, covering the storage patterns, lifecycle triggers, and architecture behind agents that remember you.</description><content:encoded><![CDATA[<p>Out of the box, AI agents have no memory. Every conversation starts with a blank slate.</p>
<p>Most people assume you need vector databases, complex retrieval pipelines, or specialized memory infrastructure to fix this. But it turns out the storage is the easy part. The hard part is knowing when to write and when to load. Get that right, and the rest is just files.</p>
<div class="not-prose my-8 callout-accent">
  <p class="text-[var(--color-text-secondary)]">
    <span class="font-medium text-[var(--color-accent)]">Prefer video?</span> Watch <a href="https://youtu.be/Seu7nksZ_4k?si=Xx8wnlL6j8nsLYq5" class="text-[var(--color-accent)] underline hover:opacity-80">How AI Agents Remember Things on YouTube →</a>
  </p>
</div>
<p>I&rsquo;ll use OpenClaw as a case study here. Its memory model is one of the clearest real-world implementations I&rsquo;ve seen. But the patterns apply to any agent you build.</p>
<h2 id="why-agents-have-no-memory-by-default">Why Agents Have No Memory By Default</h2>
<p>AI models are inherently stateless. There&rsquo;s no memory between calls. What looks like a conversation is just an increasingly long context window being passed on each turn. Every message, every response, every tool call gets appended to the transcript and sent with the next request.</p>
<p>This works fine for a one-off question. It breaks down the moment you want an agent that knows you.</p>
<p>Memory systems handle this by splitting the problem in two: the session, and longer-term memory.</p>
<h3 id="sessions">Sessions</h3>
<p>A session is the history of a single conversation with an LLM. While the conversation is active, that history gets passed along with each call, and the model can see everything said so far. But LLMs have finite context windows, and as you approach that limit, something has to give.</p>
<p>That something is compaction. Compaction takes the session&rsquo;s conversation history and condenses it down to the most important information so the conversation can continue. There are three different strategies for triggering it:</p>
<ol>
<li><strong>Count-based</strong>: compact once the conversation exceeds a certain token size or turn count</li>
<li><strong>Time-based</strong>: triggered when the user stops interacting for a period of time, handled in the background</li>
<li><strong>Event-based</strong>: an agent detects that a task or topic has concluded and triggers compaction. The most intelligent approach, but also the hardest to implement accurately</li>
</ol>
<p>The shared problem with all three: you can&rsquo;t simply carry entire old conversations forward into a new session. Context windows don&rsquo;t allow it. That&rsquo;s where long-term memory comes in.</p>
<p>Think of it as a desk and a filing cabinet. The session is the messy desk, with notes scattered around and documents open. Memory is the filing cabinet where things are categorized and stored for later. When the session ends, whatever isn&rsquo;t filed is gone.</p>
<h2 id="the-memory-taxonomy">The Memory Taxonomy</h2>
<p>Google published a whitepaper in November 2025 titled <a href="https://www.kaggle.com/whitepaper-context-engineering-sessions-and-memory">&ldquo;Context Engineering: Sessions &amp; Memory&rdquo;</a> that provides a useful framework for thinking about this. It breaks agent memory into three types.</p>
<p><strong>Episodic memory</strong> covers events and interactions. &ldquo;What happened in our last conversation?&rdquo; If you spent a session debugging a webhook integration, episodic memory is what lets the agent recall that context in your next conversation.</p>
<p><strong>Semantic memory</strong> is facts and preferences. &ldquo;What do I know about this user?&rdquo; Tech stack, coding style, project conventions. These are stable facts that don&rsquo;t change much from session to session.</p>
<p><strong>Procedural memory</strong> is workflows and learned routines. &ldquo;How do I accomplish this task?&rdquo; The agent&rsquo;s understanding of your deployment process, your testing patterns, your PR review checklist.</p>
<p>All three work together to form what we&rsquo;d call an agent&rsquo;s memory. The challenge isn&rsquo;t categorizing them. It&rsquo;s extracting them from conversation and keeping them accurate over time.</p>
<h2 id="extraction-and-consolidation">Extraction and Consolidation</h2>
<p>In order for a memory system to be effective, it needs to extract the right things from a conversation. Not every detail is worth keeping. Targeted filtering is necessary, the same way human memory doesn&rsquo;t retain every word of a conversation. It retains key facts and decisions.</p>
<p>Beyond that, the system needs to consolidate. Consider a user who tells an agent &ldquo;I prefer dark mode&rdquo; in one session, then later says &ldquo;I like dark mode,&rdquo; and in another session mentions &ldquo;I switched to dark mode.&rdquo; Without consolidation, all three entries sit in memory saying essentially the same thing. A good memory system collapses those into a single entry: &ldquo;User prefers dark mode.&rdquo;</p>
<p>It also needs to handle updates. Something true today might not be true tomorrow. If you switch from dark mode to light mode, the memory system needs to overwrite the old entry, not append a contradictory one. Without this, memory becomes noisy and unreliable over time.</p>
<p>Both extraction and consolidation are typically handled by a separate LLM instance that takes a conversation and processes it, deciding what to keep, what to merge, and what to update.</p>
<h2 id="memory-storage">Memory Storage</h2>
<p>Storage itself is relatively straightforward. For local agents, markdown files work well. They&rsquo;re readable, debuggable, and require no infrastructure. For agents that need semantic search across a large history, a vector database is the right tool. The choice depends on the use case.</p>
<p>What matters more than the storage format is the shape of what you store: semantic memory for stable facts, episodic memory for events and recent context, and procedural memory for workflows.</p>
<h2 id="openclaws-memory-model">OpenClaw&rsquo;s Memory Model</h2>
<p>Let me walk through how one system actually implements this.</p>
<p>OpenClaw&rsquo;s memory system has three core components, and all of them are just markdown files.</p>
<p><strong>MEMORY.md</strong> is the semantic memory store. Stable facts, user preferences, identity information. It has a recommended 200-line cap and is organized into structured sections. The key design decision: this file is loaded into every single prompt, not retrieved on demand. The agent starts every conversation already knowing who you are.</p>
<p><strong>Daily logs</strong> are OpenClaw&rsquo;s first implementation of episodic memory. They live at <code>~/.openclaw/workspace/memory/YYYY-MM-DD.md</code> and contain recent context organized by day. They&rsquo;re append-only; new entries get added, nothing is removed. Today&rsquo;s and yesterday&rsquo;s logs are loaded at the start of each session.</p>
<p><strong>Session snapshots</strong> are the second implementation of episodic memory. When you start a new session with <code>/new</code> or <code>/reset</code>, a hook captures the last 15 meaningful messages from your conversation, filtering out tool calls, system messages, and slash commands. It&rsquo;s not a summary; it&rsquo;s the raw conversation text, saved as a markdown file with a descriptive name like <code>~/.openclaw/workspace/memory/2026-02-08-api-design.md</code>.</p>
<p>So at its core, OpenClaw&rsquo;s memory is markdown files. But the files are only half the story. Without something that reads and writes them at the right time, they&rsquo;re just sitting there doing nothing.</p>
<p>The files are the filing cabinet. What comes next are the four mechanisms that move things from the desk to the cabinet at the right moments.</p>
<h2 id="how-it-all-comes-together">How It All Comes Together</h2>
<p><strong>Mechanism 1: Bootstrap loading at session start.</strong></p>
<p>For every new conversation, MEMORY.md is automatically injected into the prompt. The agent always has it. On top of that, the agent&rsquo;s instructions tell it to read today&rsquo;s and yesterday&rsquo;s daily logs for recent context. MEMORY.md is injected by the system; the daily logs are loaded by the agent itself, following its own instructions.</p>
<p>This is the simplest pattern and the most important one. The agent doesn&rsquo;t have to search for context. It&rsquo;s just there.</p>
<p><strong>Mechanism 2: Pre-compaction flush.</strong></p>
<p>OpenClaw takes a count-based approach to compaction. When a session nears the context window limit, OpenClaw injects a silent agentic turn (invisible to the user) with the following instructions:</p>
<blockquote>
<p>&ldquo;Pre-compaction memory flush. Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed). If nothing to store, reply with NO_REPLY.&rdquo;</p></blockquote>
<p>When the agent sees this, it writes anything worth keeping to the daily log, then replies with <code>NO_REPLY</code> so it never surfaces in the conversation.</p>
<p>This turns a destructive operation into a checkpoint. Losing context becomes a save point rather than a loss. It&rsquo;s the write-ahead log pattern: save before you lose, load when you start. The same pattern databases have used for decades, applied to agent memory.</p>
<p><strong>Mechanism 3: Session snapshot on <code>/new</code>.</strong></p>
<p>When you explicitly start a new session, a hook grabs the last chunk of your conversation, filters to meaningful messages only, and saves it with a descriptive filename. It only fires on explicit <code>/new</code> or <code>/reset</code>; closing the browser doesn&rsquo;t trigger it. It&rsquo;s an intentional save point, not an automatic backup.</p>
<p><strong>Mechanism 4: User says &ldquo;remember this.&rdquo;</strong></p>
<p>The simplest mechanism. If you ask the agent to remember something, it determines whether it belongs in MEMORY.md as semantic memory or the daily log as episodic memory, and writes accordingly. No special hook needed, just file-writing capabilities and instructions for how to categorize.</p>
<h2 id="why-this-matters-beyond-openclaw">Why This Matters Beyond OpenClaw</h2>
<p>Claude Code recently shipped a native memory feature. It also uses markdown files. The pattern is becoming standard.</p>
<p>The agents that feel most useful, the ones that stick as part of your workflow, are the ones that remember you. An agent that asks your tech stack every session doesn&rsquo;t feel like a colleague. An agent that already knows your conventions and what you worked on yesterday does.</p>
<p>The building blocks are the same regardless of what you&rsquo;re building on: file-first storage, lifecycle triggers tied to meaningful session events, and extraction and consolidation to keep memory clean over time.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>OpenClaw&rsquo;s entire memory system comes down to markdown files and knowing when to write to them. Semantic memory in MEMORY.md. Episodic memory in daily logs and session snapshots. And four mechanisms that fire at the right moments in a conversation&rsquo;s lifecycle.</p>
<p>You don&rsquo;t need a complex setup to give an agent memory. You need a clear answer to three questions: what&rsquo;s worth remembering, where does it go, and when does it get written.</p>
<hr>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://youtu.be/Seu7nksZ_4k?si=Xx8wnlL6j8nsLYq5">How AI Agents Remember Things</a>: The video companion to this post</li>
<li><a href="https://www.kaggle.com/whitepaper-context-engineering-sessions-and-memory">Context Engineering: Sessions and Memory</a>: Google&rsquo;s whitepaper on agent memory taxonomy</li>
<li><a href="/posts/2025-12-08-understanding-claude-code-context-window/">Understanding Claude Code&rsquo;s Context Window</a>: How context windows work and how to manage them</li>
<li><a href="/posts/2025-11-25-how-i-use-claude-code/">How I Use Claude Code: My Complete Development Workflow</a>: Practical patterns for AI-assisted development</li>
</ul>
<hr>
<p>Want to go deeper on agent memory and context architecture? I work with engineers and teams on designing agent systems that actually hold up. <a href="/ai-agents/">Learn more</a>.</p>
]]></content:encoded></item></channel></rss>