<?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: Harrison Guo</title>
    <description>The latest articles on DEV Community by Harrison Guo (@harrisonsec).</description>
    <link>https://dev.to/harrisonsec</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%2F3809272%2F593698c5-7201-4bb0-898e-055cdbc0a2d2.png</url>
      <title>DEV Community: Harrison Guo</title>
      <link>https://dev.to/harrisonsec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harrisonsec"/>
    <language>en</language>
    <item>
      <title>Tool Calls Need Completion Ownership: Why Your Agent's 200 OK Lies</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Thu, 13 Aug 2026 16:45:35 +0000</pubDate>
      <link>https://dev.to/harrisonsec/tool-calls-need-completion-ownership-why-your-agents-200-ok-lies-na7</link>
      <guid>https://dev.to/harrisonsec/tool-calls-need-completion-ownership-why-your-agents-200-ok-lies-na7</guid>
      <description>&lt;p&gt;An agent is asked to send a payment confirmation email. It calls the &lt;code&gt;send_email&lt;/code&gt; tool. The tool returns &lt;code&gt;{"status": "queued", "message_id": "msg_abc123"}&lt;/code&gt;. The agent writes back to the user: &lt;em&gt;Done. Confirmation email sent.&lt;/em&gt; Two hours later a support ticket arrives. The customer never got the email. The provider's queue had dropped the message during a regional failover, and no one on the path between the agent and the recipient's inbox ever held ownership of the claim &lt;em&gt;this email reached the customer.&lt;/em&gt; Every layer received a token, forwarded it, and declared its own part finished.&lt;/p&gt;

&lt;p&gt;The instinct in the incident review is to blame the model. It reported something false with total confidence, so the model must have hallucinated. It did not. The model read a success signal and faithfully relayed it. The signal was a lie before the model ever saw it, and it was a lie for a reason that has nothing to do with language models.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A tool call's 200 means the request was accepted, not that the effect happened. Put completion ownership in the wrapper: verify the side effect before the word success ever reaches the model. The model cannot infer completion from a status code, and it will report exactly the confidence the token implies.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sits in the same first-principles line as &lt;a href="https://harrisonsec.com/blog/validation-is-a-loop-not-an-assertion/" rel="noopener noreferrer"&gt;validation is a loop, not an assertion&lt;/a&gt; and &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;determinism where you can, judgement where you must&lt;/a&gt;. Validation-is-a-loop argues that in a generative system you have to check the agent's work rather than assume it. This piece is about a narrower and sneakier place the check goes missing: not the model's reasoning, but the tool boundary underneath it, where a status code gets mistaken for a fact. It is also the direct descendant of an old distributed-systems question I wrote about in &lt;a href="https://harrisonsec.com/blog/rpc-vs-nats-who-owns-completion/" rel="noopener noreferrer"&gt;RPC vs NATS, who owns completion&lt;/a&gt;. The agent stack rediscovered that question and, for the most part, got it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 200 that means accepted, not done
&lt;/h2&gt;

&lt;p&gt;Trace the token upward through a typical stack and you find three separate places where an honest but incomplete signal gets promoted into a false one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool API layer.&lt;/strong&gt; The tool returns 200 because the request was accepted, not because the effect was observed. Email providers return 200 on enqueue. Async job endpoints return 202 with a job id. Eventual-consistency writes return 200 the moment the write is durable on one node, before it has propagated anywhere a subsequent read would find it. In every one of these, 200 is a true statement about &lt;em&gt;receipt&lt;/em&gt; and says nothing about &lt;em&gt;outcome.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent framework layer.&lt;/strong&gt; The framework sees a 2xx, marks the tool call as succeeded, and appends a &lt;code&gt;success&lt;/code&gt; entry to the conversation. This is the layer that does the real damage, because it collapses two genuinely different states, request accepted and effect observed, into one word. The framework had the status code and threw away everything about what it meant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model layer.&lt;/strong&gt; The model reads &lt;code&gt;success&lt;/code&gt; in the tool result and generates &lt;em&gt;Done. Email sent,&lt;/em&gt; with high confidence. The confidence is not a defect. It is the correct response to the token it was given. Ask a person to relay a message stamped SUCCESS and they will relay it as success too. The model is the last honest link in a chain that lied to it three steps earlier.&lt;/p&gt;

&lt;p&gt;The customer, at the end of all this, gets a confident report that contradicts observable reality. And the postmortem points at the one component that behaved correctly given its inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where completion ownership went
&lt;/h2&gt;

&lt;p&gt;The underlying problem is older than agents, older than HTTP. Any time an operation crosses a boundary, someone has to own the answer to &lt;em&gt;did it actually happen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;RPC's classic wound is the caller who gets a network error partway through a call. Did the operation run or not? A network error is not a no. It is an &lt;em&gt;unknown.&lt;/em&gt; The operation may have committed on the server and had its acknowledgement lost on the way back. Without an idempotency key to retry safely and a read-after-write check to confirm the effect, the caller genuinely cannot tell, and any recovery it attempts is a guess.&lt;/p&gt;

&lt;p&gt;The way out, in every reliable system, is to name an owner. Either the caller polls until it sees the effect with its own eyes, or the receiver commits and only then reports back. As I argued in the &lt;a href="https://harrisonsec.com/blog/rpc-vs-nats-who-owns-completion/" rel="noopener noreferrer"&gt;RPC vs NATS piece&lt;/a&gt;, fire-and-forget messaging pushes ownership onto the caller, and request-response pushes it onto the receiver. Neither placement is wrong. What is always wrong is &lt;em&gt;ambiguity&lt;/em&gt; about which layer holds it, because ambiguity means the answer is nobody, and nobody is exactly what produces the phantom confirmation email.&lt;/p&gt;

&lt;p&gt;AI tool calls inherited this problem and, in the rush to wire models to real actions, mostly skipped the part where you decide who owns completion. The wrapper trusts the API's 200. The API trusts its dispatcher. The dispatcher trusts the worker. The worker logs success on enqueue. The token travels all the way up to the model and out to the user, and at no point did any layer commit to having observed the effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declare the mode in the wrapper
&lt;/h2&gt;

&lt;p&gt;The fix starts by refusing to let a status code stand in for an outcome. Every tool wrapper has to declare which of three modes it operates in, and that declaration lives in the wrapper, not in the model's head.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Returns when&lt;/th&gt;
&lt;th&gt;Caller responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;committed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The effect has been observed&lt;/td&gt;
&lt;td&gt;May report success directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accepted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The request was enqueued, effect not yet observed&lt;/td&gt;
&lt;td&gt;Must verify the effect before reporting success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optimistic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best-effort send, no guarantee available&lt;/td&gt;
&lt;td&gt;Must surface uncertainty to the user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a wrapper-level discipline for a concrete reason: the model cannot derive the mode from a 200. Two tools can return byte-identical success payloads while one has durably applied its effect and the other has merely queued it. The distinction exists only in knowledge the wrapper has and the status code does not carry. If the wrapper does not encode the mode, the information is gone by the time the model sees the result, and no amount of prompting recovers it. This is the &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;technique boundary&lt;/a&gt; in miniature: the deterministic layer knows something the model cannot infer, so the deterministic layer has to state it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The closed loop for accepted-mode tools
&lt;/h2&gt;

&lt;p&gt;For anything in &lt;code&gt;accepted&lt;/code&gt; mode, success is not a value the tool returns. It is a state a verifier confirms. The wrapper returns a handle, and a verification step stands between the tool and the model's next turn.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The wrapper returns &lt;code&gt;{"status": "accepted", "polling_token": "..."}&lt;/code&gt;. It does not return &lt;code&gt;success&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A verifier step polls the token until it reaches a terminal state, delivered or failed, reading the actual effect rather than re-reading the queue that accepted it.&lt;/li&gt;
&lt;li&gt;Only the terminal state is surfaced to the model.&lt;/li&gt;
&lt;li&gt;If polling exhausts its budget, the wrapper surfaces &lt;code&gt;uncertain&lt;/code&gt;, the operation may or may not have completed, and explicitly not &lt;code&gt;success&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That verifier is middleware sitting between the tool and the next model turn, the same shape as the loop in &lt;a href="https://harrisonsec.com/blog/validation-is-a-loop-not-an-assertion/" rel="noopener noreferrer"&gt;validation is a loop, not an assertion&lt;/a&gt;: the agent acts, the system verifies, scores the result, and routes on it. And the verification has to observe the effect itself, not a proxy for it. Re-reading the queue that already said &lt;em&gt;accepted&lt;/em&gt; will happily confirm &lt;em&gt;accepted&lt;/em&gt; forever. That is a &lt;a href="https://harrisonsec.com/blog/a-wrong-ruler-is-worse-than-no-ruler/" rel="noopener noreferrer"&gt;wrong ruler&lt;/a&gt;: a check that looks like verification but measures the wrong thing, which is worse than no check because it manufactures false confidence. A read-after-write worthy of the name reads the recipient's mailbox state, the inserted row, the written file, not the acknowledgement that a request to produce them was received.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent action
   |
   v
Tool wrapper
   |
   |-- committed --&amp;gt; Effect observed --------------&amp;gt; Model context: "success"
   |
   |-- accepted --&amp;gt; Verifier (poll the real effect)
                        |-- terminal: delivered ---&amp;gt; Model context: "success"
                        |-- terminal: failed ------&amp;gt; Model context: "failed"
                        |-- budget exhausted ------&amp;gt; Model context: "uncertain"
                        \-- not yet terminal ------&amp;gt; keep polling (loop back)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every path in that graph ends at an honest token. The only way the model reports success is if some layer below it actually watched the effect land.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five questions for every tool in the box
&lt;/h2&gt;

&lt;p&gt;This is ordinary reliability hygiene, applied one tool at a time. For each tool your agent can call, you should be able to answer yes to all five:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the return value distinguish &lt;em&gt;queued&lt;/em&gt; from &lt;em&gt;applied&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;If the tool is asynchronous, is there a polling token or a callback to confirm the terminal state?&lt;/li&gt;
&lt;li&gt;Does the wrapper enforce verification before &lt;code&gt;success&lt;/code&gt; can reach the model?&lt;/li&gt;
&lt;li&gt;On verification timeout, does the wrapper surface &lt;em&gt;uncertain&lt;/em&gt;, rather than defaulting to &lt;em&gt;success&lt;/em&gt; or &lt;em&gt;failure&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Is there a retry budget owned in one place, so a burst of retries cannot compound the ambiguity?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is where this connects to cost as well as correctness. Uncoordinated retries stacked at three layers are exactly the &lt;a href="https://harrisonsec.com/blog/your-ai-bill-is-a-distributed-systems-problem/" rel="noopener noreferrer"&gt;retry storm&lt;/a&gt; that inflates an AI bill, and they are also a completion-ownership failure: three layers each hoping the operation happened, none of them owning the answer. One retry budget in one place fixes both faces of the same bug.&lt;/p&gt;

&lt;p&gt;Any tool that fails one of these five carries completion-ownership debt, and that debt is paid in user-visible incorrectness, the agent asserting things that did not happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is 2PC versus eventual consistency, wearing a tool schema
&lt;/h2&gt;

&lt;p&gt;For anyone who has built distributed systems, the whole thing has a familiar shape. A two-phase commit says &lt;em&gt;I hold ownership and will not report done until the effect is durable.&lt;/em&gt; Eventual consistency says &lt;em&gt;I forward my part and trust the next layer to converge.&lt;/em&gt; Both are legitimate designs. The trouble is that most agent tool stacks are built like eventual-consistency systems but &lt;em&gt;report&lt;/em&gt; like two-phase commits: they emit a crisp, immediate &lt;em&gt;success&lt;/em&gt; for an effect that is still only propagating, or still only queued, or already quietly dropped.&lt;/p&gt;

&lt;p&gt;The fix is not to force every tool into two-phase commit. Plenty of effects are genuinely eventual, and that is fine. The fix is honesty in the report. If the system is eventual, the token that reaches the model has to say so, &lt;code&gt;accepted&lt;/code&gt; and then a verified terminal state, never a premature &lt;code&gt;success&lt;/code&gt;. If you have shipped idempotency keys and read-after-write checks before, you already have every piece of this. The polling token is a request UUID with a different label, and the verifier is a read-after-write check you already know how to write.&lt;/p&gt;

&lt;p&gt;The bill for skipping it does not arrive as a stack trace. It arrives as a support ticket, hours later, from a customer the agent told with total confidence that something was done. Read the completion one layer earlier, in the wrapper, where you can still tell &lt;em&gt;accepted&lt;/em&gt; from &lt;em&gt;observed,&lt;/em&gt; and the model stops lying, because for the first time nothing below it is lying to the model.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This piece sits in the Generative Systems, First Principles line. Its correctness siblings: &lt;a href="https://harrisonsec.com/blog/validation-is-a-loop-not-an-assertion/" rel="noopener noreferrer"&gt;Validation Is a Loop, Not an Assertion&lt;/a&gt;, &lt;a href="https://harrisonsec.com/blog/a-wrong-ruler-is-worse-than-no-ruler/" rel="noopener noreferrer"&gt;A Wrong Ruler Is Worse Than No Ruler&lt;/a&gt;, and &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;Determinism Where You Can, Judgement Where You Must&lt;/a&gt;. The distributed-systems ancestor: &lt;a href="https://harrisonsec.com/blog/rpc-vs-nats-who-owns-completion/" rel="noopener noreferrer"&gt;RPC vs NATS, Who Owns Completion&lt;/a&gt;. The cost companion: &lt;a href="https://harrisonsec.com/blog/your-ai-bill-is-a-distributed-systems-problem/" rel="noopener noreferrer"&gt;Your AI Bill Is a Distributed Systems Problem&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://harrisonsec.com/blog/tool-calls-need-completion-ownership/" rel="noopener noreferrer"&gt;harrisonsec.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your AI Bill Is a Distributed Systems Problem, Not a Model-Pricing Problem</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:01:31 +0000</pubDate>
      <link>https://dev.to/harrisonsec/your-ai-bill-is-a-distributed-systems-problem-not-a-model-pricing-problem-2h82</link>
      <guid>https://dev.to/harrisonsec/your-ai-bill-is-a-distributed-systems-problem-not-a-model-pricing-problem-2h82</guid>
      <description>&lt;p&gt;A team I was helping watched their model bill jump to several times its usual size in a single month. The token meter had not predicted it. The first question in the room was the one almost everyone asks: &lt;em&gt;did the provider raise prices, or should we move to a cheaper model?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both were the wrong question, and they were wrong in an instructive way. The same engineers, if their AWS egress bill had tripled, would never have opened with &lt;em&gt;did AWS raise prices.&lt;/em&gt; They would have asked &lt;em&gt;what is calling more,&lt;/em&gt; pulled up the request graph, and started looking for the amplification. The instinct is correct and automatic when the unit is HTTP calls. It deserts people the moment the unit becomes tokens.&lt;/p&gt;

&lt;p&gt;That gap is the whole subject of this piece.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A surprising AI bill is a symptom. The disease is almost always a distributed systems bug, a retry storm, a hidden fanout, a cache that stopped hitting, a conversation that grows without bound, that you would recognize instantly if it were denominated in network calls instead of tokens. Debug the call graph, not the model price.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sits in the same first-principles line as &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;determinism where you can, judgement where you must&lt;/a&gt; and &lt;a href="https://harrisonsec.com/blog/validation-is-a-loop-not-an-assertion/" rel="noopener noreferrer"&gt;validation is a loop, not an assertion&lt;/a&gt;. Where those are about correctness, this one is about cost, and the move is the same: take a problem that looks new because it wears an AI costume, and recognize the old shape underneath. On the architecture side it pairs with &lt;a href="https://harrisonsec.com/blog/observability-billing-t-architecture-ai-api-calls/" rel="noopener noreferrer"&gt;Observability and Billing for AI API Calls&lt;/a&gt;, which is about how to build the telemetry. This piece is about how to read it when the number jumps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The diagnostic flip
&lt;/h2&gt;

&lt;p&gt;When a backend engineer's egress bill spikes, the reflex is to ask what changed in the call graph. Nobody's first move is to email the vendor about pricing. When an AI engineer's model bill spikes, the reflex too often inverts: the first suspects are the price sheet and the model choice, and the call graph is the last thing anyone looks at.&lt;/p&gt;

&lt;p&gt;These are the same question wearing different clothes. &lt;em&gt;What is driving the spend, and is the driver behaving the way it should?&lt;/em&gt; We get it right for HTTP because we have years of muscle memory in that unit. We get it wrong for tokens because the unit is unfamiliar and, crucially, because the bill arrives pre-aggregated into a single dollar figure that hides the graph that produced it. The dollar figure is the least useful view of the problem, and it is usually the only view people start from.&lt;/p&gt;

&lt;p&gt;So before anything else, refuse the pricing question. Assume the spend is a downstream-service spend that misbehaved, and go find the misbehavior. It will almost always be one of five shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five failure modes, denominated in tokens
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Retry storms.&lt;/strong&gt; A transient provider error triggers a retry at the SDK layer, and again at the agent layer, and again at the workflow orchestrator, because all three were written to be resilient and none of them knew about the others. One user request becomes several billed calls. This is the exact retry storm you have debugged on a degraded downstream HTTP service, where nested retry policies multiply instead of coordinate. The fix is the same: one retry budget, owned in one place, not three hopeful ones stacked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fanout amplification.&lt;/strong&gt; An agent spawns several parallel tool calls, and each tool, somewhere inside its implementation, makes its own model call to parse or summarize its result, and each of those results feeds back into the main conversation. One user request quietly becomes many model calls, and most of them are hidden inside tool code where no one thinks to look. This is request fanout amplification, the same reason a single page load can trigger forty backend calls when each component fetches for itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cache misses where caches should hit.&lt;/strong&gt; The provider supports caching a stable prompt prefix, and you are not using it, so the same system prompt and the same long preamble get retokenized and rebilled on every single call. This is a CDN accidentally configured to pass every request through to origin. The content never changed, and you paid to recompute it anyway, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Unbounded conversation growth.&lt;/strong&gt; Conversation history is appended turn after turn with no compaction, so the tokens billed per turn grow with the length of the session, and the total cost of a session grows quadratically with its length. This is an unbounded buffer, the same class of leak that &lt;a href="https://harrisonsec.com/blog/channels-arent-message-passing/" rel="noopener noreferrer"&gt;treating a channel like message passing&lt;/a&gt; produces, except the buffer is a transcript and the leak shows up on an invoice instead of in a heap profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Wrong model for the job.&lt;/strong&gt; A frontier model is doing work a smaller model handles perfectly well, on every call, by default. This is running your ad-hoc analytical queries against the transactional cluster because it was the connection string you had, instead of against the warehouse built for them. It is also, in the language of the &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;technique boundary&lt;/a&gt;, using the most expensive technique where a cheaper one was sufficient, which is the cost face of the same mistake that piece describes for correctness.&lt;/p&gt;

&lt;p&gt;Four of these five are amplification bugs: something is producing more calls, or more tokens per call, than the work requires. The fifth is a tiering mistake. None of them is a price change, and none of them is fixed by switching models, which is exactly why switching models first tends to disappoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forensics, in order
&lt;/h2&gt;

&lt;p&gt;The diagnosis runs in the same order every time, and each step reads a distributed systems metric off the call graph, not a number off the token meter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Calls per user request
   near your workflow's theoretical minimum? -&amp;gt; healthy
   several times the minimum?                -&amp;gt; retry storm or hidden fanout (modes 1, 2)

2. Tokens-per-call distribution
   stable week over week?                    -&amp;gt; healthy
   the tail is climbing?                     -&amp;gt; unbounded conversation or uncached prefix (modes 3, 4)

3. Model mix over time
   right tier per task type?                 -&amp;gt; healthy
   a routing change shifted traffic up?      -&amp;gt; wrong model / tiering (mode 5)

4. Provider reject rate
   negligible?                               -&amp;gt; healthy
   climbing?                                 -&amp;gt; the trigger that sets off mode 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters. Provider rejects sit at the bottom of the list but at the root of the causal chain, because a rise in transient rejects is what ignites a retry storm at the top of it. Read the graph top to bottom to localize the symptom, then bottom to top to find the cause.&lt;/p&gt;

&lt;p&gt;The point of laying it out this way is that none of it is novel. Your observability stack already knows how to compute calls per request, tail distributions, traffic mix, and error rates. You compute them for every other downstream service you depend on. The only new thing is pointing that same machinery at the model provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  User request
       |
       v
     Agent  --fanout xN--&amp;gt;  Tool calls (each hides a model call)
       |                         |
       | retries xR              | retries xR
       v                         v
              Provider  (cache layer)
                     |
                     v
                 The bill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every multiplier on an edge in that graph is a place the bill can balloon while the price per token never moves. The invoice only shows you the node at the far right. The leak is always on an edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Billing is not cost forensics
&lt;/h2&gt;

&lt;p&gt;Here is the line that decides whether you can do any of the above. If your AI observability emits tokens multiplied by price for each call and stops there, you have billing. You know what you paid. You cannot see why, because the why lives in the edges of the call graph, and a per-call token count has thrown the graph away.&lt;/p&gt;

&lt;p&gt;Cost forensics needs the graph kept intact: calls attributed back to the originating user request, fanout factors per tool, cache hit rates per prompt prefix, retry distributions per layer, model mix over time. That is the same telemetry shape you already maintain for HTTP dependencies, and building it deliberately is the subject of the &lt;a href="https://harrisonsec.com/blog/observability-billing-t-architecture-ai-api-calls/" rel="noopener noreferrer"&gt;T-shaped architecture piece&lt;/a&gt;. The relevant point here is diagnostic: if that data does not exist, no one, however senior, can tell you where your bill is leaking, because the evidence was discarded at collection time. Add the graph, and an engineer who has never touched an LLM but is good at HTTP cost forensics can find the leak, because a token is just a byte with cognitive content, and a model provider is just a downstream service that bills by the payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a model switch is actually the answer
&lt;/h2&gt;

&lt;p&gt;Not never. After you have ruled out retries, fanout, cache misses, and conversation growth, mode five is a real and common diagnosis, and moving a class of calls to a smaller model is the right fix. The discipline is only about order. Switch models before ruling out the amplification modes and you can land on a cheaper model that still has a retry storm sitting on top of it, which presents as the smaller model being mysteriously expensive too, and now you have two confusing bills instead of one. Rule out the graph problems first. Then a model switch is a measurement-backed decision rather than a hopeful guess, and it tends to hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old hygiene, new invoice
&lt;/h2&gt;

&lt;p&gt;If your team has someone with good instincts for HTTP retry budgets, request fanout, cache hit ratios, and payload-size distributions, that person can already debug your model bill. Everything transfers. Retry budgets are retry budgets. Fanout is fanout. An uncached prefix is an uncached prefix. The provider is one more downstream service that responds to the same hygiene you already apply to object storage, managed databases, and third-party APIs.&lt;/p&gt;

&lt;p&gt;The bill is the last place the bug appears and the most expensive place to read it, because by the time it shows up there it has already been paid. Read it one step earlier, in the call graph, where every one of these five modes is visible as a multiplier on an edge, and where you have known how to fix each of them for years. Your AI bill is not a new kind of problem. It is an old kind of problem with an unfamiliar unit, and the moment you convert the unit back, you already know what to do.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This piece sits in the Generative Systems, First Principles line. The correctness companions: &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;Determinism Where You Can, Judgement Where You Must&lt;/a&gt; and &lt;a href="https://harrisonsec.com/blog/validation-is-a-loop-not-an-assertion/" rel="noopener noreferrer"&gt;Validation Is a Loop, Not an Assertion&lt;/a&gt;. The architecture companion: &lt;a href="https://harrisonsec.com/blog/observability-billing-t-architecture-ai-api-calls/" rel="noopener noreferrer"&gt;Observability and Billing for AI API Calls&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Validation Is a Loop, Not an Assertion: Why Your AI Agent Reports Success It Never Achieved</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Tue, 28 Jul 2026 16:27:16 +0000</pubDate>
      <link>https://dev.to/harrisonsec/validation-is-a-loop-not-an-assertion-why-your-ai-agent-reports-success-it-never-achieved-1d32</link>
      <guid>https://dev.to/harrisonsec/validation-is-a-loop-not-an-assertion-why-your-ai-agent-reports-success-it-never-achieved-1d32</guid>
      <description>&lt;p&gt;An agent I was reviewing was asked to rename a function across a repository. It ran for a while, made its edits, and returned a clean summary: &lt;em&gt;Done. Renamed &lt;code&gt;parseUser&lt;/code&gt; to &lt;code&gt;parseAccount&lt;/code&gt; everywhere.&lt;/em&gt; The summary was confident, well formatted, and wrong. Six of eight call sites were updated. Two were not, one buried in a string that built the name dynamically and one in a test helper the search had skipped.&lt;/p&gt;

&lt;p&gt;The agent did not lie. It returned what its model believed was true at the moment it returned. The problem is that nothing sat between &lt;em&gt;I believe I succeeded&lt;/em&gt; and &lt;em&gt;I report success&lt;/em&gt;. There was no step that walked back out into the world, looked at what had actually changed, and compared it to what the task asked for. The agent's confidence and the agent's correctness were two completely unrelated numbers, and only one of them made it into the summary.&lt;/p&gt;

&lt;p&gt;This is the single most common way I see production agents fail, and it is almost never a model problem. It is a validation problem, and specifically it is validation built in the wrong shape.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In a deterministic system, validation is an assertion: check once, pass or fail. In a system driven by non-deterministic model output, validation has to be a loop: observe the real outcome, score how close it is, and decide whether to accept, retry, route, or stop. The most common production agent failure is treating the second case like the first.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This piece sits alongside two companions. &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;Determinism where you can, judgement where you must&lt;/a&gt; is about deciding which technique owns each part of a system. &lt;a href="https://harrisonsec.com/blog/a-wrong-ruler-is-worse-than-no-ruler/" rel="noopener noreferrer"&gt;A wrong ruler is worse than no ruler&lt;/a&gt; is about verifying the checks you trust before you let them hold authority. This piece is about the shape of the check itself, once you have decided the output is non-deterministic enough to need one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an assertion assumes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;assert(result == expected)&lt;/code&gt; is one of the most useful lines in deterministic software, and it is useful because of a set of assumptions that are almost always true in that world. The operation you just ran is deterministic, so it either did the thing or it did not. The check is far cheaper than the operation, so running it once at the end costs nothing worth counting. And the answer is binary, because in a deterministic system there is no meaningful state between success and failure. You parsed the integer or you threw. You wrote the row or you got an error.&lt;/p&gt;

&lt;p&gt;Every one of those assumptions breaks the moment the operation is a model call.&lt;/p&gt;

&lt;p&gt;The operation is no longer deterministic, so &lt;em&gt;it did the thing&lt;/em&gt; is now a probability, not a fact. The check is no longer trivially cheaper than the operation, because verifying whether a model actually accomplished a fuzzy goal can be almost as hard as the goal. And the answer is no longer binary, because the whole texture of model failure is partial. The rename that got six of eight. The summary that captured four of the five key points. The extraction that found every date except the one written in words. Partial success is not an edge case in these systems. It is the main case, and an assertion has no vocabulary for it. &lt;code&gt;6 of 8&lt;/code&gt; and &lt;code&gt;8 of 8&lt;/code&gt; collapse to the same &lt;code&gt;false&lt;/code&gt;, which throws away exactly the information you needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the loop
&lt;/h2&gt;

&lt;p&gt;A loop keeps the information an assertion discards. It does not ask &lt;em&gt;did it pass.&lt;/em&gt; It asks &lt;em&gt;how close did it get, and what should happen because of that.&lt;/em&gt; Concretely it is four moves and a stopping rule.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validate by observation.&lt;/strong&gt; After the action, look at the real, observable outcome, not the model's report of it. For the rename, that means searching the codebase for the old and new names and seeing which call sites actually changed. The validator's input is the world, not the transcript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score with partial credit.&lt;/strong&gt; Turn the observation into a number or a structured verdict that can express &lt;em&gt;most of the way there.&lt;/em&gt; Six of eight is &lt;code&gt;0.75&lt;/code&gt;, and that number carries the two misses that a boolean would have erased.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide against thresholds.&lt;/strong&gt; Is the score high enough to call the task done? Low enough to abandon and report failure honestly? Or in the band between, where more work might close the gap?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route the in-between case.&lt;/strong&gt; When the score is neither clearly done nor clearly hopeless, choose the next action deliberately. Retry only the two missed call sites. Ask the user whether the dynamic-string case should even be touched. Escalate to a stronger model. The router is where the loop earns its keep, because it is the part an assertion cannot do at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate to a terminal condition.&lt;/strong&gt; Repeat until one of three things is true: the score clears the accept threshold, a retry budget is exhausted, or a human steps in. The budget matters as much as the score. A loop with no budget does not converge, it hangs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drawn out, the control flow is small but it is a cycle, and the cycle is the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        +---------------- route: retry / clarify / escalate ----------------+
        |                                                                   |
        v                                                                   |
  Agent action --&amp;gt; Validate --&amp;gt; Score --&amp;gt; Decide --+-- clears accept  --&amp;gt; Report success
                  (observe)   (partial)            |
                                                   +-- below abandon  --&amp;gt; Halt, surface partial progress
                                                   |
                                                   +-- in between ------&amp;gt; Route ----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice where control lives. The model reasons inside this loop and it can even do the scoring when the judgement is subjective. But it does not own the loop. The decision of what counts as done, and what happens when the task is not done, stays with the orchestrator. This is the same boundary the &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;technique piece&lt;/a&gt; draws: the model may reason under the control, it may not direct the control itself. An agent that gets to declare its own task finished and exit on its own authority has no loop. It has an assertion it wrote for itself and then graded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring function is the hard half
&lt;/h2&gt;

&lt;p&gt;If you have designed retry-with-backoff for a flaky network call, you have already built most of this loop. Observe, decide, retry with a budget, give up gracefully. That machinery ports over almost unchanged. The part that does not port, the part that makes this genuinely a production AI problem rather than a distributed-systems problem you already solved, is the scoring function.&lt;/p&gt;

&lt;p&gt;For the rename, scoring is easy, because the outcome is observable by deterministic means. You can enumerate call sites and count. Whenever you can score by observation like that, do, because a count does not hallucinate. The hard cases are the ones where the goal is subjective: is this summary faithful, is this explanation coherent, does this answer satisfy the brief. There the score has to come from judgement, often a model judging another model's output, and now you have a new problem stacked on the first one.&lt;/p&gt;

&lt;p&gt;That scorer is itself a check, and a check that can be wrong. If you let a broken scorer drive the loop, you do not merely fail to catch errors. You manufacture confident wrong verdicts and feed them straight back into the agent's next action. A loop built on a bad ruler is worse than no loop, for exactly the reasons the &lt;a href="https://harrisonsec.com/blog/a-wrong-ruler-is-worse-than-no-ruler/" rel="noopener noreferrer"&gt;companion piece&lt;/a&gt; lays out: a wrong check with authority does not leave you uncertain, it leaves you confidently wrong, and here it does so in a cycle that compounds. So the discipline from that piece applies in full to the scorer: verify it at least as hard as the claims it will act on, score by comparison against anchors rather than emitting a bare number, and know the ceiling of human agreement before you trust a model to beat it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two places the loop lives
&lt;/h2&gt;

&lt;p&gt;In a real agent the loop shows up at two scales, and mature systems run both.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;per-step, as middleware.&lt;/strong&gt; A validator runs after every tool call, before the model sees the result. What gets fed back into the next prompt is not the raw API response. It is the validated, scored, sometimes rewritten version of it. This is what stops a single bad step from silently poisoning everything downstream, because the model never gets to reason on top of an unchecked result. The loop here is tight and cheap and runs constantly.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;end-to-end, as a harness.&lt;/strong&gt; At the boundary of the whole task, you check the full resulting state against the intended outcome and treat the score as a circuit breaker. If the end-to-end score is below threshold, the agent does not report success. It halts and surfaces the partial progress honestly: here is what changed, here is what did not, here is where it stopped. This is the layer that would have caught the rename. Per-step middleware might have passed each edit individually while the aggregate still missed two sites, and only a check against the whole goal sees that gap.&lt;/p&gt;

&lt;p&gt;You want both because they catch different failures. Middleware catches the step that went wrong. The harness catches the task that came out wrong even though every step looked fine. Skip the middleware and errors compound before you notice. Skip the harness and you are back to an agent that reports success it never achieved, which is where we started.&lt;/p&gt;

&lt;h2&gt;
  
  
  The distributed-systems reframe
&lt;/h2&gt;

&lt;p&gt;If all of this feels familiar, it should. It is eventual consistency wearing different clothes.&lt;/p&gt;

&lt;p&gt;An assertion-style check on a freshly written row in a distributed store fails not because the write was wrong but because it has not propagated yet. The correct pattern was never a single assertion. It was a loop: poll until the read converges with the write, or until a timeout says stop waiting. Model output validation is the same pattern with cognitive uncertainty standing in for replication lag. The output is not yet known-good, so you observe, you score, you decide whether to wait for a better one or accept what you have, and you bound the whole thing with a budget so it terminates.&lt;/p&gt;

&lt;p&gt;Which is why teams from a distributed-systems background tend to get this right faster once it is named. You already know that in any system where the truth arrives late and imperfectly, a one-shot check is the wrong instrument. You built loops for that years ago. An LLM is just another source where the truth arrives late and imperfectly. The retry-with-backoff instinct is half the answer. The scoring function is the half that is new.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an assertion is still right
&lt;/h2&gt;

&lt;p&gt;The loop is not a universal upgrade, and reaching for it everywhere is its own mistake. The whole reason the loop exists is that the output is non-deterministic. Where the output is deterministic, an assertion is not just adequate, it is correct, and wrapping a deterministic step in a scoring loop adds cost and latency to buy nothing.&lt;/p&gt;

&lt;p&gt;The schema either validates or it does not. The parser either succeeds or it throws. The row count either matches or it does not. Those are assertions, and they should stay assertions, sitting at the boundaries of your system doing exactly the one-shot job they are good at. The &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;technique boundary&lt;/a&gt; is the tool for deciding which is which: put as much of the system as you can onto deterministic ground where a plain assertion holds, and reserve the loop for the parts that are genuinely non-deterministic and genuinely need judgement. An agent that scores and retries its way through a JSON schema check is making the opposite mistake to the one this piece is about, and it is just as wasteful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one honest question
&lt;/h2&gt;

&lt;p&gt;The next time an agent hands you a confident &lt;em&gt;done&lt;/em&gt;, ask it the question the loop asks and the assertion cannot: not &lt;em&gt;did it pass&lt;/em&gt;, but &lt;em&gt;how do you know, and what did you observe to know it.&lt;/em&gt; If the answer is that the model believed it, you have an assertion the model wrote for itself. If the answer is a real observation of the world, scored against the goal, with a decision that followed from the score, you have a loop. Only one of those two agents can be trusted to tell you when it failed, and it is the one that was built to look.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This piece extends two companions. The boundary it assumes: &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;Determinism Where You Can, Judgement Where You Must&lt;/a&gt;. The check-verification discipline its scorer depends on: &lt;a href="https://harrisonsec.com/blog/a-wrong-ruler-is-worse-than-no-ruler/" rel="noopener noreferrer"&gt;A Wrong Ruler Is Worse Than No Ruler&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A Wrong Ruler Is Worse Than No Ruler: Verifying the Checks You Trust</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Fri, 17 Jul 2026 19:47:14 +0000</pubDate>
      <link>https://dev.to/harrisonsec/a-wrong-ruler-is-worse-than-no-ruler-verifying-the-checks-you-trust-1gkb</link>
      <guid>https://dev.to/harrisonsec/a-wrong-ruler-is-worse-than-no-ruler-verifying-the-checks-you-trust-1gkb</guid>
      <description>&lt;p&gt;There is one failure mode I have learned to fear more than a missing check.&lt;/p&gt;

&lt;p&gt;A system with no check for something is at least honest about it. The gap is visible, the uncertainty is real, and everyone downstream knows not to lean on that part. A system with a &lt;em&gt;wrong&lt;/em&gt; check for the same thing is worse, and it is worse in a specific, dangerous way. It answers with confidence. It overrules the signals that were actually right. And it does all of this wearing the one badge nobody thinks to question. &lt;em&gt;It's deterministic, so it must be correct.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A wrong check with authority is worse than no check at all. No check leaves you honestly uncertain. A wrong authoritative check leaves you confidently wrong, and it silences the correct signal it overruled.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The two companion pieces to this one both push in the same direction: &lt;a href="https://harrisonsec.com/blog/shrink-the-stochastic-surface/" rel="noopener noreferrer"&gt;put as much of the system as you can onto deterministic ground&lt;/a&gt;, and &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;let a rule decide wherever a rule can&lt;/a&gt;. I still believe every word of that. But both standards make a quiet assumption I want to drag into the light. When they say &lt;em&gt;rules first&lt;/em&gt;, and especially when they say a deterministic rule may &lt;em&gt;overrule&lt;/em&gt; a probabilistic judge, they assume the rule is right. This piece is about what happens when it is not, and how to make sure it is before you let it hold a veto.&lt;/p&gt;

&lt;h2&gt;
  
  
  The badge nobody checks
&lt;/h2&gt;

&lt;p&gt;A deterministic number reads as ground truth. That is its great virtue and its hidden trap. When a metric prints &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;47&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, it does not sound like an opinion, it sounds like a fact, and we grant it the standing of one. In a rules-first architecture we go further: we give the deterministic layer &lt;em&gt;authority over the probabilistic one&lt;/em&gt;. The rule gates the output. The rule vetoes the judge. That is the correct design. A fluent model that hallucinates a verdict should not get to overrule a schema check that a parser can settle.&lt;/p&gt;

&lt;p&gt;But look at what that authority does to the cost of an error. An advisory signal that is wrong is noise: you can ignore it. An &lt;em&gt;authoritative&lt;/em&gt; signal that is wrong is not noise, it is a wrong answer with the power to enforce itself. And the badge of determinism, the quiet "it can't be wrong, it's just arithmetic," is exactly what stops anyone from auditing it. The check you trust the most, because it is deterministic and it is yours, is the one whose errors you are least equipped to catch. You built it to be believed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A metric that lied
&lt;/h2&gt;

&lt;p&gt;Here is the one that taught me the lesson.&lt;/p&gt;

&lt;p&gt;Picture an evaluator for generated long-form documents, the kind a model drafts section by section. One of its quality checks looks for lazy repetition: a document that just restates the same paragraph a dozen times over is worse than one that actually develops. A reasonable thing to measure. The implementation folded each paragraph into a signature, a hash of its normalized text, and flagged the document when too many signatures came out identical.&lt;/p&gt;

&lt;p&gt;The hash was cheap, and it was broken. It collided, so genuinely different paragraphs sometimes produced the same signature. And it was brittle to trivial edits, so the same paragraph with two words swapped or its sentences reordered hashed to something new. The net effect was a metric that missed near-duplicates when their surface form changed, and occasionally invented sameness where the content was genuinely different.&lt;/p&gt;

&lt;p&gt;On its own, a buggy metric is just noise you could learn to ignore. But this one had been handed authority. When the model judge read the document and said, correctly, "these sections are near-duplicates of each other," the repetition metric was allowed to &lt;em&gt;veto&lt;/em&gt; the complaint: "the signatures don't match, so the judge is imagining it." And the veto won. The score came out clean.&lt;/p&gt;

&lt;p&gt;Sit with what that pipeline was doing on that facet. It was running its own logic backwards: using a &lt;em&gt;wrong deterministic signal&lt;/em&gt; to overrule a &lt;em&gt;correct subjective one&lt;/em&gt;, and then reporting a better score for the trouble. The judge had been right. The rule silenced it. And because the silencing came from a deterministic check, it arrived stamped with confidence, and every inflated score looked earned. Nobody goes back to re-litigate a number the arithmetic already settled. That is the trap closing.&lt;/p&gt;

&lt;p&gt;The failure did not surface as an error. It surfaced as &lt;em&gt;cleaner results&lt;/em&gt;, which is the most dangerous shape a failure can take, because clean results are what everyone was hoping for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why wrong-and-authoritative is the worst quadrant
&lt;/h2&gt;

&lt;p&gt;Lay it out as a two-by-two: a check is either right or wrong, and it either carries authority or is merely advisory. Three of those cells are fine. A right check with authority is the whole point of the discipline. A right check that is only advisory is a mild waste. A wrong check that is only advisory is ignorable noise. It is the fourth cell, &lt;strong&gt;wrong and authoritative&lt;/strong&gt;, that is not a lesser version of a good check but an active liability, and it is worse than the empty cell where no check exists at all.&lt;/p&gt;

&lt;p&gt;Compare the two directly. With no repetition check, you know you cannot currently measure repetition. The gap is on the map, and a repetitive document simply passes unremarked, the same as anything else you haven't instrumented. With a &lt;em&gt;wrong&lt;/em&gt; repetition check that vetoes the judge, a repetitive document passes &lt;em&gt;and&lt;/em&gt; the one signal that correctly caught it gets overruled &lt;em&gt;and&lt;/em&gt; the final score ticks upward, all under a badge that discourages anyone from doubting it. The missing check costs you a blind spot you know about. The wrong check costs you the correct answer you already had, plus the false confidence that you don't need to look.&lt;/p&gt;

&lt;p&gt;Which yields the single most useful habit I took from this: &lt;strong&gt;suspect the metric first.&lt;/strong&gt; When a deterministic check refutes an observation that a careful human or a competent model would make, the base rate is not on the check's side. A hand-rolled hash is far more likely to be broken than a fluent reader is to be hallucinating "these sections are near-duplicates" about a document where they visibly are. The reflex to trust the number &lt;em&gt;because it is a number&lt;/em&gt; is precisely the reflex that keeps the wrong number in charge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other way a ruler lies
&lt;/h2&gt;

&lt;p&gt;There is a second ruler that lies, and it lies in the very layer the first one was busy overruling. It is the bare absolute score: &lt;code&gt;quality = 0.6&lt;/code&gt;, emitted by a model judge with no anchors, no comparison, no zero-point, and then handled as if it were a measurement.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;0.6&lt;/code&gt; like that is not a reading; it is a decimal in the costume of one. Ask what it is 0.6 &lt;em&gt;of&lt;/em&gt;. Better than which example, worse than which, on a scale pinned to what? There is no answer underneath. Humans and models share a specific profile here: both are reliable at &lt;em&gt;ordering&lt;/em&gt; ("A is closer to the brief than B") and unreliable at &lt;em&gt;absolute calibration&lt;/em&gt; ("this deserves a 0.6"). A standalone perceptual number inherits all of the unreliable half and none of the reliable half. It is a subjective opinion that has been rounded to two decimal places and thereby laundered into looking objective, the same laundering the broken hash performed, run in the other layer.&lt;/p&gt;

&lt;p&gt;The fix rhymes with the fix for the first ruler: do not trust the number until the number has earned trust. For a subjective axis, that means &lt;strong&gt;score by comparison, against anchors&lt;/strong&gt;, a set of labelled reference examples that fix what good and excellent actually look like, and rank new outputs against them instead of emitting a free decimal. And before you trust a judge's agreement with humans, measure how well humans agree with &lt;em&gt;each other&lt;/em&gt;. That inter-rater ceiling is the best score any judge can honestly aspire to. If two careful reviewers only agree seven times in ten on an axis, a judge that reports a confident number above that is not beating the humans, it is hiding the disagreement they honestly surfaced. Reporting a judge's number without that ceiling is one more measurement with no zero-point. A ruler with no marked zero and no fixed unit is not a strict ruler; it is a confident guess holding a straightedge.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a check earns its authority
&lt;/h2&gt;

&lt;p&gt;So the discipline here is not "add more checks." It is narrower and more demanding: &lt;strong&gt;before any check is allowed to gate output or overrule another signal, verify the check itself, as hard as the claim it will silence.&lt;/strong&gt; In practice that is four moves.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recompute it a second, independent way.&lt;/strong&gt; Derive the same quantity by a different method and require the two to agree. If a repetition score can be computed by hashing and also by direct comparison, do both; a disagreement is a bug in one of them, and you want to find that in a test, not discover it as a mysteriously clean production score.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regression-test it in both directions.&lt;/strong&gt; A case you know is repetitive must trip it; a case you know is varied must not. A metric validated only in the direction it usually fires is half-tested, and the untested half is exactly where a false veto hides. And wrong does not only mean buggy. A check with correct arithmetic and a badly chosen threshold is wrong in the same way and hides in the same untested direction, so the cases you pick have to pin the boundary, not just the obvious middle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it in shadow before it gates.&lt;/strong&gt; Put it in production computing its verdicts, but let it change nothing. Watch it at scale, side by side with the signal it will eventually overrule. A wrong metric announces itself here, as a steady stream of vetoes against observations that were plainly correct, and it does so &lt;em&gt;before&lt;/em&gt; it has moved a single real verdict. Shadow-first is not caution for its own sake; it is the one place a confidently-wrong check reveals itself while the damage is still zero.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat a veto as a claim, not a license.&lt;/strong&gt; When a rule overrules a judge, it is asserting something checkable, "these sections are not actually duplicates," and that assertion has to be as verifiable as the judge's was. The fact-check does not get to be the one unaudited step in a pipeline whose entire purpose is to audit everything else.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is an asymmetry worth naming here, because it is the whole reason this failure survives. We already do all four of these things for models. We stage a new model behind a flag, we hold a judge back until it agrees with humans, we assume the probabilistic thing needs proving before we lean on it. We almost never extend the same suspicion to a rule, because the rule wears the badge that says it does not need it. That asymmetry is the bug. The deterministic layer is handed the most authority and subjected to the least verification, and it is handed that authority &lt;em&gt;precisely because&lt;/em&gt; nobody expects to have to check it.&lt;/p&gt;

&lt;p&gt;Underneath all four is a habit good teams already apply to &lt;em&gt;other people's&lt;/em&gt; numbers. When a result comes back too clean, too uniform, suspiciously convenient, you don't celebrate it. You go verify it against the source before you repeat it. This piece is that same skepticism, turned inward, and pointed most sharply at whatever metric you have handed a gate or a veto. External results earn scrutiny because they might be flattering. Your own authoritative checks deserve &lt;em&gt;more&lt;/em&gt;, because they are flattering and you trust them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a rough check is fine
&lt;/h2&gt;

&lt;p&gt;Every standard needs its boundary, so here is this one's: &lt;strong&gt;the danger is authority, not error.&lt;/strong&gt; A rough, sometimes-wrong signal is not merely tolerable, it is useful, as long as it stays advisory. A heuristic that flags documents for a human to glance at, a cheap metric that sorts a review queue, a smell test that nudges attention toward the likely problems: each can be wrong a fair fraction of the time and still pay for itself, because the cost of a wrong advisory signal is a wasted glance.&lt;/p&gt;

&lt;p&gt;The line is crossed the moment a metric gains authority, the moment it can fail a build, block an output, or overrule another signal without appeal. At that instant its tolerance for being wrong drops toward zero, because its errors stop costing a glance and start costing the correct signal it now outranks. So the standard is not "verify every check to perfection," which would freeze you. It is "match the verification to the authority." Advisory signals can be rough and useful. Anything holding a veto has to have earned it, in both directions, in the open, before it ever changes a verdict.&lt;/p&gt;

&lt;p&gt;A missing ruler tells you honestly that you cannot measure something yet. A wrong ruler tells you confidently that you can, and hands you the wrong number with a straight face. And if that ruler also has the authority to overrule the one instrument that was reading correctly, it will quietly make your whole system worse while every dashboard turns greener. Between the two, the honest gap is the safer place to stand. The only work worth doing from there is turning it into a ruler you have actually checked.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This piece extends two companions. The standard it builds on: &lt;a href="https://harrisonsec.com/blog/shrink-the-stochastic-surface/" rel="noopener noreferrer"&gt;Shrink the Stochastic Surface&lt;/a&gt;. The boundary it assumes: &lt;a href="https://harrisonsec.com/blog/the-technique-boundary/" rel="noopener noreferrer"&gt;Determinism Where You Can, Judgement Where You Must&lt;/a&gt;. And the architecture both sit inside: &lt;a href="https://harrisonsec.com/blog/generative-ai-builds-shapes-not-games/" rel="noopener noreferrer"&gt;Generative AI Builds Shapes, Not Games&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Determinism Where You Can, Judgement Where You Must: The Technique Boundary for AI Systems</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Thu, 09 Jul 2026 15:27:05 +0000</pubDate>
      <link>https://dev.to/harrisonsec/determinism-where-you-can-judgement-where-you-must-the-technique-boundary-for-ai-systems-1o4h</link>
      <guid>https://dev.to/harrisonsec/determinism-where-you-can-judgement-where-you-must-the-technique-boundary-for-ai-systems-1o4h</guid>
      <description>&lt;p&gt;I have now written this same sentence three times in three pieces, so it is time to write the sentence underneath it.&lt;/p&gt;

&lt;p&gt;The first companion piece, &lt;a href="https://harrisonsec.com/blog/generative-ai-builds-shapes-not-games/" rel="noopener noreferrer"&gt;Generative AI Builds Shapes, Not Games&lt;/a&gt;, argued that generative AI produces plausible shapes and that correctness has to come from structure and verification wrapped around the generator. That answers &lt;em&gt;whether&lt;/em&gt; you need something outside the model. The second, &lt;a href="https://harrisonsec.com/blog/shrink-the-stochastic-surface/" rel="noopener noreferrer"&gt;Shrink the Stochastic Surface&lt;/a&gt;, argued that reliability is set by how much of the output you leave the model solely responsible for, and that the work is shrinking that fraction. That answers &lt;em&gt;how much&lt;/em&gt; structure, and &lt;em&gt;where the line goes&lt;/em&gt; between model and machine.&lt;/p&gt;

&lt;p&gt;Both leave one question open. Once you have decided that a part of the system should be deterministic structure rather than a raw sample, &lt;em&gt;which&lt;/em&gt; structure? A rule? A state machine? A model reasoning inside a harness? And the question almost everyone is getting wrong in 2026: should any of it be an agent?&lt;/p&gt;

&lt;p&gt;This piece is about that boundary. It is less glamorous than the other two because it is the part you have to get right in the code, not on the whiteboard, but it is where systems actually live or die. Here is the law, as plainly as I can put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Determinism where you can, probabilistic judgement only where the question genuinely demands it, and control always with the orchestrator, never with an autonomous loop. The model may reason under that control; it may not direct the control itself.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything below is an unpacking of that one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four techniques, not one tool
&lt;/h2&gt;

&lt;p&gt;The dominant failure mode in AI system design right now is that everything collapses into a single verb: &lt;em&gt;call the model&lt;/em&gt;. Need to validate something, call the model. Need to route, call the model. Need to decide what to do next, call the model. It feels modern and it is almost always wrong, because it treats four fundamentally different kinds of computation as if they were one.&lt;/p&gt;

&lt;p&gt;There are four techniques, and each has exactly one correct home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rule engine&lt;/strong&gt; owns any question with one machine-computable, reproducible answer. Is this valid syntax? Is the payload under the size bound? Does this reference resolve? Is the structure connected? These are not opinions. A parser answers them in microseconds, for free, and is right every single time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A state machine&lt;/strong&gt; owns lifecycle and flow, the questions where &lt;em&gt;what happens next&lt;/em&gt; depends on &lt;em&gt;where you are now&lt;/em&gt; and only some transitions are legal. A job goes &lt;code&gt;pending → running → done | failed | timeout&lt;/code&gt;. A retry goes &lt;code&gt;closed → open → half-open&lt;/code&gt;. The point of making this explicit is that it kills an entire bug class, the one you get from ad-hoc boolean flags, where a job somehow transitions from &lt;code&gt;completed&lt;/code&gt; back to &lt;code&gt;running&lt;/code&gt; because two flags disagreed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An LLM&lt;/strong&gt; owns probabilistic judgement, and only that: the questions that are genuinely subjective and that no rule can answer. Is this writing coherent? Does this read as the thing the prompt asked for? Is it good versus merely acceptable? There is no formula for "cozy." Something has to make a perceptual call, and for the parts a rule cannot reach, that something is a model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An agent&lt;/strong&gt;, an LLM in a loop choosing its own next actions, owns far less than the current enthusiasm suggests, and in a large class of systems it owns nothing at all. We will get to why.&lt;/p&gt;

&lt;p&gt;The reason the boundary matters is that placing work in the wrong tier is expensive in &lt;em&gt;both&lt;/em&gt; directions, and people usually only notice one.&lt;/p&gt;

&lt;p&gt;Put an LLM where a rule belongs and you get the obvious costs, slower and more expensive, plus a subtle one that is worse: it is &lt;em&gt;less correct&lt;/em&gt;. An LLM can hallucinate a syntax verdict. A parser cannot. You have taken a question with a perfect deterministic answer and handed it to the one tool in your kit that can be confidently wrong about it. That is not a trade of accuracy for flexibility. It is strictly worse on every axis.&lt;/p&gt;

&lt;p&gt;Put a rule where the question is genuinely subjective and you get the opposite failure: the rule simply cannot answer, so you either ship a gate that misses the thing it exists to catch, or you contort a threshold into pretending a taste question is arithmetic. Both are ways of lying about the nature of the problem.&lt;/p&gt;

&lt;p&gt;Getting the boundary right is not a matter of preference. It follows from what each tool is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that draws the line
&lt;/h2&gt;

&lt;p&gt;There is a single question that sorts most work into the right tier, and it is worth memorizing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Would two informed people always reach the same answer from the data alone?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run any criterion through it and one of three things happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes, always.&lt;/strong&gt; The answer is a mechanical fact: a count, a dimension, a material, a connectivity check. This is the rule engine's territory, no exceptions. If two experts with the raw data in front of them cannot disagree, there is nothing for a model to add except cost and the possibility of error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not from the raw data, but the criteria can be articulated.&lt;/strong&gt; Two reviewers might land in slightly different places, but they can &lt;em&gt;explain&lt;/em&gt; what they are looking at, and that explanation can be written down as an explicit standard. This is the model's territory, but with a crucial constraint: the model's job is not ineffable taste, it is &lt;em&gt;reasoning over that written standard&lt;/em&gt;, and the reasoning is the product. A judgement that hangs on a named criterion is auditable and improvable. A bare number is neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The judgement is real, but nobody has written it down yet.&lt;/strong&gt; The reviewer knows excellent when they see it and cannot articulate why. This is the bucket everyone skips, and skipping it is the most common way an LLM judge silently fails. If you hand this straight to a model, it does not decline to answer. It scores against its own generic, unstated taste, and you get something reliable against the wrong ruler. The work here is &lt;em&gt;elicitation&lt;/em&gt;: dragging the tacit standard out of the domain owner and into explicit criteria, at which point it becomes the second bucket and a model can apply it. Until that happens, the honest move is to route it to a human and log what you could not yet express, not to let the model paper over the gap.&lt;/p&gt;

&lt;p&gt;The three buckets are not academic. They are the difference between a judge that applies &lt;em&gt;your&lt;/em&gt; standard and a judge that applies &lt;em&gt;its&lt;/em&gt; standard while wearing your logo. Most of the effort in building a trustworthy judge goes into the third bucket, the elicitation, and almost none of the discourse does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line that actually matters: control versus reasoning
&lt;/h2&gt;

&lt;p&gt;Now the hard part, the one the whole law turns on.&lt;/p&gt;

&lt;p&gt;The instinct, once you have accepted "determinism where you can," is to read the LLM tier as &lt;em&gt;narrow&lt;/em&gt;: one call, one score, get in and get out. That is the wrong lesson, and it leaves enormous value on the table. The boundary is not about limiting how much the model &lt;em&gt;reasons&lt;/em&gt;. A model can reason at length, chain several steps, weigh evidence, propose a plan, and all of that can be perfectly sound engineering. The boundary is about who owns &lt;em&gt;control&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Reasoning is the model producing a judgement, a diagnosis, a plan, a score, as a step inside a flow that something else drives. Control is deciding &lt;em&gt;what step happens next&lt;/em&gt; and &lt;em&gt;when the work is done&lt;/em&gt;. An agent is precisely a model that has been handed control: it chooses its own actions, re-runs stages at will, and self-determines completion. That single property, self-direction of control, is the thing to withhold.&lt;/p&gt;

&lt;p&gt;Say it as a rule: &lt;strong&gt;let the model propose, keep the disposing with the orchestrator.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fine: the model emits a diagnosis, a suggested fix, and a confidence. The orchestrator decides whether to apply it, retry, or escalate.&lt;/li&gt;
&lt;li&gt;Fine: the model reasons through five criteria and returns a structured verdict for each.&lt;/li&gt;
&lt;li&gt;Not fine: the model decides which criteria to check, re-runs whichever stages it feels like, and announces on its own that it is finished.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleanest place this distinction shows up is function calling, because the same API serves both sides of the line. Using a tool schema to make the model &lt;em&gt;emit a structured result&lt;/em&gt;, to fill a form with a sub-score and the fact it relied on, is just an output format, and it is fine. Letting the model &lt;em&gt;choose which tools to call, in what order, and when to stop&lt;/em&gt; is a control loop, and that is what makes it an agent. Tool schemas appearing in your code tells you nothing. Who owns the loop tells you everything.&lt;/p&gt;

&lt;p&gt;Here is the part that makes the whole thing click, and it is an argument from engineering, not from taste. &lt;strong&gt;Handing control to a model is only worth it when choosing the next step actually requires intelligence.&lt;/strong&gt; In a surprising amount of real work, it does not. If the list of things to check is fixed, and each check is a static, design-time mapping from criterion to computation, then there is nothing to &lt;em&gt;decide&lt;/em&gt;. The control flow carries no intelligence. Handing it to the model in that case buys you exactly nothing and costs you a great deal: non-reproducibility, latency, token spend, and a fresh set of failure modes, loops that never terminate, checks silently skipped, tools hallucinated. You pay the full price of an agent for a control flow a &lt;code&gt;for&lt;/code&gt; loop expresses perfectly.&lt;/p&gt;

&lt;p&gt;So the question is never "could this be an agent?" Almost anything &lt;em&gt;could&lt;/em&gt; be. The question is "does choosing the next step here require judgement that is not known at design time?" Reserve autonomy for the cases where the answer is genuinely yes: open-ended or branching work whose shape you cannot lay out in advance, or an exploration phase whose entire purpose is to &lt;em&gt;discover&lt;/em&gt; the steps, which you then harden into a fixed pipeline. Everywhere else, a fixed loop with a model reasoning inside it is not a compromise. It is the better design on its own merits, before you even invoke reproducibility.&lt;/p&gt;

&lt;p&gt;There is one seam worth leaving open, because it is the place agency earns its keep even inside a fixed pipeline: the unexpected. If the model, while judging, notices something worth checking that is not on the static list, let it &lt;em&gt;propose&lt;/em&gt; an extra check. The orchestrator still decides whether to run it. That places the model's intelligence where it actually helps, noticing the unanticipated, and keeps it away from the part that needs none, selecting among known checks. Bounded, and still not driving.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same boundary on the generation side
&lt;/h2&gt;

&lt;p&gt;Everything so far has been about inspection: taking an output and deciding whether it passes. But this is not an evaluation-only law. It governs generation just as strictly, and the mistake there is the mirror image. The default way to make a model produce an artifact is to ask it for the whole thing from a blank canvas, which hands the entire construction to the sampler and maximizes exactly the surface the companion pieces warned about.&lt;/p&gt;

&lt;p&gt;The disciplined version applies the same split. Let the model do the subjective, creative part, planning, choosing, proposing parameters, and let a deterministic layer execute that plan against a pre-validated structure. A model that plans a bounded edit to a known-good template, with a rule engine applying that edit inside the template's constraints, inherits the template's validity and its taste, and confines the model to a change small enough to stay valid. The invalid result becomes unreachable instead of caught downstream, which is the strongest form of the funnel from &lt;a href="https://harrisonsec.com/blog/shrink-the-stochastic-surface/" rel="noopener noreferrer"&gt;Shrink the Stochastic Surface&lt;/a&gt;: the cheapest possible check is making the wrong output impossible to emit. Same boundary, same reason. The model proposes, deterministic structure disposes, whether the thing being produced is a verdict or an artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case that makes it obvious: never put an agent in the gate
&lt;/h2&gt;

&lt;p&gt;If you want a single place to feel this law in your gut, look at evaluation.&lt;/p&gt;

&lt;p&gt;An evaluator, a quality gate, a validator, anything whose job is to render a verdict, has one non-negotiable property: &lt;strong&gt;same input, same verdict.&lt;/strong&gt; Reproducibility is not a nice-to-have for a gate; it is the whole basis of trust in it. A gate that rejects someone's work with &lt;code&gt;0.42&lt;/code&gt; today and &lt;code&gt;0.71&lt;/code&gt; tomorrow for the same input is unappealable, undebuggable, and uncalibratable. Nobody can act on it, including you.&lt;/p&gt;

&lt;p&gt;Now notice what an agent &lt;em&gt;is&lt;/em&gt;: a thing whose defining virtue is self-directed adaptivity, whose whole selling point is that it decides for itself what to do next based on what it sees. That is a wonderful property for an explorer and a catastrophic one for a ruler. An evaluator that decides for itself what to check next is non-reproducible by construction. Its adaptivity, the exact feature you would be buying it for, is the thing that destroys its authority.&lt;/p&gt;

&lt;p&gt;This is the irony of the current moment. The industry is racing to make everything agentic, and the single most seductive place to reach for an agent, "just let the smart model look at the output and judge it," is one of the places it is most wrong. The correct relationship between an agent and a gate is not that the gate is an agent. It is that the gate &lt;em&gt;watches&lt;/em&gt; the agent. Measure the agent's behaviour, count its convergence rounds, score its output, but the thing doing the scoring must be the opposite of an agent: fixed, predictable, the same ruler every time. You do not want your ruler to have opinions about how long it is today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where determinism and judgement collaborate: the rule as lie detector
&lt;/h2&gt;

&lt;p&gt;The law is not "rules and models live in separate rooms." Their most valuable interaction is when they work on the &lt;em&gt;same&lt;/em&gt; judgement, and it is the mechanism that turns an LLM judge from a black box into something auditable.&lt;/p&gt;

&lt;p&gt;Start from the honest position: the subjective part is irreducible. No rule computes "harmonious" or "coherent." A model has to produce that number. But producing the number is not the same as being trusted for it, and this is where the rule engine earns a second job.&lt;/p&gt;

&lt;p&gt;When the model scores, make it &lt;em&gt;state the evidence it relied on&lt;/em&gt;. Not just "this summary is inaccurate, 0.3," but "this summary is inaccurate because it claims the report recommends option B." Now that stated evidence often reduces to a checkable fact, and a deterministic check verifies it. Does the source document actually recommend option B, or does a string search show it never mentions B at all? Does the code the model called unsafe actually reach the unchecked path, or does a static check show the guard is present? Does the answer it praised for citing three sources actually cite three, or is it one repeated? Whenever the model's load-bearing evidence is a fact, the rule engine confirms or refutes it, and a score built on a refuted fact is voided.&lt;/p&gt;

&lt;p&gt;The division of authority is precise, and neither side can do the other's job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;model has the scoring power&lt;/strong&gt;. It produces the perceptual judgement and names the fact it rested on. The rule engine cannot author a quality score, because "harmonious" is not computable, so it never scores.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;rule engine has veto power over the evidence&lt;/strong&gt;. It verifies the cited fact and can void a score built on a false one, but it cannot replace that score with a number of its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So neither is solely decisive. When the cited fact checks out, the model's score stands. When it is refuted, the rule does not overwrite the score, it &lt;em&gt;invalidates&lt;/em&gt; it and routes to a bounded re-judge or a human. The judge becomes trustworthy not because you have decided to trust it, but because its evidence can be deterministically refuted. That is the difference between "we believe the model" and "the model cannot lie about the parts that are checkable." Only the second one is engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like assembled
&lt;/h2&gt;

&lt;p&gt;None of these techniques is interesting alone. The value is in how they compose, and the composition has a shape: a deterministic spine that owns the loop, with the model hanging off it as a called reasoning node that proposes and never drives.&lt;/p&gt;

&lt;p&gt;Read one concrete gate end to end. Content arrives. Cheap rules run first, syntax and schema and size, and anything that fails dies immediately for free. What survives enters a lifecycle state machine that governs the run. The orchestrator then calls the model as a judge: it reasons over the standard and returns per-criterion sub-scores plus the facts it relied on. Those facts go straight to a rule fact-check, which refutes any that are false and sends the judgement back for a bounded re-score. Only what clears both the rules and the verification becomes a verdict.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deterministic spine — the orchestrator owns the loop:

  content ─▶ cheap rules ─pass─▶ lifecycle FSM ─▶ [call judge] ─▶ rule fact-check ─confirmed─▶ verdict
               └─ fail fast ──────────────────────────────────────────────────────────────────▶ verdict

The judge is called and returns — it never drives:

  [call judge]  ──request──▶  LLM judge · reasoning node
                             (proposes sub-scores, cites facts; no control, no loop)
  LLM judge  ──proposal + cited facts──▶  rule fact-check
  rule fact-check  ──refuted──▶  bounded re-score  ⟲ back to [call judge]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what never happens in that picture: the model never holds the arrow that decides what runs next. It is called, it reasons, it returns. Every solid line, the loop itself, stays with the orchestrator. The dotted lines are the model's entire role, a request out and a proposal back. That is the whole law in one diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ordering falls out for free
&lt;/h2&gt;

&lt;p&gt;One more thing drops out of the boundary without any extra design, and it is the reason the whole arrangement is also the cheap one.&lt;/p&gt;

&lt;p&gt;Deterministic checks cost microseconds and no money. Model calls cost seconds and real dollars. So the boundary hands you the correct ordering by default: run the cheap, reproducible gates &lt;em&gt;first&lt;/em&gt;. They weed out the obviously broken before any paid work happens. The expensive judge runs &lt;em&gt;last&lt;/em&gt;, only on content that already cleared everything cheap. You never spend a model call scoring something that fails a syntax check, which would be burning money to grade garbage.&lt;/p&gt;

&lt;p&gt;Cost then scales with quality instead of volume: bad inputs die cheap at the front, and you only pay the expensive price for things good enough to deserve it. This is the same funnel shape the stochastic-surface piece described for reliability, and it is not a coincidence that the reliable arrangement and the cheap arrangement are the same arrangement. They are both consequences of putting each question in the tier that fits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserve the seams, so reasoning stays additive
&lt;/h2&gt;

&lt;p&gt;The last clause of the law, control never to the agent, is the one most likely to be misread as "never add model reasoning to the loop." It is not. It is "add it without moving control into a loop the model owns." Those are different, and keeping them different is what lets a deterministic system grow bounded intelligence later without a rewrite.&lt;/p&gt;

&lt;p&gt;If you expect to add diagnosis, repair recommendation, or deeper failure analysis down the line, build the seams now, cheaply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replayable events.&lt;/strong&gt; Every transition and finding emitted as a durable, ordered record, so a later reasoning step can replay exactly what happened instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independently addressable stages.&lt;/strong&gt; Keep the pipeline decomposed, so a diagnosis step can point at one stage without unpicking the whole run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A read-only retrieval seam.&lt;/strong&gt; A way to pull prior records and context as &lt;em&gt;evidence&lt;/em&gt; for a diagnosis, without letting the diagnosis mutate the verdict path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-verdict hooks.&lt;/strong&gt; Failure analysis runs &lt;em&gt;after&lt;/em&gt; the reproducible verdict, never inside it, so it can never change the result it is analysing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advisory output only.&lt;/strong&gt; A repair step produces a suggested fix, a rationale, and a confidence. Whether to apply, retry, or escalate stays the orchestrator's call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The invariant across all of them is the law restated: repair and retry are controlled by the orchestrator, not by a model acting on its own. Anything that would move control into an autonomous loop is not a small extension, it is a change to the fundamental property of the system, and it should be treated as one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same law, one level down
&lt;/h2&gt;

&lt;p&gt;These three pieces are one argument at three depths. The first said correctness comes from structure around the generator, not from a bigger generator. The second said the engineering is minimizing the fraction of output only the model owns. This one says: of the part you keep deterministic, choose the determinism that fits the question, a rule for facts, a state machine for flow, and of the part you keep probabilistic, let the model reason as much as the problem needs but never let it hold the wheel.&lt;/p&gt;

&lt;p&gt;The probabilistic part proposes. The deterministic part disposes. And control, the decision of what happens next and when the work is done, is deterministic, always. That is not a constraint on what your system can do. It is the thing that makes what it does trustworthy, reproducible, cheap, and yours to debug at three in the morning when it matters.&lt;/p&gt;

&lt;p&gt;Determinism where you can. Judgement where you must. Control never to the agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the third piece in the arc. The first: &lt;a href="https://harrisonsec.com/blog/generative-ai-builds-shapes-not-games/" rel="noopener noreferrer"&gt;Generative AI Builds Shapes, Not Games&lt;/a&gt;. The second: &lt;a href="https://harrisonsec.com/blog/shrink-the-stochastic-surface/" rel="noopener noreferrer"&gt;Shrink the Stochastic Surface&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related, the same law at other layers: &lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt; and &lt;a href="https://harrisonsec.com/blog/agent-architecture-compute-allocation-advisor-strategy/" rel="noopener noreferrer"&gt;Agent Architecture Is a Compute Allocation Problem: The Advisor Strategy&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Shrink the Stochastic Surface: A Design Standard for Probabilistic Systems</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Tue, 30 Jun 2026 16:08:30 +0000</pubDate>
      <link>https://dev.to/harrisonsec/shrink-the-stochastic-surface-a-design-standard-for-probabilistic-systems-58n2</link>
      <guid>https://dev.to/harrisonsec/shrink-the-stochastic-surface-a-design-standard-for-probabilistic-systems-58n2</guid>
      <description>&lt;p&gt;I keep writing variations of the same sentence. Agent memory has to terminate at a source of truth. An agent loop has to terminate at a check. Generative 3D has to terminate at a verifier. The probabilistic part proposes, a deterministic part disposes.&lt;/p&gt;

&lt;p&gt;Four pieces, one shape. That is usually a sign there is a law underneath, not four coincidences. This is my attempt to write the law down, and then to turn it into a standard I can apply to the next system before it ships instead of after it breaks.&lt;/p&gt;

&lt;p&gt;The companion piece argued that generative AI builds plausible shapes and that correctness has to come from structure and verification wrapped around the generator. That answers &lt;em&gt;whether&lt;/em&gt; you need something outside the model. It does not answer &lt;em&gt;how much&lt;/em&gt;, or &lt;em&gt;where the line goes&lt;/em&gt;. This piece is about the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quantity nobody names
&lt;/h2&gt;

&lt;p&gt;Every probabilistic system has a number attached to it that almost no one states out loud: the fraction of its output whose correctness rests on a model getting it right, with nothing deterministic to anchor or check that part.&lt;/p&gt;

&lt;p&gt;Call it the &lt;strong&gt;stochastic surface&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A pure end-to-end model has a stochastic surface of one. Everything it emits is a sample, and every sample is trusted as final. A pocket calculator has a stochastic surface of zero. A real system sits between, and the exact position is the single most important design decision in the thing, more than the model, more than the prompt, more than the framework.&lt;/p&gt;

&lt;p&gt;Here is the claim, stated as plainly as I can:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The reliability of a probabilistic system is bounded by the size of its stochastic surface. Reliability engineering is surface reduction: pushing as much of the output as possible onto deterministic structure, and leaving the model only the irreducible part that genuinely has no anchor.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a style preference. It follows from what a sample is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the surface bounds reliability
&lt;/h2&gt;

&lt;p&gt;A deterministic component has a property a sampler never has: when it is wrong, it is wrong the same way every time, and you can find out. A unit test fails. A constraint is violated. A schema rejects the payload. The error is &lt;em&gt;locatable&lt;/em&gt;, which means it is &lt;em&gt;fixable&lt;/em&gt;, which means over time that part of the system trends toward correct and stays there.&lt;/p&gt;

&lt;p&gt;A sample has none of this. It is drawn fresh, it can be wrong differently every time, and, crucially, &lt;strong&gt;it carries no signal about whether it is wrong&lt;/strong&gt;. A plausible-looking output and a correct output are indistinguishable from inside the generator. That is the entire problem in one sentence: the model cannot tell you it succeeded, so it cannot be trusted even when it did.&lt;/p&gt;

&lt;p&gt;Now stack many such samples. If thirty percent of your output is on the stochastic surface, then thirty percent of every result is an unverified claim that could be confidently, undetectably wrong, and you have no map of which thirty percent. The failures do not announce themselves. They surface downstream, far from where they were born, as a doorless cottage the workflow was perfectly happy to call done.&lt;/p&gt;

&lt;p&gt;So the rough mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reliability ≈ 1 − (unanchored stochastic fraction)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not literally a formula you compute, but the right intuition. Every percent of output you move off the surface, by retrieving it instead of inventing it, by deriving it from a rule instead of sampling it, by checking it against a spec, is a percent that stops being an undetectable liability. You do not make a probabilistic system reliable by making the model better. You make it reliable by giving the model less of the output to be solely responsible for.&lt;/p&gt;

&lt;p&gt;That reframes everything. It means the work is not "pick the best model." It is "draw the smallest possible circle around the part only a model can do, and build deterministic structure everywhere else." Two sides of a system show this most clearly: how you generate, and how you evaluate. They turn out to be the same move.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generation side: anchor, don't invent
&lt;/h2&gt;

&lt;p&gt;When you need a model to produce something, the lazy default is to describe it and let the model conjure it from the prior. That maximizes the stochastic surface on purpose. Almost always you can do better, and the options form a clean ladder ordered by &lt;strong&gt;how many bits the model has to invent&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: retrieve, then edit
&lt;/h3&gt;

&lt;p&gt;The cheapest and most underrated move: do not generate from scratch, find the nearest high-quality real example and modify only what the task requires.&lt;/p&gt;

&lt;p&gt;In a game studio this is the difference between "generate a medieval cottage" and "here is a hand-built cottage our artists shipped, adapt it to a 15 by 15 footprint with a south door." The first asks the model to hallucinate an entire artifact. The second hands it a correct starting point and asks for a delta. The stochastic surface collapses from the whole object to the edit.&lt;/p&gt;

&lt;p&gt;The principle generalizes far past games. Retrieval-augmented generation is this move for text. Asking a coding model to modify an existing, tested function instead of writing one blind is this move for code. In every case the logic is identical: &lt;strong&gt;the model's real workload is total information minus retrievable information.&lt;/strong&gt; Anything already present in a real example is information the model does not have to invent, and therefore cannot get wrong. You spend a similarity search to buy down the surface. It is almost always a good trade.&lt;/p&gt;

&lt;p&gt;The failure mode to respect: retrieval anchors you to the retrieved thing, so retrieval quality becomes the new floor. Garbage neighbors give garbage edits. But notice what changed, the failure moved from an invisible generative hallucination to a &lt;em&gt;visible, checkable&lt;/em&gt; retrieval step you can inspect, score, and improve. That is surface reduction even when it is imperfect, because it converts an unanchored failure into an anchored one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2: fine-tune to narrow the distribution
&lt;/h3&gt;

&lt;p&gt;Retrieval changes the &lt;em&gt;starting point&lt;/em&gt;. Fine-tuning changes the &lt;em&gt;distribution itself&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is the tier worth dwelling on, because it is the one most often reached for first and understood least. A base model samples from an enormous manifold of plausible-for-the-internet outputs. Fine-tuning on your own high-quality, domain-specific data reshapes that manifold so the model's default sample lands much closer to what your domain considers good. You are not teaching it new facts so much as &lt;strong&gt;moving the center of mass of its prior&lt;/strong&gt; onto your distribution, and shrinking the variance.&lt;/p&gt;

&lt;p&gt;For a studio, training a model on your own corpus of shipped, art-directed, style-consistent assets does something retrieval cannot: it makes the &lt;em&gt;typical&lt;/em&gt; generation on-style and on-spec, not just the retrieved-and-edited one. It raises the floor everywhere, including the cases where you have no neighbor to retrieve. The economics also favor it once volume is high enough: a one-time training cost amortized across millions of generations, versus paying for a large frontier prompt every single time.&lt;/p&gt;

&lt;p&gt;But be precise about what fine-tuning does and does not do to the stochastic surface, because this is where people over-trust it. Fine-tuning &lt;strong&gt;lowers the surface, it does not remove it.&lt;/strong&gt; A model fine-tuned on perfect 15 by 15 cottages will produce cottages that are &lt;em&gt;usually closer&lt;/em&gt; to 15 by 15. It still has no representation of "footprint equals 15 by 15" as a predicate to satisfy and check. It samples from a tighter distribution, but it is still sampling. The discrete constraint is still discrete, and a narrower continuous distribution is still continuous. Fine-tuning buys you a better-behaved sampler. It does not buy you a solver or a verifier, and treating a fine-tuned model as if it were one is exactly the over-trust this whole standard is built to prevent.&lt;/p&gt;

&lt;p&gt;The right read: fine-tuning is the strongest &lt;em&gt;generation-side&lt;/em&gt; lever, and generation-side levers have a ceiling. They make the proposal better. They never make the proposal self-disposing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3: generate the residual only
&lt;/h3&gt;

&lt;p&gt;After you have retrieved what you can and narrowed what you can, whatever is left, the genuinely novel part with no prior to anchor to, is what you let the model invent freely. That residual is where a generative prior earns its keep: plausible, varied, rich single forms. The sphere and the gatehouse from the companion piece are this tier done well.&lt;/p&gt;

&lt;p&gt;The discipline is to make that residual as small as the task allows, and to wrap it, never to let it stand as the final word. Which is the other side of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evaluation side: rules first, model last
&lt;/h2&gt;

&lt;p&gt;Now flip from producing output to judging it. The same law applies, and the anti-pattern is more seductive because it looks like progress: hand the whole output to an LLM and ask "is this good?"&lt;/p&gt;

&lt;p&gt;That puts your evaluation's stochastic surface at one hundred percent. You have built a judge that cannot tell you when it is wrong, to assess a generator that cannot tell you when &lt;em&gt;it&lt;/em&gt; is wrong. Two unanchored samplers in a trench coat. It demos beautifully and rots quietly.&lt;/p&gt;

&lt;p&gt;The reliable structure is a hierarchy, ordered by how anchored each layer is, and you push as much weight as possible to the top:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quantifiable, to a deterministic metric.&lt;/strong&gt; Anything you can measure, you measure. Footprint dimensions. Block counts. Latency. Compile success. Test pass rate. Schema validity. Forbidden-element count equal to zero. This is the bedrock layer and it should carry the most weight the domain allows, because it has a stochastic surface of zero. It is right the same way every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Formalizable but not numeric, to a program check.&lt;/strong&gt; Things that are not a number but are still decidable: is the door actually passable (run a pathfinder), does the graph have the required structure, does the config satisfy this invariant. Still deterministic, still locatable, still zero surface. This is the procedural and symbolic layer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Irreducibly subjective, to a bounded model judge.&lt;/strong&gt; What is genuinely left, "is this &lt;em&gt;fun&lt;/em&gt;," "is this on-brand," "is this elegant", goes to an LLM. But scoped: a small, well-defined slice, with a rubric, calibrated against human ratings, and auditable after the fact. The model judges the residual, not the whole.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The number that matters is the &lt;strong&gt;share of your evaluation weight sitting in layers 1 and 2.&lt;/strong&gt; That is your evaluation's anchored fraction, and it is the inverse of its stochastic surface. A good eval system is one where the subjective model-judged slice is small and shrinking.&lt;/p&gt;

&lt;p&gt;And here is the part that makes evaluation more than a one-time setup. Every time you take a judgment that used to live in the model's head, "this looks too wide," and turn it into a rule, "footprint must equal the spec," you move it from layer 3 to layer 1 &lt;strong&gt;permanently&lt;/strong&gt;. It never goes back. Evaluation built this way is a &lt;em&gt;ratchet&lt;/em&gt;: a mechanism for steadily converting accumulated human judgment into deterministic checks, one notch at a time, each notch shrinking the surface and never releasing it. "Let the AI grade the AI" is the opposite, a surface stuck at one hundred percent with no mechanism to ever bring it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two sides are one move
&lt;/h2&gt;

&lt;p&gt;Step back and the generation ladder and the evaluation hierarchy are the same diagram viewed from two ends.&lt;/p&gt;

&lt;p&gt;On both sides you are drawing a line. On one side of the line is the deterministic part: retrieved examples, narrowed distributions, constraint solvers, metrics, program checks. On the other side is the irreducible stochastic residual: the novel shape, the genuinely subjective call. &lt;strong&gt;That line is the stochastic surface.&lt;/strong&gt; Generation and evaluation are just its two projections, one for producing, one for judging.&lt;/p&gt;

&lt;p&gt;Every system I keep writing about is an instance of drawing that line well. Agent memory fails when a model's hedge gets stored as a fact with no source to check it against; the fix anchors memory to a source of truth, shrinking the surface. The advisor strategy spends a cheap model on the bulk and reserves expensive, decisive compute for the few points that must be right; that is surface reduction in the cost dimension. Retrieval versus grep, plan-generate-solve-verify for game content, rules-first evaluation, all the same law: &lt;strong&gt;the probabilistic component proposes, and a deterministic component, as much of one as the problem allows, disposes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The standard
&lt;/h2&gt;

&lt;p&gt;The point of a law is to use it before you ship, not to explain the wreckage after. So here is the checklist I am going to run on the next probabilistic system I build, and the one after that. Five questions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What is the stochastic surface?&lt;/strong&gt; Name the exact fraction of the output whose correctness rests on the model alone, unretrieved and unchecked. If you cannot point to it, you do not understand the system yet. Stop here until you can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What did you anchor instead of invent?&lt;/strong&gt; For every generated part, did you retrieve a real example first (tier 1), narrow the distribution with fine-tuning where volume justifies it (tier 2), and reserve free generation for the residual only (tier 3)? Each part that skipped the ladder is surface you chose not to reduce, and you should be able to say why.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What is checkable, and is it checked?&lt;/strong&gt; Every predicate the output must satisfy that &lt;em&gt;can&lt;/em&gt; be verified deterministically, is it? Sizes, counts, schemas, invariants, passability. Anything checkable but unchecked is the worst category: a failure you could have caught for free and did not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Where does the model judge, and is that slice bounded?&lt;/strong&gt; In evaluation, what share of weight is deterministic (layers 1 and 2) versus model-judged (layer 3)? Is the judged slice small, rubric-bound, calibrated, and auditable? A universal LLM judge is a red flag, not a feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is the surface shrinking over time?&lt;/strong&gt; Is there a ratchet, a path by which recurring human judgments become permanent rules and the surface trends down with use? A system whose surface is fixed will not get more reliable no matter how long it runs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Score every component against these five and the weak points light up immediately. They are always the same shape: a part handed to the model that could have been retrieved, derived, or checked, and was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the standard stops
&lt;/h2&gt;

&lt;p&gt;A law worth trusting comes with its boundary, so here is mine. &lt;strong&gt;This standard governs correctness tasks, and only those.&lt;/strong&gt; A correctness task has a spec the output must satisfy: a size, a passable door, a compiling program, a routed circuit, a factual answer. There, a large stochastic surface is pure liability and shrinking it is the whole job.&lt;/p&gt;

&lt;p&gt;But some tasks have no spec, and for those the standard inverts. Style exploration. Brainstorming. A first rough draft meant to be thrown away. Concept art whose only requirement is "show me something I would not have thought of." There the stochastic surface &lt;em&gt;should&lt;/em&gt; be near one hundred percent, because there is no predicate to anchor to and constraints would only strangle the thing you wanted. Clamping a creative task with a verifier is the same category error as trusting a generator on a correctness task, run backwards.&lt;/p&gt;

&lt;p&gt;So the discipline underneath the discipline is telling the two apart. Ask of any output: is there a spec it must satisfy, or am I sampling for novelty? If there is a spec, the standard applies and you shrink the surface as far as the problem allows. If there is not, let the model run, and do not pretend a verifier was missing.&lt;/p&gt;

&lt;p&gt;The hard engineering was never the model. An adequate one is already here, and the next one will be better in ways that do not touch this. The hard engineering is drawing the line around it, and making that line as small as the problem allows.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the second piece in a pair. The first: &lt;a href="https://harrisonsec.com/blog/generative-ai-builds-shapes-not-games/" rel="noopener noreferrer"&gt;Generative AI Builds Shapes, Not Games&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Related, the same law at other layers: &lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt; and &lt;a href="https://harrisonsec.com/blog/agent-architecture-compute-allocation-advisor-strategy/" rel="noopener noreferrer"&gt;Agent Architecture Is a Compute Allocation Problem: The Advisor Strategy&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Generative AI Builds Shapes, Not Games: The Constraint Gap and the Architecture That Closes It</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Mon, 22 Jun 2026 23:40:52 +0000</pubDate>
      <link>https://dev.to/harrisonsec/generative-ai-builds-shapes-not-games-the-constraint-gap-and-the-architecture-that-closes-it-2e30</link>
      <guid>https://dev.to/harrisonsec/generative-ai-builds-shapes-not-games-the-constraint-gap-and-the-architecture-that-closes-it-2e30</guid>
      <description>&lt;p&gt;I sat down to benchmark a tool and ended up with a map of a wall.&lt;/p&gt;

&lt;p&gt;Higgsfield shipped a Minecraft "prompt-to-build" feature: type a prompt, get a structure in-world about a minute later. I ran eight building prompts through it, scored each one, and walked through the results. The point started as "how good is this tool." It ended somewhere more useful, because the &lt;em&gt;shape&lt;/em&gt; of the failures turned out to be a clean read on where generative AI hits a wall in game content, and why, and what the architecture that gets past it has to look like.&lt;/p&gt;

&lt;p&gt;The one-sentence version: generative models are &lt;strong&gt;plausibility engines&lt;/strong&gt;, and games need &lt;strong&gt;correctness engines&lt;/strong&gt;. Those are not the same machine, and you do not get one by scaling the other.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — An AI Minecraft builder produced recognizable single forms (a sphere, a tower, a castle gatehouse with a genuinely walkable gate) in about a minute, but dropped exact sizes, named materials, and door positions, failed to compose all three multi-object scenes I gave it, satisfied a "no lava" rule only vacuously (it places no fluids at all), and surfaced no signal for whether any output met the prompt. That pattern is the signature of what generative models are: samplers over a distribution of &lt;em&gt;forms&lt;/em&gt;. Game content demands three things a form-sampler structurally lacks: discrete constraint satisfaction, compositional structure, and functional correctness with verification. Scaling adds plausible shapes, not those three capabilities. The architecture that closes the gap separates the continuous from the discrete: a symbolic planner emits a scene graph and explicit constraints, generative models fill per-object shape, a solver places objects to satisfy the constraints, and a verifier checks the result and repairs failures. Plausibility from the generator; correctness from structure and verification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The evidence, briefly
&lt;/h2&gt;

&lt;p&gt;The full hands-on benchmark, per-prompt scores, and figures are in the &lt;a href="https://dev.to/blog/i-tested-higgsfield-minecraft-prompt-to-build/"&gt;companion findings post&lt;/a&gt;. The compressed version is enough to ground the argument.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked.&lt;/strong&gt; Single cohesive forms with a strong visual prior came out fast and recognizable. "A giant sphere" produced a clean voxel sphere. A watchtower read as a tower. A gatehouse with "two towers and a central gate players can walk through" came back as exactly that, in about a minute, and the gate was genuinely passable when I walked through it.&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%2Fxn10qgjf5h022ifiykns.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxn10qgjf5h022ifiykns.jpg" alt="Gatehouse: two towers, a central arch, a passable gate" width="800" height="517"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The strongest result. A castle gatehouse is one cohesive, canonical form with named sub-parts, and it generated fast and well.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn't.&lt;/strong&gt; The moment a prompt depended on discrete, checkable requirements, those requirements fell away. "A 15 by 15 block cottage using mostly wood and stone, entrance on the south side, inside walkable" came back as a doorless lumpy grey wall, far wider than 15×15, no wood, no way in.&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%2Fqipq98wzvoexu3lep0iy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqipq98wzvoexu3lep0iy.jpg" alt="The 15x15 cottage prompt: a doorless lumpy wall" width="800" height="517"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The most constrained prompt produced the worst result. Every discrete requirement, size, material, door, enclosure, failed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Multi-object scenes failed three different ways across three prompts: one hung with no output, one scattered into wildly inconsistent scale, one collapsed two tents and a campfire into a single teal mound. And the negative constraint ("do not use glass, lava, water, or redstone") was "honored" only because the builder never places fluids at all. A lava-colored band read in-game as solid &lt;code&gt;orange_concrete&lt;/code&gt; with no fluid present. The rule was satisfied vacuously, by palette limitation, not by following it.&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%2Fwapelgyv0kvtwllagh4u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwapelgyv0kvtwllagh4u.jpg" alt="F3 block check: the lava-colored band is solid orange_concrete, no fluid" width="800" height="517"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The negative constraint met vacuously: a color-matched solid block, not a parsed-and-honored "do not use."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two structural facts sit underneath all of it. The behavior is consistent with a &lt;strong&gt;mesh-generation plus voxelization pipeline&lt;/strong&gt;: produce one 3D mesh, voxelize it, color-map to a block palette, place it. (I did not decompile or trace it, so treat that as the most likely explanation, not confirmed.) And there was &lt;strong&gt;no validation signal&lt;/strong&gt; anywhere: nothing in the workflow indicated whether the output satisfied size, material, door, or function.&lt;/p&gt;

&lt;p&gt;Hold those two facts. They are the whole argument in miniature: one form, no structure, no check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens: plausibility is not correctness
&lt;/h2&gt;

&lt;p&gt;It is tempting to read the cottage as a bug, a model that needs more training. That misreads what the model is.&lt;/p&gt;

&lt;p&gt;A generative 3D model is a &lt;strong&gt;sampler over a learned distribution of forms&lt;/strong&gt;. Training teaches it the manifold of plausible shapes for a text condition; inference draws a point from it. This is the right machine for one job, "give me a plausible instance of X," and it is genuinely good at it. The sphere and the gatehouse are that job done well.&lt;/p&gt;

&lt;p&gt;Game content asks for a different job: not "a plausible instance" but "an instance that &lt;em&gt;satisfies these requirements&lt;/em&gt;." And the requirements games impose come in three flavors a form-sampler has no mechanism for.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Discrete constraints
&lt;/h3&gt;

&lt;p&gt;"15×15." "South-facing door." "No lava." These are discrete, symbolic predicates. They are either satisfied or not, and you can check which.&lt;/p&gt;

&lt;p&gt;A continuous sampler has no place to &lt;em&gt;put&lt;/em&gt; a discrete predicate. It can make outputs that are distributionally &lt;em&gt;consistent with the words&lt;/em&gt; "15 by 15", things that tend to be smallish and square-ish, but it cannot &lt;em&gt;satisfy the predicate&lt;/em&gt; "footprint equals 15×15" because nothing in the architecture represents that predicate as a thing to be satisfied and checked. This is the same root cause behind image models that botch exact finger counts and legible text: counts and letters are discrete, and a plausibility sampler approximates them instead of satisfying them. The cottage's missing door is not a training gap. It is a category error to expect a form-sampler to honor a positional predicate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compositional structure
&lt;/h3&gt;

&lt;p&gt;A single object is a form. A &lt;em&gt;scene&lt;/em&gt; is a graph: objects as nodes, spatial relations as edges. "Three houses along a path with a tree between each" is a structured arrangement, not a shape.&lt;/p&gt;

&lt;p&gt;A monolithic mesh generator has no node-and-edge representation to build that graph in. Asked for a scene, it has only one move available: hallucinate the entire arrangement as a single form and voxelize it. The three scene failures are three ways that move degrades, hang, scatter, collapse, but they share one cause: there is no scene graph, so there is no composition, only a blob that gestures at the elements. "It cannot do scenes" is too strong; "it has no structured representation in which a scene could be composed" is exact.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Functional correctness, and the missing verifier
&lt;/h3&gt;

&lt;p&gt;"Players can walk through the gate" is a &lt;em&gt;functional&lt;/em&gt; property. You cannot read it off the geometry by eye with confidence; you confirm it by testing, by trying to walk the path. When the gatehouse gate turned out passable, that was the shape prior paying off, not the system knowing or checking that the function held. There is no notion of function in a form-sampler, and, more tellingly, no loop that asks "did the output satisfy the ask?" after generating.&lt;/p&gt;

&lt;p&gt;That missing loop is the deepest part. Even a model that frequently lands constraints by luck is unreliable without a verifier, because nothing distinguishes the lucky output from the failed one. The workflow had no score, no self-check, no "this build is 14×16, not 15×15." Generation without verification cannot tell you it succeeded, which means it cannot be trusted even when it did.&lt;/p&gt;

&lt;h3&gt;
  
  
  The unifying diagnosis
&lt;/h3&gt;

&lt;p&gt;Stack the three together and the diagnosis is one line: &lt;strong&gt;generative models optimize plausibility; game content requires correctness, and correctness is discrete, compositional, and functional.&lt;/strong&gt; Plausibility lives on a continuous manifold. Correctness is symbolic, structured, and checkable. They are different mathematical objects, and one architecture is built for the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why scaling alone won't close it
&lt;/h2&gt;

&lt;p&gt;The reflex in 2026 is "the next model will fix this." For this gap, scaling the same architecture has little reason to close it and some reason not to. A different architecture might; a bigger form-sampler won't.&lt;/p&gt;

&lt;p&gt;More parameters and more data make the sampler draw &lt;em&gt;more plausible shapes, more faithfully&lt;/em&gt; from the form distribution. That is real progress on the axis the architecture already optimizes. It does not add a discrete constraint representation, because the training objective never asks the model to satisfy and check a predicate. It does not add a scene graph, because the output is still one mesh. It does not add a verifier, because verification is a separate computation the generator was never built to perform.&lt;/p&gt;

&lt;p&gt;You can see the shape of this in the parts of the benchmark that &lt;em&gt;did&lt;/em&gt; improve with the form prior, versus the parts that did not. The gatehouse, more canonical, came out better than the watchtower. Scaling pushes everything along &lt;em&gt;that&lt;/em&gt; axis: better, more canonical forms. The cottage's door does not live on that axis at all. No amount of "better sphere" becomes "satisfies 15×15 with a south door," any more than a sharper camera becomes a tape measure.&lt;/p&gt;

&lt;p&gt;The gap is architectural. Closing it means adding the missing architecture, not enlarging the existing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is bigger than Minecraft
&lt;/h2&gt;

&lt;p&gt;The Minecraft builder is a clean, cheap microcosm because Minecraft makes correctness &lt;em&gt;legible&lt;/em&gt;, you can literally F3 a block and read whether the constraint held. But the same wall stands wherever an output has to &lt;em&gt;satisfy a spec&lt;/em&gt; rather than merely &lt;em&gt;look right&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAD and mechanical design:&lt;/strong&gt; a part that looks like a bracket but is 2mm off the bolt pattern is scrap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture and floor plans:&lt;/strong&gt; a plausible-looking plan with a bedroom you can't reach is not a plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit and chip layout:&lt;/strong&gt; plausible is meaningless; it routes and meets timing, or it doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code generation:&lt;/strong&gt; "looks like correct code" is exactly the trap; it compiles and passes tests, or it doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level and quest design:&lt;/strong&gt; a level must be completable, not just atmospheric.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is the same split: &lt;em&gt;plausible&lt;/em&gt; (continuous, distributional, what the generator gives you) versus &lt;em&gt;correct&lt;/em&gt; (discrete, structured, functional, what the domain demands). Game building is a vivid instance because it bundles all three correctness flavors, dimensional, compositional, functional, into one minute-long generation you can inspect. The lesson generalizes to all of production-grade generative content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture that closes the gap
&lt;/h2&gt;

&lt;p&gt;If one model can't be plausibility engine and correctness engine at once, stop asking it to be. Split the pipeline so that the continuous and the discrete each go to the machine built for them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Plan — symbolic.&lt;/strong&gt; A planner (an LLM or a program synthesizer) turns the prompt into a &lt;em&gt;structured spec&lt;/em&gt;: a scene graph (objects and spatial relations) plus explicit constraints (sizes, materials, positions, forbidden sets, functional requirements like "this gate is a passable path"). This is the discrete representation the form-sampler lacks. "15×15, south door, wood and stone" becomes machine-checkable slots, not vibes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Generate — continuous.&lt;/strong&gt; Per-object generative models produce the &lt;em&gt;shapes&lt;/em&gt;, conditioned on the spec's slots. This is exactly where the generative prior earns its keep: plausible, varied, rich single forms. The sphere and the gatehouse show the generator is already good at this when it is asked only for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Place and solve — symbolic.&lt;/strong&gt; A constraint solver or procedural placement layer arranges the generated objects to satisfy the spatial, dimensional, and adjacency constraints. This is not new technology; it is the &lt;strong&gt;procedural-generation toolbox games have used for thirty years&lt;/strong&gt;, wave-function-collapse, shape grammars, constraint-based layout, now used to &lt;em&gt;arrange generative outputs&lt;/em&gt; instead of hand-authored tiles. Determinism and satisfiability are features here, not limitations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Verify and repair — the loop the benchmark had none of.&lt;/strong&gt; A checker evaluates the &lt;em&gt;checkable&lt;/em&gt; predicates against the spec: is the footprint 15×15? is the door passable? are there zero forbidden blocks? does the object count match? Failures route back to regeneration or local repair. This is the validation signal whose absence was the most telling thing in the whole session.&lt;/p&gt;

&lt;p&gt;Plausibility comes from step 2. Correctness comes from steps 1, 3, and 4. The generator stops being asked to do the job it can't and gets to do the job it's good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deeper pattern: trust terminates outside the generator
&lt;/h2&gt;

&lt;p&gt;This is the part that ties back to everything I've been writing about agents this year, and it's why the Minecraft toy matters more than a toy should.&lt;/p&gt;

&lt;p&gt;The recurring failure of probabilistic systems is treating the generator's output as ground truth. In &lt;a href="https://dev.to/blog/agent-memory-is-a-cache-coherence-problem/"&gt;agent memory&lt;/a&gt;, the failure mode is a model's "the user &lt;em&gt;could&lt;/em&gt; do X" getting stored and later read back as "the user &lt;em&gt;did&lt;/em&gt; X", a probabilistic hedge flattened into a fact, with no source to check it against. In the &lt;a href="https://dev.to/blog/agent-architecture-compute-allocation-advisor-strategy/"&gt;advisor strategy&lt;/a&gt;, the design that works puts a cheap model on the bulk of the work and reserves the expensive, decisive computation for the few points that actually need to be right.&lt;/p&gt;

&lt;p&gt;Generative 3D for games is the same principle in a different costume. The generator is the bulk path: cheap, fast, plausible, and &lt;em&gt;not to be trusted on its own&lt;/em&gt; for any property that has to be correct. Correctness has to terminate at something outside the generator, a spec, a solver, a verifier, the same way agent memory has to terminate at a source of truth and an agent loop has to terminate at a check. &lt;strong&gt;The probabilistic component proposes; a deterministic component disposes.&lt;/strong&gt; Systems that wire it that way are reliable. Systems that trust the sampler's output as final are plausible right up until they are confidently, unverifiably wrong, a doorless cottage that the workflow was perfectly happy to call done.&lt;/p&gt;

&lt;p&gt;The winners in AI content generation will not have the best single-shape model. An adequate generator is already here. They will have the best &lt;em&gt;structure and verification wrapped around&lt;/em&gt; the generator: the planner that emits a checkable spec, the solver that satisfies it, the verifier that proves it. That is where the engineering is, and it is the half the current monolithic tools skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Monolithic text-to-3D is a phase, not a destination.&lt;/strong&gt; The single-prompt-to-single-mesh tool is the generative-AI equivalent of an early language model asked to do arithmetic in its head. The field moves toward decomposed, plan-generate-verify pipelines for the same reason agents moved toward tool use and verification: the monolith is plausible and the pipeline is correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Procedural generation gets a second life, not a retirement.&lt;/strong&gt; PCG was always deterministic and constraint-satisfying and always limited in variety and richness. Generative models are the inverse. The synthesis, generative priors for per-object shape inside a procedural and constraint-solving frame, gives you both, and the verification loop makes it trustworthy. The 30-year toolbox is the missing half of generative AI for games, not its casualty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The benchmark is a snapshot of the monolithic era.&lt;/strong&gt; Re-run the same eight prompts against a plan-generate-solve-verify system and the cottage is 15×15 with a south door, because a solver placed it and a verifier checked it, not because a bigger model finally drew it. That is the test I'd want to see, and the architecture I'd bet on.&lt;/p&gt;

&lt;p&gt;Generative AI can build a shape. Building a game asks for correctness, and correctness is a different machine. The interesting work, in games and well beyond them, is in building the second machine around the first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Empirical companion (the hands-on benchmark, full scores and figures): &lt;a href="https://dev.to/blog/i-tested-higgsfield-minecraft-prompt-to-build/"&gt;I Tested Higgsfield's Minecraft "Prompt-to-Build"&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Related: &lt;a href="https://dev.to/blog/agent-memory-is-a-cache-coherence-problem/"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Related: &lt;a href="https://dev.to/blog/agent-architecture-compute-allocation-advisor-strategy/"&gt;Agent Architecture Is a Compute Allocation Problem: The Advisor Strategy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>gamedev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Tested Higgsfield's Minecraft Prompt-to-Build. It Generates Shapes, Not Scenes.</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Thu, 18 Jun 2026 20:03:42 +0000</pubDate>
      <link>https://dev.to/harrisonsec/i-tested-higgsfields-minecraft-prompt-to-build-it-generates-shapes-not-scenes-2b72</link>
      <guid>https://dev.to/harrisonsec/i-tested-higgsfields-minecraft-prompt-to-build-it-generates-shapes-not-scenes-2b72</guid>
      <description>&lt;p&gt;Higgsfield shipped a Minecraft "prompt-to-build" feature: a mod that drops a "Supercomputer" block into your world, takes a free-text prompt, and generates a structure in-world a minute later. I spent one session putting real building prompts through it to see what it actually does, not what the landing page says it does. Eight prompts, fixed screenshots, an in-world walkthrough, and a scoring rubric.&lt;/p&gt;

&lt;p&gt;The short version: it behaves like a &lt;strong&gt;single-cohesive-3D-form generator with strong canonical priors&lt;/strong&gt;, not an architecture or scene engine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — Higgsfield's in-world prompt-to-build produced recognizable single forms (a sphere, a tower, a castle gatehouse, including a functional walkable gate) in about a minute. But in my samples it dropped discrete constraints (exact size, specified materials, door position), failed to compose a coherent multi-object scene in all three scene prompts I tried, and exposed no validation signal for whether the output met the prompt. The behavior is consistent with a mesh-to-voxel pipeline: generate one shape, color-map it to blocks. Strong on shape, weak on constraints, composition, and function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How it appears to work (inferred from behavior)
&lt;/h2&gt;

&lt;p&gt;I did not decompile the mod or capture a network trace, so this is the most likely explanation, not a confirmed fact. The observed behavior is &lt;strong&gt;consistent with a mesh-generation plus voxelization pipeline&lt;/strong&gt;: a text prompt produces a 3D mesh in the cloud, which is then voxelized, mapped to a limited block palette, and placed in-world.&lt;/p&gt;

&lt;p&gt;If that model is right, it accounts for most of what I saw. A color or texture sampler would pick block &lt;em&gt;colors&lt;/em&gt; rather than &lt;em&gt;materials&lt;/em&gt;. A mesh encodes a &lt;em&gt;shape&lt;/em&gt;, not discrete numeric or positional constraints. And a layout of separate objects has no single form to generate from. One in-game block check supports it directly: a region that looked like lava read as &lt;code&gt;minecraft:orange_concrete&lt;/code&gt; with no fluid placed at all — a solid block chosen by color.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; Minecraft Java 1.21.1 + NeoForge 21.1.233 + the Higgsfield mod, creative mode, superflat world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow:&lt;/strong&gt; place the Higgsfield "Supercomputer" block, set &lt;code&gt;Type: Structure&lt;/code&gt;, enter the prompt, insert a blank Structure medium, Generate, then print the result in-world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; about 1.15 credits per build (the UI estimates 2). Failed jobs are not charged — the one prompt that timed out cost nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts:&lt;/strong&gt; eight total — a single object, a constrained object, a functional compound form, a negative-constraint prompt, and three multi-object scene prompts — plus one geometric-primitive control (a sphere) to check that outputs are genuinely prompt-conditioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring:&lt;/strong&gt; 1 to 5 per dimension (prompt adherence, constraint adherence, spatial/functional, editability, visual, reliability). Single rater, from fixed screenshots plus an in-world walkthrough.&lt;/li&gt;
&lt;/ul&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%2F3yd30uijlbszp9xbndfs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yd30uijlbszp9xbndfs.jpg" alt="The Higgsfield Supercomputer panel in Minecraft" width="800" height="496"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The in-world "Supercomputer" panel: a free-text prompt, &lt;code&gt;Type: Structure&lt;/code&gt;, a blank Structure medium, and a credit estimate. This is the entire interface.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Prompt class&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Adher&lt;/th&gt;
&lt;th&gt;Constr&lt;/th&gt;
&lt;th&gt;Spatial&lt;/th&gt;
&lt;th&gt;Edit&lt;/th&gt;
&lt;th&gt;Visual&lt;/th&gt;
&lt;th&gt;Reliab&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;single object — watchtower&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;td&gt;recognizable tower, wrong material/size&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;object + discrete constraints — 15×15 cottage&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;td&gt;doorless lumpy wall, unrecognizable&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ctrl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;geometric primitive — "a giant sphere"&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;td&gt;clean voxel sphere&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;multi-object scene — market&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8+ min&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;timeout, no output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;functional compound form — gatehouse&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;strong:&lt;/strong&gt; 2 towers + walkable gate&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;multi-object scene — houses + path + trees&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;td&gt;elements present, incoherent scale/layout&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;multi-object scene — campsite&lt;/td&gt;
&lt;td&gt;~2.5 min&lt;/td&gt;
&lt;td&gt;collapsed into one teal blob&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;negative constraint — 8×8 base, no glass/lava/water/redstone&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;td&gt;giant slab, not 8×8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Per-prompt findings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  P1 — single object: watchtower
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a small wooden watchtower, 10 blocks tall, with a ladder, a roof, and a viewing platform."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2F6sdn2iz9us5afzppq1c6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6sdn2iz9us5afzppq1c6.jpg" alt="Watchtower result" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It reads clearly as a tall tower, so the &lt;em&gt;shape&lt;/em&gt; prior comes through. The discrete constraints did not. "Wooden" came out as orange terracotta and honeycomb blocks — the output matched colors, not the material word "wood." "10 blocks tall" became 20-plus. The ladder, roof, and platform are vaguely suggested by geometry but are not functional Minecraft elements. The surface shows the voxelization artifacts you'd expect: eroded edges, speckled palette quantization, asymmetry.&lt;/p&gt;

&lt;h3&gt;
  
  
  P2 — object + discrete constraints: 15×15 cottage
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a 15 by 15 block cottage using mostly wood and stone. Entrance on the south side, inside walkable."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fqipq98wzvoexu3lep0iy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqipq98wzvoexu3lep0iy.jpg" alt="Cottage result" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most constrained prompt produced the worst result. It does not read as a cottage at all — a long lumpy grey wall with a red-orange top band, random holes, and stray noise blocks. Every discrete constraint failed: the footprint is far wider than 15×15, it's a wall rather than an enclosed cottage, there's no wood, and there's no door on any side (confirmed in world — you cannot enter it). This is the clearest example of the pattern in my samples: the more a prompt depends on discrete, checkable requirements, the less of it came through.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control — geometric primitive: "a giant sphere"
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A giant sphere."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2F14vqtws54zds6i5vjm9x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14vqtws54zds6i5vjm9x.jpg" alt="Sphere control" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran this to settle one question: are outputs actually driven by the prompt, or just canned blobs? The result is an unmistakable, clean voxel sphere with the classic concentric-ring stepping. That's strong evidence of prompt-conditioning, and with a fresh superflat world it rules out "it was already there." It also fits the pattern from the other direction: a clean geometric form came out clean. Output looked best when the prompt carried &lt;em&gt;no&lt;/em&gt; discrete semantic constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  P3 — multi-object scene: market (timeout)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a small village market: four stalls around a central well, with paths connecting them."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No screenshot, because there was no output. The job hung for 8-plus minutes and never produced a result, so I abandoned it (no credits charged). This is the first of three prompts that ask for &lt;em&gt;multiple independent objects in a spatial layout&lt;/em&gt; rather than a single connected form. This one simply hung.&lt;/p&gt;

&lt;h3&gt;
  
  
  P4 — functional compound form: gatehouse (best result)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a gatehouse with two towers and a central gate players can walk through."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fxn10qgjf5h022ifiykns.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxn10qgjf5h022ifiykns.jpg" alt="Gatehouse result" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The strongest output in the session, and an important counter-example. It reads clearly as a castle gatehouse: two flanking towers, a central arch, battlements. Done in about a minute. Crucially, the functional requirement — "players can walk through" — was honored: the central gate is genuinely passable (verified by walking through it in world). The material came out stone-like, which fits the canonical "castle" prior, but the prompt did not specify a material, so that's a visual/prior win, not constraint-following.&lt;/p&gt;

&lt;p&gt;P4 sharpens the conclusion. A gatehouse has named sub-parts (two towers plus a gate) yet still generated fast and well, because it is &lt;strong&gt;one cohesive, canonical form&lt;/strong&gt; — unlike the market scene of separate objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  S2 — multi-object scene: houses + path + trees
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Three small houses arranged in a row along a dirt path, with a tree between each house."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fd6uyyj1bey7fndbgcqjw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6uyyj1bey7fndbgcqjw.jpg" alt="S2 scene, aerial" width="800" height="517"&gt;&lt;/a&gt;&lt;br&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%2Fhr64irmco6j5iy0qwznb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhr64irmco6j5iy0qwznb.jpg" alt="S2 scene, ground" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done in about two minutes, so scene prompts do not always hang — the market timeout looks like a one-off. And it genuinely emitted the scene &lt;em&gt;elements&lt;/em&gt;: dirt paths, several separate small structures, trees. But the composition is incoherent: wildly inconsistent scale (one oversized house next to a miniature cluster), scattered placement, blobby objects. It read the scene as one mesh to voxelize, not as an arrangement of objects. So "it cannot do scenes" is too strong; "it does not compose scenes coherently" is accurate.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 — multi-object scene: campsite
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"A campsite with two tents, a central campfire, and logs around it to sit on."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fninb6w95wtc7emcbyg48.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fninb6w95wtc7emcbyg48.jpg" alt="S3 campsite" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The opposite failure mode from S2. Instead of scattering, it collapsed into a single mound of teal blocks (the two tents merged into one), with a small patch of orange blocks that loosely reads as a campfire. No distinguishable tents, no logs. Elements are hinted by &lt;em&gt;color&lt;/em&gt; (tents teal, fire orange) but the arrangement is gone.&lt;/p&gt;

&lt;p&gt;Across the three scene prompts, none produced a coherent, usable multi-object layout. They failed three different ways: hang, scatter, collapse. The scene &lt;em&gt;elements&lt;/em&gt; can show up; the &lt;em&gt;composition&lt;/em&gt; did not.&lt;/p&gt;

&lt;h3&gt;
  
  
  P5 — negative constraint: 8×8 base, forbidden materials
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a tiny 8 by 8 starter base. Do not use glass, lava, water, or redstone."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fv9hfwi6xdr3aej7jx479.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9hfwi6xdr3aej7jx479.jpg" alt="P5 starter base" width="800" height="517"&gt;&lt;/a&gt;&lt;br&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%2Fjj33yt9f4spsz9tsxfoo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjj33yt9f4spsz9tsxfoo.jpg" alt="P5 top-down, footprint far larger than 8×8" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two findings. First, the &lt;em&gt;positive&lt;/em&gt; constraint failed the same way P1 and P2 did: the output is a large purple-orange slab, nowhere near 8×8, and not a "base." Second, the &lt;em&gt;negative&lt;/em&gt; constraint. Regions that look like lava or water turned out, on an in-game F3 block check, to be solid color-matched blocks. The orange band reads as &lt;code&gt;minecraft:orange_concrete&lt;/code&gt; with targeted fluid empty.&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%2Fwapelgyv0kvtwllagh4u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwapelgyv0kvtwllagh4u.jpg" alt="In-game F3 block check on the lava-colored band" width="800" height="517"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;F3 debug, crosshair on the lava-colored band: &lt;code&gt;Targeted Block: minecraft:orange_concrete&lt;/code&gt;, &lt;code&gt;Targeted Fluid: minecraft:empty&lt;/code&gt; — a solid block chosen by color, not a forbidden material.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So no forbidden material was actually placed, but that's because the voxelizer's palette is solid colored blocks and it never places fluids or functional blocks, not because it parsed and honored "do not use." The negative constraint is met &lt;strong&gt;vacuously&lt;/strong&gt;, by palette limitation, not by rule-following.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's strong vs. weak
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Strong:&lt;/strong&gt; single cohesive 3D forms with a clear visual prior — sphere, tower, castle gatehouse. Fast (about a minute), recognizable, and it will render named sub-parts (two towers, a gate, battlements) and even a functional opening (a walkable gate). Material is sensible when the canonical form implies it (castle implies stone).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaker:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discrete constraints get dropped.&lt;/strong&gt; Exact dimensions ("15×15", "8×8"), specified materials ("wood and stone"), and positions ("door on the south side") did not come through. (P1, P2, P5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-object scene composition (n=3, none coherent).&lt;/strong&gt; Across three scene prompts, none produced a coherent, usable arrangement: one hung, one scattered into inconsistent-scale fragments, one collapsed into a single blob. The elements can appear; the composition did not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative constraints are not meaningfully enforced.&lt;/strong&gt; Regions that looked like lava or water were solid color-matched blocks with no fluid. Forbidden materials are avoided by palette limitation, not by following the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No validation signal.&lt;/strong&gt; The workflow surfaced no self-check or score for whether it met size, material, door, or function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Across eight prompts a consistent line emerges: single cohesive forms are handled well, while scenes of independent objects and discrete or negative constraints are not. That's exactly what you'd expect from a mesh-to-voxel pipeline that generates one shape and color-maps it to blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you're using it
&lt;/h2&gt;

&lt;p&gt;If you want a quick, recognizable hero object — a tower, a statue-ish form, a gatehouse — Higgsfield's prompt-to-build is genuinely useful and fast. Lean into canonical shapes and let it pick the material.&lt;/p&gt;

&lt;p&gt;If you need a build to &lt;em&gt;satisfy&lt;/em&gt; something — an exact footprint, a specific material, a door where you asked for one, a multi-building scene with sensible spatial relationships — it isn't there yet in my samples. The gap isn't shape quality; it's everything layered on top of shape: constraints, composition, function, and any signal that the output actually met the ask. Treat the output as a starting silhouette to edit, not a finished, spec-correct build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scene composition is n=3.&lt;/strong&gt; Enough to say it produced no coherent scene in any try, but still a small sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One trial per prompt.&lt;/strong&gt; Generation is stochastic; I did not sample variance, and a given prompt might do better on a re-roll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single rater&lt;/strong&gt;, scoring from screenshots plus a walkthrough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit-limited session&lt;/strong&gt; (started with 10 credits, about 1.15 per build), so two planned prompts went unrun.&lt;/li&gt;
&lt;li&gt;Figures are frames pulled from a daytime screen recording (the in-game screenshot key conflicted with macOS during the live session). The builds are unchanged; only lighting and clarity differ from the live run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest one-line takeaway: in this session Higgsfield's Minecraft prompt-to-build handled &lt;em&gt;shape&lt;/em&gt; well and &lt;em&gt;everything that makes a shape correct&lt;/em&gt; poorly. If you test it yourself, the fastest way to see the split is to run one canonical single form (a gatehouse) and one constrained one (a 15×15 cottage with a south door) back to back.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>machinelearning</category>
      <category>review</category>
    </item>
    <item>
      <title>Agent Architecture Is a Compute Allocation Problem: The Advisor Strategy, Cost-Curve Frame Recursed</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Tue, 16 Jun 2026 20:14:53 +0000</pubDate>
      <link>https://dev.to/harrisonsec/agent-architecture-is-a-compute-allocation-problem-the-advisor-strategy-cost-curve-frame-recursed-d34</link>
      <guid>https://dev.to/harrisonsec/agent-architecture-is-a-compute-allocation-problem-the-advisor-strategy-cost-curve-frame-recursed-d34</guid>
      <description>&lt;p&gt;In April 2026, Anthropic published a blog post called &lt;em&gt;"The advisor strategy: Give agents an intelligence boost"&lt;/em&gt;, naming a pattern they had been A/B-testing in production: a cheaper model runs the agent loop end-to-end, an expensive model is consulted only when the cheap one hits a decision it can't solve. They reported concrete numbers — Haiku + Opus advisor on BrowseComp at 41.2% (Haiku alone: 19.7%) at 15% of the cost of running Sonnet through the whole task.&lt;/p&gt;

&lt;p&gt;On May 18, 2026, Tobi Lutke (CEO of Shopify) tweeted about an autoresearch setup that did exactly this: Qwen 3.6 27B running locally on an RTX 6000, with a small "advisor extension" that periodically calls GPT-5.5 for direction. 13,000 impressions, 2,400 likes, dozens of replies from engineers reproducing the pattern or building open-source implementations within hours.&lt;/p&gt;

&lt;p&gt;Underneath both of those, Stanford HazyResearch's &lt;a href="https://arxiv.org/abs/2512.21720" rel="noopener noreferrer"&gt;Minions&lt;/a&gt; paper — published months earlier — had abstracted the same pattern into a compressor-predictor framework: a small local model distills raw context into compact text that a larger remote model then reasons over. They reported their Deep Research system recovering 99% of frontier-model accuracy at 26% of the API cost.&lt;/p&gt;

&lt;p&gt;Three independent threads converging on the same architecture in roughly the same six-month window. That convergence is the story.&lt;/p&gt;

&lt;p&gt;This post argues something specific about it: the advisor strategy isn't a new pattern invented for LLMs. It's the &lt;strong&gt;third recursion&lt;/strong&gt; of the cost-curve frame from earlier in this mini-series — the same idea that argued grep beats RAG for code retrieval, and that argued SQLite + FTS5 beats a vector DB for the symbol-graph storage that grep-replacement tools (CodeGraph) need. Applied at the model-orchestration layer, the frame produces the advisor strategy. The strategy is the architecture; the frame is why.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — Anthropic, Tobi Lutke, and HazyResearch independently shipped (or described) the same agent pattern in early 2026: a cheap model runs the loop, an expensive model is consulted only for decisions. The convergence is evidence the pattern is correct; the &lt;em&gt;reason&lt;/em&gt; it's correct is the cost-curve frame from this series' first post, applied at the model-choice layer instead of the retrieval-architecture layer. Piece B argued grep+loop beats RAG because build/maintain cost dominates per-query cost below a crossover. The advisor strategy argues the same shape for tokens: cheap-model executor cost dominates expensive-model advisor cost for the bulk of low-value operations (reading context, format conversion, retries), so expensive-model tokens should be spent only at high-value decision points. Same frame, third layer.&lt;/p&gt;

&lt;p&gt;The post does three things: (1) reports the three converging threads with what each contributed; (2) makes the cost-curve recursion argument explicitly — L1 retrieval, L2 storage, L3 model orchestration; (3) maps the gotchas the hype skips (data egress on handoff, eval difficulty, handoff-contract design as actual engineering, hardware realism). The mini-series concludes here, five posts in, with cost-curve frame as a meta-design law across three layers of agent architecture.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Three convergent threads, in the order they shipped
&lt;/h2&gt;

&lt;p&gt;The convergence matters more than any single thread. Each was independent; each shipped within a six-month window of the others; each describes the same architecture from a different vantage. That's how you know the pattern is real and not just one team's design preference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic's official advisor strategy (2026-04-09)
&lt;/h3&gt;

&lt;p&gt;The Anthropic engineering blog &lt;em&gt;"The advisor strategy: Give agents an intelligence boost"&lt;/em&gt; defines the pattern as a productized engineering primitive:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Sonnet or Haiku runs the task end-to-end as the executor... When the executor hits a decision it can't reasonably solve, it consults Opus for guidance as the advisor."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The advisor never calls tools or produces user-facing output, and only provides guidance to the executor."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reported empirical numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Cost (relative to Sonnet end-to-end)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet alone (no advisor)&lt;/td&gt;
&lt;td&gt;SWE-bench Multilingual&lt;/td&gt;
&lt;td&gt;(baseline)&lt;/td&gt;
&lt;td&gt;1.00×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet + Opus advisor&lt;/td&gt;
&lt;td&gt;SWE-bench Multilingual&lt;/td&gt;
&lt;td&gt;exceeds baseline&lt;/td&gt;
&lt;td&gt;0.88× (−11.9%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haiku alone&lt;/td&gt;
&lt;td&gt;BrowseComp&lt;/td&gt;
&lt;td&gt;19.7%&lt;/td&gt;
&lt;td&gt;(baseline)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haiku + Opus advisor&lt;/td&gt;
&lt;td&gt;BrowseComp&lt;/td&gt;
&lt;td&gt;41.2%&lt;/td&gt;
&lt;td&gt;0.15× of Sonnet-end-to-end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two observations on the numbers. First: the Sonnet + Opus combination outperforms Sonnet alone while &lt;em&gt;also&lt;/em&gt; being cheaper — that's not a one-axis trade, that's a Pareto improvement. Second: the Haiku + Opus combination doubles Haiku's standalone score while costing 15% of Sonnet's. That's the &lt;em&gt;compound&lt;/em&gt; gain — better and cheaper at the same time.&lt;/p&gt;

&lt;p&gt;A specific detail in the blog: the advisor's outputs are typically &lt;strong&gt;400–700 tokens&lt;/strong&gt; — a short plan, not a full solution. That's the design saying out loud what the cost curve implies — the advisor exists to redirect, not to do work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tobi Lutke's personal experiment (2026-05-18)
&lt;/h3&gt;

&lt;p&gt;Tobi Lutke (CEO of Shopify) posted on X:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I've had very good results running autoresearch with local qwen 3.6 26b model as long as I had a simple vibed pi 'advisor' extension that allowed it to periodically ask GPT 5.5 for ideas. I think this direction has a lot of merit."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tobi's setup is the open-source mirror of Anthropic's productized pattern, with two architectural variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locality:&lt;/strong&gt; the executor runs on his own hardware (Qwen 3.6 27B on an RTX 6000), not on Anthropic's API. Local-first by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontier model choice:&lt;/strong&gt; the advisor is GPT-5.5 (OpenAI), not Opus (Anthropic). The pattern is model-agnostic on the advisor side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hardware caveat is real and worth naming: RTX 6000 is professional-grade, not consumer hardware. 27B-dense models with autoresearch-length contexts aren't laptop workloads. The pattern is reproducible at the &lt;em&gt;architecture&lt;/em&gt; level on commodity infrastructure; the &lt;em&gt;specific&lt;/em&gt; setup Tobi shows takes real investment.&lt;/p&gt;

&lt;p&gt;Within hours of Tobi's tweet, developer Rob Zolkos published &lt;a href="https://github.com/robzolkos/pi-lifeline" rel="noopener noreferrer"&gt;&lt;code&gt;pi-lifeline&lt;/code&gt;&lt;/a&gt; — an open-source escalation extension explicitly inspired by the tweet, with reasonable defaults: at least 5 rounds before the first advisor call, automatic escalation after 3 consecutive failures, plateau-detection after 6 rounds, max 10 advisor calls per session, default advisor model GPT-5.5. That's &lt;em&gt;engineering&lt;/em&gt; of the handoff contract — not a one-line config — and we'll come back to it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stanford HazyResearch Minions (2025–2026 publication window)
&lt;/h3&gt;

&lt;p&gt;Linked from a reply on Tobi's tweet — Dan Biderman pointing at HazyResearch's &lt;a href="https://arxiv.org/abs/2512.21720" rel="noopener noreferrer"&gt;Minions paper&lt;/a&gt; (arXiv 2512.21720), which abstracts the pattern into a &lt;em&gt;compressor-predictor framework&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"smaller 'compressor' LMs (that can even run locally) distill raw context into compact text that is then consumed by larger 'predictor' LMs."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Minions paper's specific numerical contribution: in their Deep Research system, a local 3B-parameter compressor recovers 99% of frontier-model accuracy at 26% of the API cost. That's the &lt;strong&gt;academic version&lt;/strong&gt; of the same architecture, with empirical bounds.&lt;/p&gt;

&lt;p&gt;Three things HazyResearch's framing adds beyond Anthropic's product blog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The compressor doesn't have to be 27B&lt;/strong&gt; — even 3B works for context distillation, depending on the task. The lower the compressor can go, the more local you can run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost-recovery curve has a specific shape&lt;/strong&gt; — 99% accuracy at 26% cost isn't linear. It's the same Pareto improvement Anthropic reported in product form: better and cheaper, not just cheaper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The general framing is "compress then decide"&lt;/strong&gt; — a slightly broader frame than "executor + advisor" because it includes the case where the compressor runs once at the start and the predictor runs once at the end, with no escalation loop. The advisor strategy is a &lt;em&gt;streaming&lt;/em&gt; version of compress-then-decide where compression happens iteratively.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why three independent confirmations matter
&lt;/h3&gt;

&lt;p&gt;Each thread is from a different vantage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt;: product engineering. Owns the model, designed the workload, reports field metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tobi Lutke&lt;/strong&gt;: individual practitioner. Different model providers (Qwen + GPT-5.5), different hosting (local + cloud), different workload (autoresearch, not coding benchmarks). Reproduced the pattern without coordinating with Anthropic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HazyResearch&lt;/strong&gt;: academic research. Different framing (compressor-predictor), different time horizon (paper preceded Anthropic's blog), different cost-quality measurement methodology.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When three independent vantages produce the same architectural answer, the design is robust to who happens to be sponsoring the work. That's the convergence-as-evidence argument — &lt;em&gt;the pattern is real and not just downstream of one organization's preferences&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The interesting question now isn't whether the pattern works (the convergence proves it does). It's &lt;strong&gt;why&lt;/strong&gt; it works — and that question has a clean answer from earlier in this mini-series.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cost-curve recursion: same frame, third layer
&lt;/h2&gt;

&lt;p&gt;Piece B (the first post in this series) argued that LLM-driven code retrieval sits on a &lt;em&gt;cost curve&lt;/em&gt;: index-based approaches pay high build cost + super-linear maintain cost, tool-loop approaches pay per-query cost only. Below a crossover point — which sits well above most projects' size — tool-loops win. Above it, indexes pay back.&lt;/p&gt;

&lt;p&gt;That argument generalizes. Applied to other agent-architecture decisions, the same frame keeps producing the right call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — Retrieval architecture (Piece B)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Tool-loop (grep + LLM iteration)&lt;/th&gt;
&lt;th&gt;Index (vector RAG)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build cost&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;super-linear in repo size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintain cost&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;super-linear in churn × structural complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-query cost&lt;/td&gt;
&lt;td&gt;N tool-call round-trips&lt;/td&gt;
&lt;td&gt;one vector search + LLM reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win condition&lt;/td&gt;
&lt;td&gt;Below crossover&lt;/td&gt;
&lt;td&gt;Above crossover&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conclusion: for most repos, build/maintain cost dominates per-query savings, so tool-loop wins. Anthropic chose grep+Glob+Read for Claude Code, not an index.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — Index storage (C2, the first-principles read of CodeGraph)
&lt;/h3&gt;

&lt;p&gt;When you &lt;em&gt;do&lt;/em&gt; cross the curve and need an index — CodeGraph's territory — the next decision is which storage layer to use.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;FTS5 + SQLite (CodeGraph)&lt;/th&gt;
&lt;th&gt;Vector DB (Chroma / Pinecone)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build cost&lt;/td&gt;
&lt;td&gt;linear in source size, parse-only&lt;/td&gt;
&lt;td&gt;super-linear (chunk + embed every file)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintain cost&lt;/td&gt;
&lt;td&gt;low (file watcher + incremental parse)&lt;/td&gt;
&lt;td&gt;super-linear (re-embed on change, handle cross-chunk refs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-query cost&lt;/td&gt;
&lt;td&gt;exact lookup, sub-millisecond&lt;/td&gt;
&lt;td&gt;ANN search + rerank + LLM call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win condition&lt;/td&gt;
&lt;td&gt;Exact-lookup workload&lt;/td&gt;
&lt;td&gt;Semantic-similarity workload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CodeGraph's queries are exact lookups (find symbol X, trace A→B, callers of Y), so FTS5 wins. Same frame as Layer 1: pay only the costs your workload demands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3 — Model orchestration (this post — the advisor strategy)
&lt;/h3&gt;

&lt;p&gt;Apply the same frame to token allocation across models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cheap-only (Haiku alone, Qwen alone)&lt;/th&gt;
&lt;th&gt;Expensive-only (Sonnet/Opus end-to-end)&lt;/th&gt;
&lt;th&gt;Executor + Advisor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-token cost on bulk operations&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;low (cheap executor handles 90%+ of tokens)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-token cost on key decisions&lt;/td&gt;
&lt;td&gt;low (but quality suffers)&lt;/td&gt;
&lt;td&gt;high (and quality matches)&lt;/td&gt;
&lt;td&gt;high (advisor only for decision tokens, ~400–700 tokens per call)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate task cost&lt;/td&gt;
&lt;td&gt;low if quality holds&lt;/td&gt;
&lt;td&gt;high regardless&lt;/td&gt;
&lt;td&gt;low (most tokens are cheap; decision tokens compound from the expensive model's quality)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate task quality&lt;/td&gt;
&lt;td&gt;depends on whether decisions are within cheap model's capability&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;high (cheap executor + expensive decisions ≈ expensive end-to-end, sometimes better)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win condition&lt;/td&gt;
&lt;td&gt;Tasks where cheap model alone is adequate&lt;/td&gt;
&lt;td&gt;Tasks where any decision could be critical&lt;/td&gt;
&lt;td&gt;Tasks where most operations are routine but some decisions are hard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most agent tasks fit the last column. The advisor strategy wins for the same structural reason grep+loop wins at Layer 1: &lt;strong&gt;the cost of the "bulk" operations dominates the cost of the "decision" operations, so the architecture should put the cheap tool on the bulk path and reserve the expensive tool for the decision path.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost-curve as a meta-design frame
&lt;/h3&gt;

&lt;p&gt;Stating the generalization explicitly: &lt;strong&gt;whenever an architecture has a "many low-value operations + few high-value operations" structure, applying expensive tools uniformly across both pays the high cost for the low-value operations too. The right design separates the two paths and uses cheap-but-good-enough tools on the bulk path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the design rule the cost-curve frame produces at every layer it's been applied to in this series. It's not specific to LLMs — &lt;em&gt;the database community calls this "use the cheapest index that satisfies the query class"; the systems community calls this "tiered storage"; the chip design community calls this "the memory hierarchy".&lt;/em&gt; The LLM-engineering version is the advisor strategy plus its retrieval-architecture cousins.&lt;/p&gt;

&lt;p&gt;This is the meta-design law the five posts in this series argue for. The argument's strength comes from the convergence — three independent recursions of the same frame producing the right architecture each time. That's not coincidence; it's the frame doing its job.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this validates from Piece B's source-code analysis
&lt;/h2&gt;

&lt;p&gt;Piece B's analysis of Claude Code's source code reported a specific finding: the Explore subagent runs on Haiku for non-ant builds (external users), not on Sonnet or Opus. The reasoning section of Piece B observed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Explore runs on Haiku for external users. Not the main reasoning model. Exploration is a cheap-tokens job — there's no creative reasoning happening, just iterate-and-filter — and Anthropic uses a fast, small, cheap model for it. The main agent gets the expensive model when it gets the summary back. This is the staffing analogue: junior associate does the deposition review, senior partner reads the brief."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the advisor strategy, visible directly in Claude Code's source. Piece B analyzed the mechanism rather than the branding, so it didn't use Anthropic's later label — but it's the same architecture. The point isn't priority over the announcement; it's that the pattern was already running in shipped code, observable by anyone reading the source rather than waiting for a launch post to name it.&lt;/p&gt;

&lt;p&gt;There's a useful takeaway here for reading any AI engineering work: &lt;strong&gt;the source code is ahead of the blog posts&lt;/strong&gt;. The blog post explains and packages what's been running in production. Reading the source is one of the cheapest ways to see where the foundational labs are betting, because the explainer post usually describes what was already shipping in the code months earlier.&lt;/p&gt;

&lt;p&gt;The advisor strategy is one of three patterns Piece B's source-code reading surfaced in this category. The other two are worth flagging because they suggest the &lt;em&gt;next&lt;/em&gt; blog posts to expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Fork-subagent architecture&lt;/strong&gt; (visible behind the &lt;code&gt;isForkSubagentEnabled()&lt;/code&gt; flag) — a different model-orchestration shape where the cheap and expensive halves &lt;em&gt;share&lt;/em&gt; a context (and prompt cache) rather than separating cleanly. If Anthropic productizes this, expect a blog post titled something like "Fork: shared-context model collaboration" in the next 1–3 quarters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;tengu_amber_stoat&lt;/code&gt; GrowthBook flag&lt;/strong&gt; — gating Explore vs. no-Explore as a deeper architectural test. If Anthropic concludes the cheap-executor-as-separate-subagent pattern doesn't pay off, the next blog post is about &lt;em&gt;why&lt;/em&gt; the advisor strategy works in some shapes and not others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general point: &lt;strong&gt;reading the source code and observing the patterns lets you write the analysis before the productized name arrives.&lt;/strong&gt; When the name does arrive, your analysis is what frames it. This is the time-shift advantage that source-leaning engineering writing has over pure-press-release-paraphrase content. It's why this series' posts have been holding up under fresh data — the frame was built from the same source the announcements describe, so new announcements tend to confirm it rather than surprise it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gotchas the hype skips
&lt;/h2&gt;

&lt;p&gt;The convergence between Tobi, Anthropic, and HazyResearch is real and the pattern is solid. But there are four gotchas the hype reliably skips that any production implementation has to address.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Data egress on handoff
&lt;/h3&gt;

&lt;p&gt;The local-first appeal of Tobi's setup (executor runs on your own GPU) hides a subtle leak. &lt;strong&gt;Every time the executor escalates to the cloud-hosted advisor, some subset of the executor's context goes to the advisor's hosting environment.&lt;/strong&gt; What gets sent is the executor's choice; once it's sent, it's no longer local.&lt;/p&gt;

&lt;p&gt;Commenter &lt;code&gt;@DarshanSays&lt;/code&gt; on Tobi's tweet flagged this explicitly: &lt;em&gt;"local + remote advisor mode quietly creates a data egress channel."&lt;/em&gt; The pattern gives you &lt;em&gt;cost control&lt;/em&gt; and &lt;em&gt;partial privacy&lt;/em&gt; — most of your raw data stays local — but not &lt;em&gt;full privacy&lt;/em&gt;. For workloads on sensitive data (security tooling, healthcare records, internal source code), the advisor's contract is now an exfiltration vector if it's poorly designed.&lt;/p&gt;

&lt;p&gt;The mitigations are real engineering, not config:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redaction layer&lt;/strong&gt; between executor and advisor — strip identifiers, replace specific names with placeholders, summarize before sending&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-off contract documentation&lt;/strong&gt; — explicit specification of what gets sent and what's excluded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; — every advisor call is logged with what was sent, so it's reviewable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Eval is structurally harder than it looks
&lt;/h3&gt;

&lt;p&gt;The benchmark numbers Anthropic and HazyResearch reported are real but represent specific tasks. &lt;strong&gt;For your task, you don't know whether the advisor strategy pays off without measuring on your workload.&lt;/strong&gt; And measuring is harder than for a single-model agent because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The executor's failure modes and the advisor's failure modes interact — bad escalation can make the advisor strategy worse than executor-alone&lt;/li&gt;
&lt;li&gt;The right escalation policy is task-dependent — too eager wastes advisor cost, too reluctant leaves executor stuck&lt;/li&gt;
&lt;li&gt;Quality differences from advisor strategy show up not just in pass/fail but in answer &lt;em&gt;completeness&lt;/em&gt; (similar to the Q4 refactor-impact analysis in C1's benchmark) and &lt;em&gt;modal status&lt;/em&gt; (is the answer correctly hedged vs. confidently wrong? — see Piece A's modality-flattening discussion)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A serious eval setup for the advisor strategy needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Baseline: executor alone, expensive model alone, advisor-strategy variant — three arms, not two&lt;/li&gt;
&lt;li&gt;Multiple escalation policies (eager / moderate / conservative) tested separately&lt;/li&gt;
&lt;li&gt;Both correctness &lt;em&gt;and&lt;/em&gt; completeness scoring, not just pass/fail&lt;/li&gt;
&lt;li&gt;Statistical reporting (variance across runs, not just averages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is more work than benchmarking a single model. It's the kind of thing teams skip because the "single number" benchmarks already look good — but the single numbers can hide that the &lt;em&gt;policy&lt;/em&gt; matters more than the &lt;em&gt;configuration&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hand-off contract design is real engineering
&lt;/h3&gt;

&lt;p&gt;The advisor strategy's "magic" is the executor calling the advisor at the right moments with the right context and getting back actionable guidance. Every clause in that sentence hides an engineering decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When to escalate&lt;/strong&gt; — after N consecutive failures? When confidence (measured how?) drops below threshold? After K rounds of no progress?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What context to send&lt;/strong&gt; — the full executor working state? A compressed summary? The recent N actions and outcomes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to format the advisor's response&lt;/strong&gt; — free-form text? Structured JSON? Action recommendations vs. analysis?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How the executor integrates the advice&lt;/strong&gt; — adopt verbatim? Treat as a hint? Use to seed the next attempt?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pi-lifeline's defaults (5 rounds before first advisor call, 3 consecutive failures auto-escalates, plateau-detection at 6 rounds, max 10 advisor calls per session) are one set of choices. They're reasonable but not universal. &lt;strong&gt;The right choices depend on the task; getting them wrong destroys the strategy's value even when the underlying models are good.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Hardware realism
&lt;/h3&gt;

&lt;p&gt;Tobi runs Qwen 3.6 27B on an RTX 6000 (NVIDIA professional-grade). The published benchmarks from Anthropic and HazyResearch use specific model versions and infrastructure. &lt;strong&gt;The architecture is reproducible on commodity infrastructure; the specific results are not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For practitioners considering the pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local executor (Qwen-class 27B+ dense model with long-context autoresearch loads) realistically needs RTX 6000 or A100-class hardware. Consumer cards (RTX 4090, RTX 5090) work for shorter contexts but throughput drops on long sessions.&lt;/li&gt;
&lt;li&gt;Quantized GGUF versions (e.g., Unsloth's quantizations) help with VRAM but not throughput — same hardware needed for the same wall-clock latency&lt;/li&gt;
&lt;li&gt;Hybrid cloud-first executor (Haiku/Sonnet on Anthropic API) avoids the hardware question but loses the local-data-leaves-only-on-escalation property&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The realistic deployment shape depends on what you're trying to optimize. Cost-only: cheap cloud executor + expensive cloud advisor. Privacy-first: local executor + cloud advisor with redaction. Speed-first: cloud executor with low advisor latency. The advisor-strategy &lt;em&gt;architecture&lt;/em&gt; is the constant; the &lt;em&gt;implementation&lt;/em&gt; varies by which axis dominates your requirements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering implementation: where to actually start
&lt;/h2&gt;

&lt;p&gt;If you're considering the advisor strategy on a real workload, the cheapest first step is to &lt;strong&gt;measure your single-model agent's token distribution&lt;/strong&gt; — what percentage of tokens go to context reading vs. format conversion vs. actual reasoning. If 70%+ of tokens go to bulk operations, the advisor strategy has a big payoff potential. If the distribution is flatter, payoff is smaller and the engineering overhead may not be worth it.&lt;/p&gt;

&lt;p&gt;Once you've decided to try the pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick an executor model&lt;/strong&gt; — start with the cheapest model that can complete &lt;em&gt;most&lt;/em&gt; of your tasks reliably. For coding agents, Haiku is the obvious starting point; for autoresearch, Qwen 3.6 27B (or whatever local 27B-class model fits your hardware).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick an advisor model&lt;/strong&gt; — Opus for Anthropic-stack workloads, GPT-5.5 for OpenAI-stack, whichever frontier model you trust on the task class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design escalation triggers&lt;/strong&gt; — start with pi-lifeline's defaults as baseline, tune based on observed executor failure patterns. The right number depends on your task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design the hand-off contract&lt;/strong&gt; — what context goes to the advisor, what format the advisor returns. Start minimal (recent N actions + current goal), expand if advisor quality is poor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement redaction&lt;/strong&gt; — if your data is sensitive, the redaction layer is non-negotiable. If not, you can skip it for v0 but document the egress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure&lt;/strong&gt; — three-arm eval (executor alone, advisor strategy, expensive alone), correctness + completeness scoring, variance across runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate on the policy, not the models&lt;/strong&gt; — when the strategy underperforms, the fix is usually in escalation timing or hand-off content, not in swapping models.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pattern works. The engineering around it determines whether it works &lt;em&gt;for you&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing — the mini-series, completed
&lt;/h2&gt;

&lt;p&gt;This is the fifth and final post in a series on agent retrieval, memory, and orchestration architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem&lt;/a&gt;&lt;/em&gt; (2026-05-25) — Layer 1: retrieval architecture. Why grep+loop beats RAG for code, and why the cost curve says so.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;&lt;/em&gt; (2026-05-28) — the cache-coherence frame for lossy auto-capture AI memory tools, with the modality-flattening failure mode mapped out.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/i-tested-codegraph-on-hono-benchmark/" rel="noopener noreferrer"&gt;I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.&lt;/a&gt;&lt;/em&gt; (2026-06-01) — empirical: when the cost curve &lt;em&gt;is&lt;/em&gt; crossed (mid-size repo, architectural questions, static-typed language), CodeGraph crosses it cheaply.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/codegraph-architecture-first-principles-llm-retrieval/" rel="noopener noreferrer"&gt;Agent Retrieval Above the Crossover: A First-Principles Read of CodeGraph&lt;/a&gt;&lt;/em&gt; (2026-06-08) — Layer 2: index storage. Why SQLite + FTS5 beats vector DBs for the symbol-graph workload, and where CodeGraph's abstraction leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post&lt;/strong&gt; (2026-06-15) — Layer 3: model orchestration. The advisor strategy as the third recursion of the cost-curve frame, validated by Anthropic's product, Tobi Lutke's experiment, and HazyResearch's academic version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Read as one argument&lt;/strong&gt;: the same cost-curve frame applies at three layers of agent architecture. At each layer, the correct design separates "bulk operations" from "decision operations" and pays only the cost each operation class requires. The five posts are five different applications of one frame, each cross-checked against fresh data as the productized announcements landed — the frame held up because it was built from the same shipped source those announcements describe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read as a toolkit&lt;/strong&gt;: if you're designing or evaluating agent architectures, the question to ask at every layer is the same. &lt;em&gt;What's the cost distribution of operations at this layer? Is there a "bulk vs. decision" split? Can the bulk path use a cheaper tool? Does the expensive tool only need to be on the decision path?&lt;/em&gt; Apply at retrieval (grep vs. RAG), storage (FTS5 vs. vector), model orchestration (executor vs. advisor). The next layer the frame will apply to is plausibly memory consolidation (cheap distillation vs. expensive synthesis) — that's a future post topic if the pattern shows up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on L2 iterating fast&lt;/strong&gt; — between when this mini-series started (B published 2026-05-25) and when D publishes (2026-06-15), the LLM-symbol-graph layer kept moving: CodeGraph shipped point releases, and more tools in the same class are arriving. They all hit the same six conditions for a viable LLM-symbol-graph that the framework predicted; where they differ is inside the &lt;em&gt;ranking&lt;/em&gt; layer — how each one orders the symbols a query surfaces (keyword + heuristics, graph-walk propagation, embedding re-rank, whatever comes next). The six conditions are about &lt;em&gt;what's required for an LLM-symbol-graph to exist at all&lt;/em&gt;; the ranking algorithm is the secondary design space &lt;em&gt;within&lt;/em&gt; the framework, and it's where the next tool will try to win. The empirical read of CodeGraph on a repo its team didn't choose is in the &lt;a href="https://harrisonsec.com/blog/i-tested-codegraph-on-hono-benchmark/" rel="noopener noreferrer"&gt;companion benchmark post&lt;/a&gt;; the first-principles architectural read is in the &lt;a href="https://harrisonsec.com/blog/codegraph-architecture-first-principles-llm-retrieval/" rel="noopener noreferrer"&gt;companion Lab post&lt;/a&gt;. L2 keeps iterating; the framework is what's stable.&lt;/p&gt;

&lt;p&gt;Three threads converged on the advisor strategy because the cost-curve frame produced it independently each time. The frame is the durable insight; the architecture is the frame instantiated at one layer. Reading the source code, watching the productization, and modeling the convergence each contribute to the same picture.&lt;/p&gt;

&lt;p&gt;If you build agents and are paying frontier-model rates for tokens that don't need them, the advisor strategy is the practical fix. If you read agents and want a frame for evaluating what comes next, the cost-curve recursion is the lens. The series ends here, five posts and three layers in.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Companion piece 1: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem: Why Claude Code Doesn't Use RAG&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Companion piece 2: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Empirical pair (Operator track): *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/i-tested-codegraph-on-hono-benchmark/" rel="noopener noreferrer"&gt;I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;First-principles companion: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/codegraph-architecture-first-principles-llm-retrieval/" rel="noopener noreferrer"&gt;Agent Retrieval Above the Crossover: A First-Principles Read of CodeGraph&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Background: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/consistency-scenarios-and-approaches-production/" rel="noopener noreferrer"&gt;Consistency in Distributed Systems: Scenarios, Trade-offs, and What Actually Works&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Anthropic advisor strategy blog (2026-04-09): "The advisor strategy: Give agents an intelligence boost"&lt;/em&gt;&lt;br&gt;
&lt;em&gt;HazyResearch Minions paper: &lt;a href="https://arxiv.org/abs/2512.21720" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2512.21720&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;pi-lifeline (open-source escalation extension inspired by Tobi Lutke): &lt;a href="https://github.com/robzolkos/pi-lifeline" rel="noopener noreferrer"&gt;https://github.com/robzolkos/pi-lifeline&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Agent Retrieval Above the Crossover: A First-Principles Read of CodeGraph</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Mon, 08 Jun 2026 19:49:41 +0000</pubDate>
      <link>https://dev.to/harrisonsec/agent-retrieval-above-the-crossover-a-first-principles-read-of-codegraph-1gd7</link>
      <guid>https://dev.to/harrisonsec/agent-retrieval-above-the-crossover-a-first-principles-read-of-codegraph-1gd7</guid>
      <description>&lt;p&gt;The prior post in this series, &lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem&lt;/a&gt;&lt;/em&gt;, argued that a viable LLM-symbol-graph would need to satisfy six specific conditions — and that no existing tool had hit all six. The post went live on 2026-05-25; seven days earlier, &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; had hit GitHub trending with exactly those six properties satisfied.&lt;/p&gt;

&lt;p&gt;That's the easy version of the update: framework predicted it, someone shipped it, here's the existence proof. The companion piece (&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/i-tested-codegraph-on-hono-benchmark/" rel="noopener noreferrer"&gt;I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.&lt;/a&gt;&lt;/em&gt;) handles the empirical half — 40 verified-connected runs, a decision matrix, the install-or-not call. Short version of that post: the tool-call savings reproduce on an independent repo (−55%), the &lt;strong&gt;cost&lt;/strong&gt; savings from the vendor benchmark don't (+7% at Hono's size). Fewer steps, not fewer dollars, until your repo is big enough.&lt;/p&gt;

&lt;p&gt;This post is the harder version of the update.&lt;/p&gt;

&lt;p&gt;The interesting question isn't whether CodeGraph works. The interesting question is &lt;strong&gt;why are its specific architectural choices right, and where does the abstraction inevitably leak?&lt;/strong&gt; Answering it gives you the lens for evaluating the next CodeGraph-class tool that ships — and there will be many — without redoing the benchmark each time.&lt;/p&gt;

&lt;p&gt;To answer it concretely rather than abstractly, I read CodeGraph against its own artifact: the SQLite database it writes to &lt;code&gt;.codegraph/codegraph.db&lt;/code&gt;. Every structural claim below is checked against the index it actually built for Hono (CodeGraph v0.9.7: 362 files, 4,128 nodes, 8,225 edges, a 7.4 MB database). The schema turns out to be the clearest statement of the architecture the tool's README never makes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — CodeGraph's architecture is right for three reasons that aren't obvious from the feature list, and all three are visible in its SQLite schema. (1) &lt;strong&gt;The AST extraction boundary&lt;/strong&gt;: tree-sitter takes what &lt;em&gt;syntax&lt;/em&gt; tells you (4,128 nodes across 13 kinds, 8,225 edges across 7 kinds) and leaves the rest to the LLM. The boundary is literal — references syntax can't resolve go into an &lt;code&gt;unresolved_refs&lt;/code&gt; table instead of becoming fake edges. (2) &lt;strong&gt;SQLite + FTS5, not a vector DB&lt;/strong&gt;: the index is plain relational tables plus a full-text table over symbol names. Zero embedding columns. The queries are exact lookups that B-tree indexes answer in log time; vector search would be solving a harder problem the workload never asks. This is the prior post's cost curve, recursed onto the index tool itself. (3) &lt;strong&gt;The abstraction leaks where syntax diverges from runtime semantics&lt;/strong&gt; — macros, metaprogramming, codegen, JIT binding. CodeGraph tags its few guessed edges with a &lt;code&gt;heuristic&lt;/code&gt; provenance flag (7 of 8,225 on Hono), which is honest; but what tree-sitter can't see at all gets no edge and no flag. Knowing that boundary is what separates a tool you trust from one you cargo-cult.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why this is a first-principles question, not a tool review
&lt;/h2&gt;

&lt;p&gt;Most coverage of CodeGraph reads like &lt;em&gt;"19k stars in a week, here's the install script."&lt;/em&gt; That's news; it isn't analysis. The same coverage will get written for every CodeGraph-class tool that ships in the next 18 months, because the pattern — tree-sitter + local index + MCP server + an instruction snippet that routes the agent to it — is now demonstrated and the ingredients are well known.&lt;/p&gt;

&lt;p&gt;The durable question isn't &lt;em&gt;"is CodeGraph good?"&lt;/em&gt; It's &lt;em&gt;"what makes this class of tool architecturally correct, and how do I evaluate the next one?"&lt;/em&gt; That's what a first-principles read produces. The benchmark in the companion post is one data point; this post is the lens for reading all future data points in the same space.&lt;/p&gt;

&lt;p&gt;If you're deciding on CodeGraph specifically, read the companion. If you're thinking about LLM retrieval as a discipline — or about to bet on, or build, a similar tool — read this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap: the six conditions, in 30 seconds
&lt;/h2&gt;

&lt;p&gt;The prior post argued any viable LLM-symbol-graph needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No-compile parsing&lt;/strong&gt; — cold start in seconds, not minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language portability&lt;/strong&gt; — one binary for many languages, not one server per stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-shaped API&lt;/strong&gt; — flat, recordy output the model can digest, not nested LSP hierarchies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broad enough coverage&lt;/strong&gt; — code-as-structure plus a text-search fallback for everything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live update without reindex&lt;/strong&gt; — file-watcher-driven, no manual rebuild&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-config install&lt;/strong&gt; — single binary, configures the agent automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;CodeGraph hits all six (the field-by-field mapping is near the end of this post). Taking the mapping as established, the interesting move is to ask: &lt;strong&gt;of the design choices CodeGraph made to hit those six, which were forced and which could have gone the other way?&lt;/strong&gt; The forced ones are good engineering. The ones that &lt;em&gt;weren't&lt;/em&gt; forced — where CodeGraph picked something specific over a live alternative — are where the architecture is making a claim, and where the first-principles content lives.&lt;/p&gt;

&lt;p&gt;Three of those choices repay a deep read. The other three (file-watcher update, single-binary distribution, instruction-snippet routing) are well-understood in their own fields — OS notifications, package distribution, prompt engineering — and amount to "do the obvious thing well." The three that don't are the three this post takes apart, each against the actual index.&lt;/p&gt;




&lt;h2&gt;
  
  
  Section 1 — The AST extraction boundary: an information-theoretic case
&lt;/h2&gt;

&lt;p&gt;CodeGraph parses source with tree-sitter and extracts a specific subset of the syntax into its graph. You don't have to take the README's word for what that subset is — it's enumerable straight out of the &lt;code&gt;nodes&lt;/code&gt; and &lt;code&gt;edges&lt;/code&gt; tables. On Hono, the 4,128 nodes break down like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node kind&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Node kind&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;import&lt;/td&gt;
&lt;td&gt;1,033&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;method&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;route&lt;/td&gt;
&lt;td&gt;873&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;interface&lt;/td&gt;
&lt;td&gt;187&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;function&lt;/td&gt;
&lt;td&gt;569&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;property&lt;/td&gt;
&lt;td&gt;169&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;362&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;class&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;type_alias&lt;/td&gt;
&lt;td&gt;358&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;enum_member&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;constant&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;variable / enum&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the 8,225 edges, which are the actually interesting part:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Edge kind&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;What it encodes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;contains&lt;/td&gt;
&lt;td&gt;2,874&lt;/td&gt;
&lt;td&gt;structural nesting (file → class → method)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;calls&lt;/td&gt;
&lt;td&gt;2,230&lt;/td&gt;
&lt;td&gt;the call graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;references&lt;/td&gt;
&lt;td&gt;1,955&lt;/td&gt;
&lt;td&gt;symbol used here, defined there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;imports&lt;/td&gt;
&lt;td&gt;1,033&lt;/td&gt;
&lt;td&gt;module dependency edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;instantiates&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;new X()&lt;/code&gt; sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;extends&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;class/interface inheritance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;implements&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;interface implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now look at what is &lt;strong&gt;not&lt;/strong&gt; there. No "type" nodes. No generic-instantiation edges. No data-flow edges. No "this dynamic dispatch resolves to that concrete method" edges. CodeGraph extracts &lt;code&gt;calls&lt;/code&gt;, &lt;code&gt;references&lt;/code&gt;, &lt;code&gt;extends&lt;/code&gt;, &lt;code&gt;implements&lt;/code&gt; — relationships that are &lt;em&gt;locally apparent in the syntax&lt;/em&gt; — and stops. The first-order reading of this is "because tree-sitter doesn't resolve types." True, but circular. The deeper reading is &lt;strong&gt;why this division of labor is correct for an LLM consumer.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The information-theoretic case
&lt;/h3&gt;

&lt;p&gt;A type-checker (or full LSP) does work the LLM cannot easily redo: resolving &lt;code&gt;obj.method()&lt;/code&gt; to the actual method given the static type of &lt;code&gt;obj&lt;/code&gt;, propagating types through generics, walking an inheritance chain to the method actually invoked. That requires the full compilation context — every transitive import, every type definition, every generic instantiation. The cost is high (a build environment, slow cold start, breaks when the build breaks) and the benefit is narrow: precise semantic resolution that's genuinely hard to reconstruct from local context.&lt;/p&gt;

&lt;p&gt;A syntactic extractor does &lt;em&gt;different&lt;/em&gt; work. It makes the structure of the source queryable, but only the structure that's locally apparent: "function &lt;code&gt;dispatch&lt;/code&gt; defined at &lt;code&gt;hono-base.ts:406&lt;/code&gt;, calls &lt;code&gt;match&lt;/code&gt; here, imported from &lt;code&gt;router&lt;/code&gt;." No types, no generics, no runtime binding — but no compilation either.&lt;/p&gt;

&lt;p&gt;The information-theoretic question is: &lt;strong&gt;given an LLM that's good at semantic reasoning but bad at structural enumeration, what's the right split between what the index provides and what the LLM provides?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CodeGraph's answer: hand the LLM the &lt;em&gt;structural skeleton&lt;/em&gt; — what calls what, what's defined where, what imports what — because enumerating that across thousands of files is exactly the part the LLM is bad at and would burn dozens of tool calls trying to do by hand. Leave the &lt;em&gt;semantic resolution&lt;/em&gt; — what does this call actually invoke at runtime under dynamic dispatch? — to the LLM, because the LLM is reasonable at that once the relevant code is in its context, and baking a type resolver into the index would multiply the build cost for a recovery the LLM mostly doesn't need.&lt;/p&gt;

&lt;p&gt;The clean way to see this boundary is the &lt;code&gt;contains&lt;/code&gt; + &lt;code&gt;calls&lt;/code&gt; + &lt;code&gt;references&lt;/code&gt; edges (7,059 of the 8,225) versus the things that &lt;em&gt;aren't&lt;/em&gt; edges at all. When the companion benchmark's Q1 asked how a &lt;code&gt;GET /users/:id&lt;/code&gt; request reaches its handler, what CodeGraph gave Claude Code was the call chain — &lt;code&gt;fetch&lt;/code&gt; → &lt;code&gt;dispatch&lt;/code&gt; → &lt;code&gt;match&lt;/code&gt; — as graph edges. What it did &lt;em&gt;not&lt;/em&gt; give, and didn't try to, was which concrete &lt;code&gt;match&lt;/code&gt; implementation runs given Hono's &lt;code&gt;SmartRouter&lt;/code&gt; picking &lt;code&gt;RegExpRouter&lt;/code&gt; at runtime. The graph located the players; the LLM read the three files and resolved the dispatch. That's the split working as designed: enumeration from the index, resolution from the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The boundary is a literal table
&lt;/h3&gt;

&lt;p&gt;Here's the detail that turns this from an argument into an observation. When tree-sitter sees a reference it cannot statically resolve to a definition, CodeGraph does not invent an edge. It writes a row to a separate &lt;code&gt;unresolved_refs&lt;/code&gt; table — name, location, the node it came from, no target. The schema has a first-class place for &lt;em&gt;"I saw a use here, I could not prove what it binds to."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On Hono, &lt;code&gt;unresolved_refs&lt;/code&gt; has zero rows — and, as it turns out, so did every other repo I indexed to check it (Section 3 has that result, and it's not the one I expected). The empty table isn't the interesting part; the table &lt;em&gt;existing&lt;/em&gt; is the architecture stating its own boundary. A tool that faked those edges — guessed a target to make the graph look complete — would be lying to the LLM in exactly the way that produces confident wrong answers. CodeGraph's choice to record the unresolved reference &lt;em&gt;as unresolved&lt;/em&gt; is the same discipline a good cache has when it marks an entry stale instead of serving it: the honest move is to represent "don't know," not to paper over it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters beyond CodeGraph
&lt;/h3&gt;

&lt;p&gt;This boundary — &lt;em&gt;syntactic graph for the index, semantic reasoning for the LLM&lt;/em&gt; — is the line the next generation of LLM-coding tools will either hold or violate. The violations are predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too far toward semantics in the index&lt;/strong&gt;: a tool that tries to be a full LSP-plus for the LLM. High build cost, slow cold start, fragile on broken builds, marginal benefit because the LLM can do that resolution from local context anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too far toward raw text in the index&lt;/strong&gt;: a tool that's just "grep with nicer indexing" — fast and broad, but it doesn't hand the LLM the structural skeleton it actually needs. That's the position grep+loop already occupies; an index there adds little.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CodeGraph sits in the middle, and that position is right for current LLM capability. As models get better at semantic resolution the line will move one way; as tool-loop iteration gets cheaper it will move the other. But the &lt;em&gt;principle&lt;/em&gt; — that there's an information-theoretic boundary worth picking, and that picking it requires modeling the LLM's real strengths and weaknesses — is the durable take. The right way to evaluate any new LLM-retrieval tool starts here: what does it choose to extract, what does it leave for the LLM, and is that split calibrated for what an LLM is actually good at?&lt;/p&gt;




&lt;h2&gt;
  
  
  Section 2 — SQLite + FTS5 vs vector DB: the cost curve, recursed
&lt;/h2&gt;

&lt;p&gt;CodeGraph stores its symbol graph in a local SQLite database. Not Chroma. Not Pinecone. Not Weaviate. Not Qdrant. The full table list from Hono's index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nodes              edges              files
unresolved_refs    nodes_fts          schema_versions
project_metadata   (+ FTS5 shadow tables: nodes_fts_data/idx/docsize/config)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nodes&lt;/code&gt; and &lt;code&gt;edges&lt;/code&gt; are plain relational tables. &lt;code&gt;nodes_fts&lt;/code&gt; is an FTS5 virtual table. Searching the whole schema for an embedding column, a vector type, a float array — anything ANN-shaped — returns nothing. The only &lt;code&gt;BLOB&lt;/code&gt; columns are FTS5's own internal segment storage (&lt;code&gt;nodes_fts_data&lt;/code&gt;), not vectors. &lt;strong&gt;There are no embeddings in CodeGraph.&lt;/strong&gt; That's not an omission; it's the architecture, and it's the same call the prior post made one level down.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cost-curve frame, recursed
&lt;/h3&gt;

&lt;p&gt;The prior post argued vector RAG over a codebase pays a build cost (chunk + embed every file), a maintain cost (re-embed on change, reconcile cross-chunk references), and a low per-query cost (ANN search + rerank) — and that for most repos this loses to grep+loop's &lt;em&gt;(zero build, zero maintain, per-query round-trips)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Apply that exact frame to CodeGraph's own storage. If CodeGraph used a vector DB for its symbols, it would pay: embed every symbol's signature and body on index; re-embed on every file save (the file-watcher would have to fire embedding calls); ANN search per query. That's the same curve the prior post argued &lt;em&gt;against&lt;/em&gt; — and CodeGraph's &lt;em&gt;workload&lt;/em&gt; doesn't justify it, because the queries it serves are &lt;strong&gt;exact lookups&lt;/strong&gt;, not similarity searches. The schema proves the queries are exact by the indexes it builds for them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Find symbol &lt;code&gt;getUserById&lt;/code&gt;"&lt;/strong&gt; → &lt;code&gt;idx_nodes_name&lt;/code&gt;, and &lt;code&gt;idx_nodes_lower_name&lt;/code&gt; for case-insensitive matches. A B-tree probe, microseconds. FTS5 (&lt;code&gt;nodes_fts&lt;/code&gt; over &lt;code&gt;name, qualified_name, docstring, signature&lt;/code&gt;) handles the fuzzier "name contains" variants. No similarity math.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Who calls &lt;code&gt;Context.set&lt;/code&gt;?"&lt;/strong&gt; → &lt;code&gt;idx_edges_target_kind&lt;/code&gt; (a reverse-edge index on &lt;code&gt;(target, kind)&lt;/code&gt;). Reverse adjacency lookup, deterministic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What does &lt;code&gt;dispatch&lt;/code&gt; call?"&lt;/strong&gt; → &lt;code&gt;idx_edges_source_kind&lt;/code&gt; (the forward-edge index). Forward adjacency, deterministic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Trace &lt;code&gt;fetch&lt;/code&gt; → &lt;code&gt;db_query&lt;/code&gt;"&lt;/strong&gt; → repeated forward-edge hops over those same indexed edges. Graph traversal on stored adjacency, no vectors anywhere in the loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those forward and reverse edge indexes are the whole ballgame. Callers and callees — the queries a code-intelligence tool exists to answer — are a single indexed adjacency lookup in each direction. Vector search cannot do this &lt;em&gt;better&lt;/em&gt;; it can only do it &lt;em&gt;fuzzier and more expensively&lt;/em&gt;, because "who calls this function" has an exact answer that an approximate-nearest-neighbor index would blur.&lt;/p&gt;

&lt;p&gt;The only queries where vector search genuinely helps are semantic ones with no symbol to anchor on — &lt;em&gt;"show me the code that does authentication."&lt;/em&gt; CodeGraph doesn't serve those. The LLM does, by issuing a sequence of exact structural queries and reasoning across the results. The division is the same one from Section 1: the index answers the exact-lookup questions deterministically; the LLM answers the fuzzy-intent questions by orchestrating exact lookups. Neither needs an embedding.&lt;/p&gt;

&lt;h3&gt;
  
  
  The recursion as a design principle
&lt;/h3&gt;

&lt;p&gt;What's elegant — and worth surfacing for its own sake — is that &lt;strong&gt;CodeGraph's storage choice is consistent with the retrieval philosophy from the prior post, one level up.&lt;/strong&gt; Both arguments are the same sentence: &lt;em&gt;exact-lookup workloads should use exact-lookup tools; approximation overhead is paid only where approximation pays back.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If CodeGraph had reached for Chroma over FTS5, it would have violated its own retrieval philosophy — paying embedding and ANN cost to answer questions that have exact answers. That it didn't, that the designer recognized the symbol-graph workload is exact-lookup-shaped and picked the cheapest exact-lookup storage available, is what makes the architecture coherent across layers rather than just locally clever.&lt;/p&gt;

&lt;p&gt;The next tool in this class will face the same fork, and most will reach for a vector DB by default, because "AI tooling = vector store" is the reflex. CodeGraph's choice is the corrective: ask what your &lt;em&gt;workload&lt;/em&gt; needs, not what the category's fashion suggests. That's the cost-curve frame functioning as a meta-design tool — every time you add a layer to an LLM stack, ask which side of the curve the new layer's workload sits on, and pick storage and algorithm from the answer, not the trend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Section 3 — Where CodeGraph's abstraction leaks
&lt;/h2&gt;

&lt;p&gt;Every index lies a little. The question is &lt;em&gt;where&lt;/em&gt; it lies and &lt;em&gt;whether you can tell when it does.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CodeGraph's graph is built from syntactic extraction, so &lt;strong&gt;anywhere the runtime semantics diverge from the syntactic structure, the graph is incomplete in a way that's hard to detect from the index alone.&lt;/strong&gt; The leak isn't a bug; it's the abstraction working as designed, at a layer that structurally cannot see certain phenomena. There's a tell for it in the schema, and there's a part the schema can't tell you about — and the difference between those two is the whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest part: the provenance column
&lt;/h3&gt;

&lt;p&gt;CodeGraph stamps every edge with a &lt;code&gt;provenance&lt;/code&gt; value. On Hono, 8,218 of the 8,225 edges have empty provenance — meaning &lt;em&gt;direct from the syntax tree&lt;/em&gt; — and exactly &lt;strong&gt;7&lt;/strong&gt; carry the value &lt;code&gt;heuristic&lt;/code&gt;. Those seven are edges CodeGraph's framework adapters &lt;em&gt;inferred&lt;/em&gt; from a recognized pattern rather than read off the AST: route registrations, framework binding conventions, the handful of cases where a tool that "supports Hono / Flask / Spring" pattern-matches a known idiom and synthesizes an edge the raw syntax doesn't spell out.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;heuristic&lt;/code&gt; tag is the architecture being honest. It is, in the vocabulary of the memory post in this series, an &lt;em&gt;arrow&lt;/em&gt;: every edge points back to &lt;em&gt;how it was derived&lt;/em&gt;, and the seven guessed edges are flagged as guesses. A consumer that cared could treat heuristic edges with less trust than syntactic ones. That's good cache hygiene — the index records the confidence of its own entries instead of presenting all of them as equally certain.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part the schema can't tell you about
&lt;/h3&gt;

&lt;p&gt;Here's the catch, and it's the one that matters: &lt;strong&gt;the provenance column only flags edges that exist.&lt;/strong&gt; The dangerous leak isn't a guessed edge that's marked as guessed. It's the edge that &lt;em&gt;should&lt;/em&gt; exist and isn't there at all — because the relationship lives in a layer tree-sitter cannot see, so there's nothing to extract, nothing to tag, and nothing to warn you. The four big zones where this happens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Macro-heavy code.&lt;/strong&gt; In Rust, &lt;code&gt;vec![1, 2, 3]&lt;/code&gt; expands at compile time into a call sequence the AST never contains; the graph shows a &lt;code&gt;vec!&lt;/code&gt; invocation, not the &lt;code&gt;Vec::new()&lt;/code&gt; + &lt;code&gt;push()&lt;/code&gt; that actually runs. For procedural macros (&lt;code&gt;#[derive(...)]&lt;/code&gt;, attribute macros), the &lt;em&gt;generated implementation&lt;/em&gt; is what executes and CodeGraph can't see into it without running the compiler — which would forfeit the no-compile property that Section 1 showed is the whole point. Same shape in C/C++ preprocessor-heavy code, Lisp/Clojure macros, Elixir compile-time metaprogramming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metaprogramming.&lt;/strong&gt; Python decorators routinely rewrite functions: &lt;code&gt;@dataclass&lt;/code&gt; synthesizes &lt;code&gt;__init__&lt;/code&gt;/&lt;code&gt;__repr__&lt;/code&gt;/&lt;code&gt;__eq__&lt;/code&gt;; &lt;code&gt;@app.route("/users")&lt;/code&gt; registers a handler with a router. Tree-sitter sees the decorator and the function as adjacent syntax, not the synthesis or the registration. CodeGraph's framework adapters catch the &lt;em&gt;common&lt;/em&gt; cases — and that's literally what the 7 heuristic edges on Hono are — but arbitrary user-defined decorators that mutate behavior are invisible. Ruby &lt;code&gt;method_missing&lt;/code&gt;, Python &lt;code&gt;__getattr__&lt;/code&gt;, Java reflection: same story. The graph confidently returns "no callers" for a method invoked entirely through reflection, and the LLM, trusting structured output, may hand you a confidently wrong blast radius.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated code.&lt;/strong&gt; Protobuf, GraphQL codegen, OpenAPI clients, ORM model generation (Prisma, SQLAlchemy declarative), JSX/Svelte compilation — the code the runtime executes isn't the code in source control. It lives in &lt;code&gt;build/&lt;/code&gt;, &lt;code&gt;dist/&lt;/code&gt;, &lt;code&gt;.cache/&lt;/code&gt;, places &lt;code&gt;.gitignore&lt;/code&gt; excludes. CodeGraph indexes what's checked in; the generated layer is outside the boundary. "Who implements &lt;code&gt;UserService&lt;/code&gt;?" returns the hand-written interface, not the generated stub that implements it on the wire. Any source-only index has this; it's worth naming because it interacts badly with the user's instinct that &lt;em&gt;an "AST graph" must be complete.&lt;/em&gt; It's complete over the source it indexed — and the generated layer was never in that source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JIT and runtime-registered bindings.&lt;/strong&gt; DI containers (Spring, Guice, Dagger, ASP.NET service collection), &lt;code&gt;FastAPI Depends&lt;/code&gt;, plugin systems with runtime registration, and — the one the companion benchmark hit directly — &lt;strong&gt;middleware chains composed at app startup.&lt;/strong&gt; Hono's &lt;code&gt;app.use(...)&lt;/code&gt; builds the middleware array at runtime; tree-sitter sees the &lt;code&gt;use&lt;/code&gt; call sites and the handler as unconnected syntax. When the benchmark's Q2 asked Claude Code to trace the middleware call stack, what &lt;code&gt;codegraph_trace&lt;/code&gt; could return was the &lt;em&gt;syntactic&lt;/em&gt; call chain through &lt;code&gt;compose()&lt;/code&gt; — accurate as far as it goes, and genuinely fewer steps than baseline grep — but the actual runtime ordering of middlewares is assembled by &lt;code&gt;app.use&lt;/code&gt; calls scattered across the app, which the graph doesn't compose. The trace looked authoritative and was structurally real; it just wasn't the runtime composition, and only someone who knew the leak zone would know to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  The empirical check, and the null result that sharpens it
&lt;/h3&gt;

&lt;p&gt;I expected &lt;code&gt;unresolved_refs&lt;/code&gt; to be where this shows up — index a macro-heavy repo, watch the table fill. So I indexed three to test it: Hono (TypeScript), &lt;a href="https://github.com/pallets/click" rel="noopener noreferrer"&gt;click&lt;/a&gt; (Python, decorator-heavy), and &lt;a href="https://github.com/ron-rs/ron" rel="noopener noreferrer"&gt;ron&lt;/a&gt; (a Rust crate leaning on &lt;code&gt;derive&lt;/code&gt; macros and serde). &lt;code&gt;unresolved_refs&lt;/code&gt; was &lt;strong&gt;zero on all three&lt;/strong&gt;; heuristic edges were 7, 0, and 0. The null result &lt;em&gt;is&lt;/em&gt; the finding. A &lt;code&gt;#[derive(Serialize)]&lt;/code&gt; impl never appears as an unresolved reference, because nothing in the source ever wrote a reference to it to leave dangling — the impl only exists after macro expansion. &lt;code&gt;codegraph callers serialize&lt;/code&gt; on ron returns its seven real syntactic callers and silently omits whatever the derive generates, with no flag and no empty-table warning, because from the index's point of view nothing is missing. And that is the trap. &lt;strong&gt;An empty &lt;code&gt;unresolved_refs&lt;/code&gt; table reads like a clean bill of health, but on derive-heavy or reflection-heavy code it means the opposite of "everything resolved" — it means the thing that didn't resolve never left a trace to flag.&lt;/strong&gt; The table catches references it can't resolve; it cannot catch code that was never written down to reference. That's the leak that costs you: not the guess that gets flagged, but the absence that looks exactly like completeness. It's the same failure shape as the memory post's &lt;em&gt;"could" stored as "did"&lt;/em&gt; — the dangerous error is always the one that wears the face of a correct answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why mapping the leaks matters
&lt;/h3&gt;

&lt;p&gt;A tool you trust everywhere is a tool you stop checking. &lt;strong&gt;The four zones above are where the LLM, trusting the graph, gives you confidently wrong answers&lt;/strong&gt; — and those are the failures that cost real engineering time, because the answer &lt;em&gt;looks right&lt;/em&gt; and you have no reason to second-guess it.&lt;/p&gt;

&lt;p&gt;The practical rule is small. Inside one of these zones — heavy macros, reflection/DI, codegen-heavy projects, runtime-composed bindings — CodeGraph is still a fine &lt;em&gt;starting point&lt;/em&gt;, but the LLM's answer has to be cross-checked against the runtime, not against the graph. Outside them — most application code in most languages, which is most of what most people query — the graph is enough. The provenance column tells you which &lt;em&gt;present&lt;/em&gt; edges were guessed; nothing tells you which &lt;em&gt;absent&lt;/em&gt; edges were never seen. That asymmetry is the actual trust boundary, and it's the thing to internalize before you wire any syntactic index into an agent's decision loop. Joel Spolsky named this pattern for compilers and frameworks twenty years ago — every abstraction leaks, and you pay for the leak precisely when you've forgotten the abstraction is there. CodeGraph is the latest data point in a very old series.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mapping CodeGraph to the six conditions
&lt;/h2&gt;

&lt;p&gt;Field-by-field, how CodeGraph hits each condition from &lt;em&gt;Agent Retrieval Is a Cost Curve Problem&lt;/em&gt;. Compressed; the prior post defines the conditions, the companion post applies them empirically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No-compile parsing.&lt;/strong&gt; Tree-sitter parses source into an AST with no build invocation, no dependency resolution, no language environment. On Hono, 362 files indexed to 4,128 nodes and 8,225 edges in &lt;strong&gt;1.7 seconds&lt;/strong&gt;; the published 7-repo benchmark reports first-index on the order of minutes for VS Code-scale (~30k files), all subsequent updates incremental. LSP needs &lt;code&gt;tsc&lt;/code&gt; / &lt;code&gt;cargo check&lt;/code&gt; / &lt;code&gt;mvn&lt;/code&gt;; CodeGraph reads raw text. &lt;strong&gt;Met.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Language portability.&lt;/strong&gt; ~19 languages via tree-sitter, plus framework adapters for route-aware extraction (Hono's 873 &lt;code&gt;route&lt;/code&gt; nodes come from one of them). One binary, no per-language server. &lt;strong&gt;Met.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. LLM-shaped API.&lt;/strong&gt; Here the scaffold version of this post — and a lot of the casual coverage — gets a fact wrong worth correcting precisely. The CLI exposes a dozen commands (&lt;code&gt;query&lt;/code&gt;, &lt;code&gt;callers&lt;/code&gt;, &lt;code&gt;callees&lt;/code&gt;, &lt;code&gt;impact&lt;/code&gt;, &lt;code&gt;affected&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;, …). But the &lt;strong&gt;MCP server exposes exactly five tools&lt;/strong&gt; to the agent: &lt;code&gt;codegraph_search&lt;/code&gt; (locations only), &lt;code&gt;codegraph_context&lt;/code&gt; (described in its own schema as the &lt;em&gt;PRIMARY tool, call FIRST for any how-does-X-work question&lt;/em&gt;), &lt;code&gt;codegraph_node&lt;/code&gt; (one symbol plus its callers/callees trail), &lt;code&gt;codegraph_explore&lt;/code&gt; (several related symbols in one capped call), and &lt;code&gt;codegraph_trace&lt;/code&gt; (the call path between two symbols). The narrowing is the design: the human CLI gets &lt;code&gt;impact&lt;/code&gt; and &lt;code&gt;affected&lt;/code&gt; as separate verbs; the agent gets a &lt;em&gt;context-first&lt;/em&gt; surface of five flat tools, each returning &lt;code&gt;{symbol, file, line, snippet, related[]}&lt;/code&gt;-shaped records, with the instruction snippet steering it to &lt;code&gt;codegraph_context&lt;/code&gt; before anything else. Ten tools would be worse for an LLM than five; CodeGraph picked five. &lt;strong&gt;Met, deliberately.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Coverage breadth.&lt;/strong&gt; Symbol graph for structure; FTS5 over &lt;code&gt;name, qualified_name, docstring, signature&lt;/code&gt; for text-fallback; Claude Code's native Grep stays enabled for everything outside the index. &lt;strong&gt;Partially met — the correct partial.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Live update without reindex.&lt;/strong&gt; OS file-watcher with a short debounce; a save re-parses the touched file and re-resolves dependents' import edges. &lt;strong&gt;Met.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Zero-config install.&lt;/strong&gt; Single binary, one-line install, auto-detects the agent, writes the MCP config and the instruction snippet, then &lt;code&gt;codegraph init -i&lt;/code&gt; builds the index. Ten minutes from curiosity to working under ~1,000 files. &lt;strong&gt;Met.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Six for six. The architecture the prior post argued was theoretically right but practically missing exists, in production, with a working installer — and, read against its own schema, the choices hold up under inspection rather than just on the landing page.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this says about LLM retrieval as a discipline
&lt;/h2&gt;

&lt;p&gt;Three things, in increasing order of generality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The right LLM-index design is not a copy of human-IDE design.&lt;/strong&gt; Sourcegraph and LSP were built for a human reading one precise answer; an LLM reads many cheap rounds and reasons across them. The architectures should differ, and CodeGraph's choices — tree-sitter not LSP, five flat MCP tools not a nested LSP API, FTS5 not vectors — are evidence of someone designing for the actual consumer instead of porting an existing design. The framework predicts the design space, and the interesting variation between the tools that will fill it is not in the six conditions (those are now the table stakes) but in the &lt;em&gt;ranking&lt;/em&gt; layer — how each one orders the symbols a query surfaces. That's where the next tool will try to win, and where the next benchmark should aim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The cost-curve frame is recursive.&lt;/strong&gt; It applies to every layer of an LLM stack, including the tools that wrap the LLM. CodeGraph's FTS5-not-Chroma choice is the same shape as the original grep-not-RAG choice. Use it as a meta-design tool: at every layer, ask which side of the curve the workload sits on, and let that pick the storage and the algorithm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The abstraction leaks are the trust boundary — and trust, in the end, has to terminate at the source.&lt;/strong&gt; This is the thread that runs through the whole series. CodeGraph's graph is a &lt;em&gt;derived view&lt;/em&gt; of the source: a cache. Its &lt;code&gt;heuristic&lt;/code&gt; provenance tags and its &lt;code&gt;unresolved_refs&lt;/code&gt; table are the parts where it keeps an arrow back to that source and is honest about what it did and didn't see. But a syntactic graph is still a lossy projection of a running program, and the leak zones are exactly where the projection drops information that only exists at runtime. The discipline that falls out of this is the same one the retrieval post and the memory post arrived at from their own directions: &lt;strong&gt;a derived artifact is trustworthy only where you can check it against the source that produced it.&lt;/strong&gt; CodeGraph is fast and exact in the 80% of code where syntax determines structure, and quietly incomplete in the 20% where it doesn't — and the only way to stay out of the failure modes is to remember the graph is a cache and keep the real code, the actual runtime, as the thing that wins every conflict.&lt;/p&gt;

&lt;p&gt;The bigger move CodeGraph represents — &lt;em&gt;third-party MCP tools filling the retrieval gap the foundation model's main agent doesn't fill&lt;/em&gt; — is the ecosystem direction the feature-flag analysis in the prior post suggested Anthropic is hedging toward. Whether Anthropic eventually builds tree-sitter symbol-graph functionality natively or leaves it to the CodeGraph-class ecosystem is a product call. The technical case for "let MCP fill it" is strong: the design space is still settling, and locking one approach into Claude Code spends option value the ecosystem is currently pricing for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing — the mini-series arc
&lt;/h2&gt;

&lt;p&gt;This is the third of a three-part Lab series on Claude Code's retrieval and memory architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem&lt;/a&gt;&lt;/em&gt; (2026-05-25) — why grep+loop, not RAG, for most projects&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;&lt;/em&gt; (2026-05-28) — why hand-curated Markdown, not lossy vector recall, for cross-session memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post&lt;/strong&gt; (2026-06-08) — what lives above the cost-curve crossover: CodeGraph as the architecturally coherent symbol-graph companion the first post argued was missing, read first-principles against its own index for what its choices say about the discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read together, the three describe one stance on agent retrieval and memory: choose &lt;strong&gt;lossless and exact&lt;/strong&gt; by default; expose &lt;strong&gt;MCP&lt;/strong&gt; as the integration substrate; let third-party tools fill the gaps you don't want to own; and keep an arrow back to the source everywhere, because every derived view is a cache and the source is the only thing that can't drift from itself. The cost-curve frame is the math, the cache-coherence frame is the failure taxonomy, and the first-principles read of CodeGraph is what the architecture, looked at carefully, says about where LLM retrieval is going.&lt;/p&gt;

&lt;p&gt;If you're building agent retrieval, the three frames are now in your toolkit. The companion empirical post gives you the install-or-not decision; this one gives you the lens for the next ten tools that ship in the same space.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Companion piece 1 (this is the third in a 3-post Lab series): *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem: Why Claude Code Doesn't Use RAG&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Companion piece 2: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Empirical pair on the Operator track: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/i-tested-codegraph-on-hono-benchmark/" rel="noopener noreferrer"&gt;I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Background: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/consistency-scenarios-and-approaches-production/" rel="noopener noreferrer"&gt;Consistency in Distributed Systems: Scenarios, Trade-offs, and What Actually Works&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;CodeGraph repo: *&lt;/em&gt;&lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;https://github.com/colbymchenry/codegraph&lt;/a&gt;***&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>sqlite</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Mon, 01 Jun 2026 22:39:59 +0000</pubDate>
      <link>https://dev.to/harrisonsec/i-tested-codegraph-on-hono-the-tool-call-savings-reproduce-the-cost-savings-dont-389p</link>
      <guid>https://dev.to/harrisonsec/i-tested-codegraph-on-hono-the-tool-call-savings-reproduce-the-cost-savings-dont-389p</guid>
      <description>&lt;p&gt;Two weeks ago CodeGraph hit GitHub trending — tree-sitter + SQLite/FTS5 + MCP for Claude Code, 19k+ stars in a week. The team published a benchmark on 7 repos showing &lt;strong&gt;35% cheaper, 57% fewer tokens, 46% faster, 71% fewer tool calls&lt;/strong&gt; vs. baseline.&lt;/p&gt;

&lt;p&gt;Those are big numbers. They're also numbers from a benchmark designed by the team that built the tool, on repos they chose. Designer bias is the #1 risk in any retrieval benchmark — when you pick the test repos and write the ground truth, you'll consciously or unconsciously favor your own tool's strengths.&lt;/p&gt;

&lt;p&gt;So I ran an independent test on an 8th repo — &lt;strong&gt;Hono&lt;/strong&gt; (TypeScript, ~280 source files, in neither CodeGraph's published 7-repo suite nor any other published benchmark I could find). 5 architectural questions covering different retrieval shapes, with a deliberate control case (Q5) where the tool should not win. Two conditions (baseline grep+Read+Glob+Explore vs. CodeGraph active), &lt;strong&gt;4 repeats&lt;/strong&gt; per question per condition. 40 runs on Claude Opus 4.8 — and, critically, &lt;strong&gt;every CodeGraph run was verified to have connected, and actual &lt;code&gt;codegraph_*&lt;/code&gt; tool usage was recorded per run&lt;/strong&gt; (more on why that sentence exists below).&lt;/p&gt;

&lt;p&gt;The result splits in a way the single published headline number hides — and the split is the useful part.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — On Hono, CodeGraph delivers a &lt;strong&gt;large, consistent reduction in tool calls (-55%, 14.0 → 6.3 avg) and a smaller latency win (-20%)&lt;/strong&gt; — the published 7-repo direction reproduces here. But &lt;strong&gt;cost is a wash: +6.8%&lt;/strong&gt;, not the published −35%. On narrow-scope questions (route lookup, middleware trace) CodeGraph is actually &lt;strong&gt;20-43% more expensive&lt;/strong&gt;, because each structural lookup loads a big chunk of graph context that costs more in cached tokens than the grep round-trips it replaces. The cost win only appears on broad multi-file navigation (Q3 multi-runtime adapters: &lt;strong&gt;−29% cost, −80% tool calls, −53% latency&lt;/strong&gt;). A second finding: baseline grep+Read has &lt;strong&gt;high variance&lt;/strong&gt; — the agent occasionally spiraled to 47-52 tool calls on the broad questions, while CodeGraph never exceeded 16. &lt;strong&gt;Net at Hono's size: CodeGraph makes the agent take fewer steps and finish faster, but not for fewer dollars.&lt;/strong&gt; Total cost of the 40 valid runs: ~$14 of Opus 4.8 calls. Raw per-run CSV and the 5 verbatim prompts are below.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What "tool calls down, cost flat" actually means
&lt;/h2&gt;

&lt;p&gt;CodeGraph's published 7-repo suite (VS Code, Excalidraw, Django, Tokio, OkHttp, Gin, Alamofire) skews larger and more architecturally complex than Hono. Hono is ~280 TypeScript source files (362 files indexed by CodeGraph, including tests and configs), 16MB on disk — small enough that a thoughtful agent with grep + Read can finish most architectural questions in a handful of tool calls.&lt;/p&gt;

&lt;p&gt;The interesting result is that the &lt;em&gt;axes come apart&lt;/em&gt;. CodeGraph replaces several grep+Read round-trips with one or two structural lookups — so &lt;strong&gt;step count drops hard (-55%)&lt;/strong&gt;. But each &lt;code&gt;codegraph_context&lt;/code&gt; / &lt;code&gt;codegraph_explore&lt;/code&gt; call returns a sizeable chunk of graph context, which then rides along in the conversation cache and gets re-read every turn. At Hono's size, the dollar cost of carrying that cached payload roughly equals the dollar cost of the grep round-trips it replaced — so &lt;strong&gt;dollars stay flat (+7%) even as steps fall by more than half&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's not a contradiction of the cost-curve thesis from &lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;the prior post in this mini-series&lt;/a&gt; — it's a sharper reading of it. Hono sits &lt;strong&gt;above&lt;/strong&gt; the step-count crossover (the index already saves tool calls) but &lt;strong&gt;below&lt;/strong&gt; the dollar crossover (it doesn't yet save money). On a much bigger repo, the grep path churns through far more files and the index pays back on dollars too. Hono just happens to land in the gap between the two crossovers.&lt;/p&gt;

&lt;p&gt;A useful complementary benchmark answers three things the published one doesn't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-validation on a repo not chosen by the tool's team&lt;/strong&gt; — do the published advantages generalize?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Within-repo variance across question types&lt;/strong&gt; — does the win concentrate on certain question shapes? (It does — heavily.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A control case where the tool shouldn't win&lt;/strong&gt; — Q5 (text search) tests whether the agent correctly &lt;em&gt;declines&lt;/em&gt; to use the structural engine when grep is the right tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup — install CodeGraph, ~10 minutes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install (downloads a single binary, no Node/npm required)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh

&lt;span class="c"&gt;# clone the test repo + index it&lt;/span&gt;
git clone https://github.com/honojs/hono.git ~/tmp/hono
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/tmp/hono
codegraph init &lt;span class="nt"&gt;-i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Index build time on Hono (362 files, 4,128 nodes, 8,225 edges): &lt;strong&gt;1.7 seconds.&lt;/strong&gt; On-disk index: 7.1 MB.&lt;/p&gt;

&lt;p&gt;Per-condition setup for the two arms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline (control):&lt;/strong&gt; a clean copy of Hono via &lt;code&gt;rsync -a --exclude='.codegraph/'&lt;/code&gt; to a separate directory so Claude couldn't accidentally grep into the index. No MCP servers registered. Agent uses native Glob + Grep + Read + Explore + Task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeGraph active:&lt;/strong&gt; original Hono directory with &lt;code&gt;.codegraph/&lt;/code&gt; present, MCP server registered:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&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="nl"&gt;"codegraph"&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="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;"codegraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--mcp"&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;Both arms run &lt;code&gt;claude --print --output-format stream-json --model opus&lt;/code&gt; so the model and the rest of the agent loop are identical; the only varying input is whether the CodeGraph MCP server is in the loop. Each run is a fresh session with no prior context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying the tool actually ran (this is not optional)
&lt;/h3&gt;

&lt;p&gt;A retrieval-tool benchmark is only valid if the tool is actually in the loop — and I learned that the hard way. My first pass at this benchmark silently ran with CodeGraph's MCP server &lt;strong&gt;never connected&lt;/strong&gt;: the config was missing the &lt;code&gt;--mcp&lt;/code&gt; flag, and Claude Code proceeds without a server that fails its hand-shake in time rather than erroring out. Every "CodeGraph" run was really just grep+Read. The comparison was noise, and the numbers looked plausibly small — which is exactly how a broken benchmark slips through.&lt;/p&gt;

&lt;p&gt;So for the data here, every run is instrumented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--strict-mcp-config&lt;/code&gt;&lt;/strong&gt; — only the server under test is loaded, with no contamination from other globally-registered MCP servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-warmed daemon + &lt;code&gt;MCP_TIMEOUT=30000&lt;/code&gt;&lt;/strong&gt; — CodeGraph's stdio server attaches to a warm daemon and finishes its handshake &lt;em&gt;before&lt;/em&gt; the agent loop starts. (MCP connection is async; on a fast question the agent can otherwise finish before a cold server is ready.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A per-run assertion&lt;/strong&gt; that CodeGraph reached &lt;code&gt;connected&lt;/code&gt; status, plus a record of whether the agent actually invoked a &lt;code&gt;codegraph_*&lt;/code&gt; tool. Runs that didn't connect were discarded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All 20 CodeGraph runs in this post connected. The agent invoked CodeGraph on Q1-Q4 (4/4 repeats each) and — correctly — chose grep on the Q5 control (0/4). Most vendor benchmarks never report this check. After watching mine fail it silently, I won't publish a retrieval benchmark without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 questions
&lt;/h2&gt;

&lt;p&gt;Full verbatim prompts in the Appendix. Brief overview:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;What it tests&lt;/th&gt;
&lt;th&gt;Hypothesis for CodeGraph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Route resolution: &lt;code&gt;GET /users/:id&lt;/code&gt; → handler&lt;/td&gt;
&lt;td&gt;Route-aware extraction&lt;/td&gt;
&lt;td&gt;Strong win&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Middleware chain trace through &lt;code&gt;app.use&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Dynamic dispatch tracing&lt;/td&gt;
&lt;td&gt;Decisive win via structural lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-runtime adapter architecture&lt;/td&gt;
&lt;td&gt;Cross-file abstraction&lt;/td&gt;
&lt;td&gt;Mid-strong win&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Refactor impact: add mandatory &lt;code&gt;requestId&lt;/code&gt; to &lt;code&gt;Context&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Impact propagation + completeness&lt;/td&gt;
&lt;td&gt;Strong win (what &lt;code&gt;codegraph_impact&lt;/code&gt; is built for)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text search: every literal &lt;code&gt;'Content-Type'&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Keyword search baseline&lt;/td&gt;
&lt;td&gt;~Parity; agent should &lt;em&gt;decline&lt;/em&gt; the tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Q5 is the &lt;strong&gt;CONTROL&lt;/strong&gt; — the tool should not win here, and whether the agent even reaches for it is itself a signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;

&lt;p&gt;Each row averaged over 4 repeats. Cost is Claude's own &lt;code&gt;total_cost_usd&lt;/code&gt; (the API's authoritative figure, not my own multiplication); wall latency from request to final token; tool calls counted from unique &lt;code&gt;tool_use&lt;/code&gt; blocks in the transcript; tokens are input + output (cache tokens tracked separately in the &lt;a href="https://harrisonsec.com/codegraph-hono-benchmark-data.csv" rel="noopener noreferrer"&gt;CSV&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost / tokens
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;th&gt;Baseline cost&lt;/th&gt;
&lt;th&gt;CodeGraph cost&lt;/th&gt;
&lt;th&gt;Δ cost&lt;/th&gt;
&lt;th&gt;Baseline tokens&lt;/th&gt;
&lt;th&gt;CodeGraph tokens&lt;/th&gt;
&lt;th&gt;Δ tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q1 route&lt;/td&gt;
&lt;td&gt;$0.321&lt;/td&gt;
&lt;td&gt;$0.393&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;+22.5%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;td&gt;10,115&lt;/td&gt;
&lt;td&gt;6,045&lt;/td&gt;
&lt;td&gt;−40.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q2 middleware&lt;/td&gt;
&lt;td&gt;$0.212&lt;/td&gt;
&lt;td&gt;$0.303&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;+43.4%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;td&gt;7,233&lt;/td&gt;
&lt;td&gt;5,649&lt;/td&gt;
&lt;td&gt;−21.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3 multi-runtime&lt;/td&gt;
&lt;td&gt;$0.490&lt;/td&gt;
&lt;td&gt;$0.348&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;−28.9%&lt;/strong&gt; ✓✓&lt;/td&gt;
&lt;td&gt;11,582&lt;/td&gt;
&lt;td&gt;7,048&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−39.1%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4 refactor&lt;/td&gt;
&lt;td&gt;$0.402&lt;/td&gt;
&lt;td&gt;$0.509&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;+26.5%&lt;/strong&gt; ❌&lt;/td&gt;
&lt;td&gt;9,119&lt;/td&gt;
&lt;td&gt;8,567&lt;/td&gt;
&lt;td&gt;−6.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5 text (ctrl)&lt;/td&gt;
&lt;td&gt;$0.267&lt;/td&gt;
&lt;td&gt;$0.253&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−5.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8,874&lt;/td&gt;
&lt;td&gt;8,998&lt;/td&gt;
&lt;td&gt;+1.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aggregate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.338&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.361&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+6.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,385&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7,261&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−22.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Tool calls / wall latency
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;th&gt;Baseline calls&lt;/th&gt;
&lt;th&gt;CodeGraph calls&lt;/th&gt;
&lt;th&gt;Δ calls&lt;/th&gt;
&lt;th&gt;Baseline latency&lt;/th&gt;
&lt;th&gt;CodeGraph latency&lt;/th&gt;
&lt;th&gt;Δ latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q1 route&lt;/td&gt;
&lt;td&gt;7.8&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−12.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;49.8s&lt;/td&gt;
&lt;td&gt;51.2s&lt;/td&gt;
&lt;td&gt;+2.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q2 middleware&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;4.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−20.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;41.2s&lt;/td&gt;
&lt;td&gt;43.1s&lt;/td&gt;
&lt;td&gt;+4.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3 multi-runtime&lt;/td&gt;
&lt;td&gt;35.2&lt;/td&gt;
&lt;td&gt;7.0&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;−80.1%&lt;/strong&gt; ✓✓&lt;/td&gt;
&lt;td&gt;123.7s&lt;/td&gt;
&lt;td&gt;58.4s&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;−52.8%&lt;/strong&gt; ✓✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4 refactor&lt;/td&gt;
&lt;td&gt;19.8&lt;/td&gt;
&lt;td&gt;11.8&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;−40.5%&lt;/strong&gt; ✓&lt;/td&gt;
&lt;td&gt;87.9s&lt;/td&gt;
&lt;td&gt;85.9s&lt;/td&gt;
&lt;td&gt;−2.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5 text (ctrl)&lt;/td&gt;
&lt;td&gt;2.2&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;−11.1%&lt;/td&gt;
&lt;td&gt;51.2s&lt;/td&gt;
&lt;td&gt;43.5s&lt;/td&gt;
&lt;td&gt;−15.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aggregate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−55.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;70.8s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.4s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−20.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two headline rows: &lt;strong&gt;−55% tool calls&lt;/strong&gt; (real and consistent — CodeGraph used fewer tools on every single question) and &lt;strong&gt;+6.8% cost&lt;/strong&gt; (CodeGraph is &lt;em&gt;not&lt;/em&gt; cheaper on Hono). The latency win (−20%) is real but concentrated: almost all of it is Q3; on Q1/Q2/Q4 latency is within ±5%.&lt;/p&gt;

&lt;h3&gt;
  
  
  The variance story — CodeGraph bounds the worst case
&lt;/h3&gt;

&lt;p&gt;Averages hide the most interesting result. Baseline tool-call counts, per repeat:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;th&gt;Baseline (4 repeats)&lt;/th&gt;
&lt;th&gt;CodeGraph (4 repeats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q3 multi-runtime&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14, 23, 52, 52&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5, 6, 8, 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4 refactor&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9, 10, 13, 47&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9, 10, 12, 16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the broad questions, baseline grep+Read &lt;strong&gt;occasionally spiraled&lt;/strong&gt; — the agent without an index wandered to 47-52 tool calls chasing files. Across all 40 runs, baseline ranged from 2 to &lt;strong&gt;52&lt;/strong&gt; tool calls; CodeGraph ranged from 1 to &lt;strong&gt;16&lt;/strong&gt;. &lt;strong&gt;A large part of CodeGraph's value here isn't the mean — it's that it bounds the worst case.&lt;/strong&gt; When the structural answer is one graph query away, the agent can't spiral. That's a reliability property, not just an efficiency one, and it doesn't show up in a single average.&lt;/p&gt;

&lt;p&gt;A caveat on sample size: this is 4 repeats on one repo. Treat the &lt;strong&gt;magnitudes as indicative and the directions as robust&lt;/strong&gt; — CodeGraph used fewer tool calls in every question and nearly every repeat, and the cost direction was consistent within cells (more expensive on Q1/Q2/Q4, cheaper only on Q3). What I would not over-read is the exact percentages; a 47-vs-9 baseline spread on Q4 means the per-question means carry real uncertainty even at n=4.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-question narrative
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1 (route resolution) — CodeGraph used, but more expensive.&lt;/strong&gt; Both arms traced &lt;code&gt;app.fetch&lt;/code&gt; → &lt;code&gt;#dispatch&lt;/code&gt; → &lt;code&gt;this.router.match()&lt;/code&gt; and read &lt;code&gt;SmartRouter&lt;/code&gt; → &lt;code&gt;RegExpRouter&lt;/code&gt;. CodeGraph used &lt;code&gt;codegraph_context&lt;/code&gt; + &lt;code&gt;codegraph_trace&lt;/code&gt; (2 calls/run) and cut tool calls 13% and tokens 40% — but cost rose 22.5% and latency was flat. The structural context it front-loaded was heavier than the 1-2 grep steps it saved. &lt;strong&gt;Hono's router is small enough (5-6 files for the full picture) that grep finds it directly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2 (middleware chain trace) — used, 43% more expensive.&lt;/strong&gt; CodeGraph landed the &lt;code&gt;app.use&lt;/code&gt; → middleware array → &lt;code&gt;compose()&lt;/code&gt; chain in 4 tool calls vs baseline's 5, but cost jumped 43%. Same mechanism as Q1, more pronounced: the call-chain context payload dominated a question baseline answered cheaply in 5 small steps. &lt;strong&gt;The clearest example of "fewer steps, more dollars."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3 (multi-runtime adapter) — the unambiguous win.&lt;/strong&gt; Enumerating 6 adapter directories (Cloudflare Workers / Deno / Bun / Node / AWS Lambda / Vercel Edge) is exactly where one graph query beats many Glob+grep iterations. Baseline averaged &lt;strong&gt;35 tool calls and 124s&lt;/strong&gt; (and spiraled to 52 twice); CodeGraph: &lt;strong&gt;7 calls, 58s, −29% cost.&lt;/strong&gt; This is the question shape where structural retrieval pays back on every axis at once — and the only one where it saved money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4 (refactor impact: add &lt;code&gt;requestId&lt;/code&gt; to &lt;code&gt;Context&lt;/code&gt;) — tools halved, cost up.&lt;/strong&gt; Supposed to be CodeGraph's strongest case (&lt;code&gt;codegraph_impact&lt;/code&gt; is built for blast-radius). It did cut tool calls 40% (and tamed baseline's 47-call spiral), but cost rose 26.5%: the impact-graph walk pulled wide context the agent didn't fully need at Hono's size. Completeness was comparable across arms (both identified the &lt;code&gt;Context&lt;/code&gt; constructors in &lt;code&gt;src/hono-base.ts&lt;/code&gt;, the &lt;code&gt;Variables&lt;/code&gt; plumbing, and the per-method handler signatures). &lt;strong&gt;Fewer, more-bounded steps — but the propagation graph isn't wide enough here to pay back on dollars.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5 (text search, control) — the agent declined the tool, and that's the point.&lt;/strong&gt; On a pure literal-&lt;code&gt;'Content-Type'&lt;/code&gt; search, the agent &lt;strong&gt;never invoked CodeGraph in any of the 4 repeats&lt;/strong&gt; — it reached straight for grep. Result: near-parity (−5% cost, −15% latency, both inside the noise). The old version of this post claimed an "FTS5 fallback" win here; that was an artifact of the broken first run. The truth is simpler and better: &lt;strong&gt;with CodeGraph connected and available, the agent correctly chose grep for a grep-shaped task.&lt;/strong&gt; No over-engineering. That's the table-stakes behavior you actually want from a retrieval tool, and it's worth more than a fabricated 1-step saving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-validation with CodeGraph's published 7-repo benchmark
&lt;/h2&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;Published (7 repos)&lt;/th&gt;
&lt;th&gt;This test (Hono, n=4)&lt;/th&gt;
&lt;th&gt;Reproduces?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool calls&lt;/td&gt;
&lt;td&gt;−71%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−55%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓ Yes — same ballpark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;−46%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−20%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~ Directionally, ~half the magnitude&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;td&gt;−57%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−23%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~ Directionally, smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−35%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+6.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ &lt;strong&gt;No — opposite sign&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tool-call reduction is the part that generalizes cleanly to a repo the team didn't pick. The cost reduction is the part that doesn't — and that's not an attack on CodeGraph, it's a statement about repo size. Their published suite skews large (VS Code is 30k+ files; Tokio is mid-thousands), and their own published table is &lt;strong&gt;non-monotonic in file count&lt;/strong&gt; — Gin (~110 files) shows a 21% cost win while OkHttp (~645 files) shows ~2%, and Tokio (~790 files) shows 82%. &lt;strong&gt;Repo size matters, but it isn't a clean threshold; question shape matters at least as much.&lt;/strong&gt; A single repo can't locate a universal crossover. What Hono shows is one clear data point: at ~280 files, the step-count win is already here, the dollar win isn't yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision matrix — install CodeGraph when
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Install CodeGraph&lt;/th&gt;
&lt;th&gt;Skip (baseline grep+Read is enough)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You care about &lt;strong&gt;fewer agent steps / lower latency&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;✓ (−55% tool calls even on a small repo)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You're optimizing &lt;strong&gt;dollar cost&lt;/strong&gt; on a sub-~500-file repo&lt;/td&gt;
&lt;td&gt;(may cost slightly more)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repo is large (low thousands of files+)&lt;/td&gt;
&lt;td&gt;✓ (dollar win should appear too)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workload is &lt;strong&gt;broad multi-file navigation / architecture&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;✓ (this is where it wins on every axis)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workload is &lt;strong&gt;narrow single-symbol lookups&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;(fewer steps, but not cheaper)&lt;/td&gt;
&lt;td&gt;(grep is fine)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static-typed (TS / Rust / Go / Java / Swift / C#)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic-typed (Python / Ruby / untyped JS)&lt;/td&gt;
&lt;td&gt;⚠️ partial (tree-sitter misses runtime semantics)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow is text-search dominant&lt;/td&gt;
&lt;td&gt;(no penalty — the agent declines the tool)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent &lt;strong&gt;reliability&lt;/strong&gt; matters (bounding worst-case exploration)&lt;/td&gt;
&lt;td&gt;✓ (caps the 50-tool-call spirals)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You're not sure&lt;/td&gt;
&lt;td&gt;install it; ~10 min, &amp;lt;2s to index a Hono-sized repo, uninstall is one command&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key call from the data:&lt;/strong&gt; at Hono's scale the reason to install CodeGraph is &lt;strong&gt;fewer steps, lower latency, and bounded worst-case exploration&lt;/strong&gt; — not a lower bill. If your decision rule is purely dollars-per-query on a small repo, baseline grep+Read is still competitive. If it's agent speed, predictability, or you're working in a larger codebase, the index earns its place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd want to test next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Larger TS repo head-to-head&lt;/strong&gt; — same 5 questions on Prisma (~2,000 TS files) or TanStack Query (~600 files) to find where the &lt;em&gt;dollar&lt;/em&gt; crossover actually is. Hypothesis: cost flips negative somewhere in the high hundreds to low thousands of files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic-typed repo&lt;/strong&gt; — same 5 questions on FastAPI or Django REST to see how much of the step-count win survives when tree-sitter can't resolve dynamic dispatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-session compounding&lt;/strong&gt; — single-question runs miss the multi-turn agent context. Does the per-query step saving compound across a real session, or stay linear?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All future content. None block the install-or-not decision the data above already answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-line verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On TypeScript / Rust / Go projects, install CodeGraph if you want fewer agent steps, lower latency, and bounded worst-case exploration — those reproduce on an independent repo. Don't install it expecting a lower bill on a small codebase: at Hono's ~280-file scale it was ~7% &lt;em&gt;more&lt;/em&gt; expensive, and in this benchmark a cost win appeared only on broad multi-file navigation (Q3) — the published −35% likely needs much larger repos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the architectural deep-dive on &lt;em&gt;why&lt;/em&gt; this class of tool works and where the abstractions leak, see the companion Lab piece &lt;em&gt;Agent Retrieval Above the Crossover: A First-Principles Read of CodeGraph&lt;/em&gt; (publishing 2026-06-08).&lt;/p&gt;

&lt;p&gt;For the broader cost-curve framework this benchmark applies, see the prior Lab post: &lt;em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: Benchmark Questions
&lt;/h2&gt;

&lt;p&gt;The 5 prompts used, verbatim. Each was sent to Claude Code in a fresh session (&lt;code&gt;claude --print --model opus&lt;/code&gt;), 4 times per arm. No follow-up prompts within a run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q1 — Route resolution
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;When a request hits &lt;code&gt;GET /users/:id&lt;/code&gt; in a Hono app, walk me through how Hono's routing finds and invokes the right handler. Where in the source does the URL → handler matching happen, and what data structure stores the route table?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Q2 — Middleware chain trace
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Hono middleware is chained via &lt;code&gt;app.use(middleware)&lt;/code&gt;. When a request flows through several middlewares before hitting the handler, what's the actual call stack from the incoming request to the handler? Specifically — how does Hono ensure middleware runs in order, and how is &lt;code&gt;c&lt;/code&gt; (context) + &lt;code&gt;next&lt;/code&gt; passed between them?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Q3 — Cross-file abstraction navigation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Hono supports multiple runtime adapters (Cloudflare Workers, Deno, Bun, Node, AWS Lambda, Vercel Edge). How is this multi-runtime abstraction implemented? What's the shared interface, and where do the per-runtime adapters live? Show me the architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Q4 — Refactor impact
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine I'm planning to add a mandatory new property &lt;code&gt;requestId: string&lt;/code&gt; to Hono's &lt;code&gt;Context&lt;/code&gt; class. What files and functions across the codebase would be affected? Give me the full blast radius — where Context is constructed, where it's typed in signatures, and where mandatory-property additions would break.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Q5 — Text search (control)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Find every place in the Hono codebase where the literal string &lt;code&gt;'Content-Type'&lt;/code&gt; (the exact HTTP header name, case-sensitive) appears. Include source code, tests, comments, and documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Scoring &amp;amp; environment
&lt;/h3&gt;

&lt;p&gt;Each question evaluated on cost (&lt;code&gt;total_cost_usd&lt;/code&gt;), tokens (input+output, cache tracked separately), wall latency, unique tool-call count, and a manual correctness/completeness check (both arms agreed on the same answer in every Q). Every CodeGraph run was additionally checked for &lt;code&gt;connected&lt;/code&gt; MCP status and actual &lt;code&gt;codegraph_*&lt;/code&gt; tool invocation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Environment:&lt;/strong&gt; Hono @ commit &lt;code&gt;2cbeadda&lt;/code&gt; (2026-05-28) · CodeGraph 0.9.7 · Claude Code 2.1.159 · model &lt;code&gt;claude-opus-4-8&lt;/code&gt; (Opus 4.8) · macOS. &lt;strong&gt;Raw per-run data&lt;/strong&gt; (cost, tokens, tool calls, latency, connection status) for all 40 runs: &lt;strong&gt;&lt;a href="https://harrisonsec.com/codegraph-hono-benchmark-data.csv" rel="noopener noreferrer"&gt;CSV&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Appendix: Why there's no third arm (knowing)
&lt;/h2&gt;

&lt;p&gt;I intended to include &lt;strong&gt;knowing&lt;/strong&gt; (Blackwell Systems) as a third arm. In headless batch mode its MCP server connected only intermittently — knowing advertises asynchronous &lt;code&gt;tools/listChanged&lt;/code&gt;, which races Claude Code's MCP startup window, so on most runs the agent never saw knowing's tools and silently fell back to grep+Read.&lt;/p&gt;

&lt;p&gt;Reporting task-cost numbers from runs where the tool wasn't actually in the loop is exactly the trap that invalidated my &lt;em&gt;first&lt;/em&gt; attempt at this benchmark (see Verifying the tool actually ran), so I'm not publishing knowing figures. That's a limitation of my batch harness, &lt;strong&gt;not&lt;/strong&gt; a verdict on knowing — a persistent / pre-warmed MCP host or an interactive session would likely fix it. If I get a reliable knowing setup, I'll benchmark it on its own terms and publish separately.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Lab companion (first-principles architectural read of CodeGraph and the class of tools it represents): **Agent Retrieval Above the Crossover&lt;/em&gt;* — publishing 2026-06-08.*&lt;br&gt;
&lt;em&gt;Prior Lab post in the retrieval / memory mini-series: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Companion Lab post on cross-session memory: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-memory-is-a-cache-coherence-problem/" rel="noopener noreferrer"&gt;Agent Memory Is a Cache Coherence Problem&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;CodeGraph repo: *&lt;/em&gt;&lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;https://github.com/colbymchenry/codegraph&lt;/a&gt;***&lt;/p&gt;

</description>
      <category>ai</category>
      <category>benchmark</category>
      <category>devtools</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Agent Memory Is a Cache Coherence Problem</title>
      <dc:creator>Harrison Guo</dc:creator>
      <pubDate>Fri, 29 May 2026 15:17:06 +0000</pubDate>
      <link>https://dev.to/harrisonsec/agent-memory-is-a-cache-coherence-problem-4jmk</link>
      <guid>https://dev.to/harrisonsec/agent-memory-is-a-cache-coherence-problem-4jmk</guid>
      <description>&lt;p&gt;This post is one half of a pair. The other half — &lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;&lt;em&gt;Agent Retrieval Is a Cost Curve Problem&lt;/em&gt;&lt;/a&gt; — argues that Claude Code's within-session code retrieval avoids RAG because the cost curve says it should. This piece argues something parallel about &lt;em&gt;cross-session memory&lt;/em&gt;: the lossy auto-capture systems being marketed as "AI memory" are, in classical distributed-systems vocabulary, &lt;strong&gt;caches&lt;/strong&gt;. They inherit every problem caches have always had, and the hype around them is mostly arguing for one side of a write-back vs write-through trade as if the other side didn't exist.&lt;/p&gt;

&lt;p&gt;Sequel to &lt;em&gt;&lt;a href="https://harrisonsec.com/blog/consistency-scenarios-and-approaches-production/" rel="noopener noreferrer"&gt;Consistency in Distributed Systems: Scenarios, Trade-offs, and What Actually Works&lt;/a&gt;&lt;/em&gt;. If you remember that piece, you'll recognize the move: take a problem space the AI community is debating with fresh vocabulary, and notice that the database community already mapped the failure modes thirty years ago.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; — Cross-session agent memory varies on two independent axes, not one: &lt;strong&gt;fidelity&lt;/strong&gt; (lossless vs lossy) and &lt;strong&gt;retrieval&lt;/strong&gt; (exact lookup vs approximate vector). Claude Code's built-in memory plus a hand-written &lt;code&gt;CLAUDE.md&lt;/code&gt; lives at &lt;em&gt;lossless + exact&lt;/em&gt;. The currently-trending &lt;code&gt;claude-mem&lt;/code&gt; (70k+ GitHub stars as of May 2026) lives at &lt;em&gt;lossy + approximate&lt;/em&gt; — auto-capture passed through a Haiku compression step and recalled via SQLite-FTS5 + Chroma vectors. The second is, structurally, a cache: a derived lossy view of the source of truth, retrieved approximately. It inherits every cache problem the distributed-systems literature already named — staleness, wrong-row retrieval, no coherence with the source. I ran claude-mem under controlled conditions and compared it against the deterministic CLAUDE.md baseline; the numbers (and the &lt;em&gt;kinds&lt;/em&gt; of failures) line up with the classical cache framing. The most interesting failure isn't tokens or latency. It's that compression flattens &lt;strong&gt;modality&lt;/strong&gt; — a hedged hypothetical becomes a flat fact, indistinguishable from a firm decision. An agent confidently acting on a maybe-it-said-yes is worse than an agent with no memory at all.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Two Axes (Don't Collapse Them Into One)
&lt;/h2&gt;

&lt;p&gt;Most takes on agent memory collapse the design space onto a single axis: "lossless and limited" vs "lossy and powerful." That framing hides the failure modes.&lt;/p&gt;

&lt;p&gt;The real space is two-dimensional:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fidelity&lt;/strong&gt; — &lt;em&gt;lossless&lt;/em&gt; (verbatim, what-you-wrote-is-what-was-stored) vs &lt;em&gt;lossy&lt;/em&gt; (LLM-compressed: a summary written by a smaller model over the raw events).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt; — &lt;em&gt;exact / curated&lt;/em&gt; (you wrote an index entry; the system reads it back) vs &lt;em&gt;approximate / semantic&lt;/em&gt; (vector embeddings; cosine similarity; top-K nearest neighbors).&lt;/li&gt;
&lt;/ol&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%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBheGVzWyJUd28gYXhlcywgZm91ciBxdWFkcmFudHMiXQogICAgICAgIGRpcmVjdGlvbiBMUgogICAgICAgIHN1YmdyYXBoIGNvbDFbIkV4YWN0IHJldHJpZXZhbCJdCiAgICAgICAgICAgIHExWyI8Yj5Mb3NzbGVzcyArIEV4YWN0PC9iPjxici8-Q0xBVURFLm1kIC8gaGFuZC1jdXJhdGVkPGJyLz5QcmVjaXNpb246IGhpZ2g8YnIvPkNvdmVyYWdlOiBsb3c8YnIvPlVwa2VlcDogbWFudWFsIl0KICAgICAgICAgICAgcTNbIjxiPkxvc3N5ICsgRXhhY3Q8L2I-PGJyLz5Db21wcmVzc2VkIGJ1dCBrZXl3b3JkLWluZGV4ZWQ8YnIvPih1bnVzdWFsIGluIHByYWN0aWNlKSJdCiAgICAgICAgZW5kCiAgICAgICAgc3ViZ3JhcGggY29sMlsiQXBwcm94aW1hdGUgcmV0cmlldmFsIl0KICAgICAgICAgICAgcTJbIjxiPkxvc3NsZXNzICsgQXBwcm94aW1hdGU8L2I-PGJyLz5SYXcgbG9ncyArIHZlY3RvciBzZWFyY2g8YnIvPihodWdlIHN0b3JhZ2U7IHdlYWsgc2lnbmFsKSJdCiAgICAgICAgICAgIHE0WyI8Yj5Mb3NzeSArIEFwcHJveGltYXRlPC9iPjxici8-PGI-Y2xhdWRlLW1lbSBhbmQgbW9zdCAnQUkgbWVtb3J5JyB0b29sczwvYj48YnIvPkF1dG8tY29tcHJlc3MgKyB2ZWN0b3IgcmVjYWxsPGJyLz5Ud28gbGF5ZXJzIG9mIGFwcHJveGltYXRpb24iXQogICAgICAgIGVuZAogICAgZW5kCgogICAgY2xhc3NEZWYgZ29vZCBmaWxsOiNmMGZmZjQsc3Ryb2tlOiMyZjg1NWEKICAgIGNsYXNzRGVmIHdhcm4gZmlsbDojZmVmNWU3LHN0cm9rZTojYjc3OTFmCiAgICBjbGFzc0RlZiBiYWQgZmlsbDojZmVkN2Q3LHN0cm9rZTojYzUzMDMwCiAgICBjbGFzcyBxMSBnb29kCiAgICBjbGFzcyBxMiB3YXJuCiAgICBjbGFzcyBxMyB3YXJuCiAgICBjbGFzcyBxNCBiYWQ%3D" 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%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBheGVzWyJUd28gYXhlcywgZm91ciBxdWFkcmFudHMiXQogICAgICAgIGRpcmVjdGlvbiBMUgogICAgICAgIHN1YmdyYXBoIGNvbDFbIkV4YWN0IHJldHJpZXZhbCJdCiAgICAgICAgICAgIHExWyI8Yj5Mb3NzbGVzcyArIEV4YWN0PC9iPjxici8-Q0xBVURFLm1kIC8gaGFuZC1jdXJhdGVkPGJyLz5QcmVjaXNpb246IGhpZ2g8YnIvPkNvdmVyYWdlOiBsb3c8YnIvPlVwa2VlcDogbWFudWFsIl0KICAgICAgICAgICAgcTNbIjxiPkxvc3N5ICsgRXhhY3Q8L2I-PGJyLz5Db21wcmVzc2VkIGJ1dCBrZXl3b3JkLWluZGV4ZWQ8YnIvPih1bnVzdWFsIGluIHByYWN0aWNlKSJdCiAgICAgICAgZW5kCiAgICAgICAgc3ViZ3JhcGggY29sMlsiQXBwcm94aW1hdGUgcmV0cmlldmFsIl0KICAgICAgICAgICAgcTJbIjxiPkxvc3NsZXNzICsgQXBwcm94aW1hdGU8L2I-PGJyLz5SYXcgbG9ncyArIHZlY3RvciBzZWFyY2g8YnIvPihodWdlIHN0b3JhZ2U7IHdlYWsgc2lnbmFsKSJdCiAgICAgICAgICAgIHE0WyI8Yj5Mb3NzeSArIEFwcHJveGltYXRlPC9iPjxici8-PGI-Y2xhdWRlLW1lbSBhbmQgbW9zdCAnQUkgbWVtb3J5JyB0b29sczwvYj48YnIvPkF1dG8tY29tcHJlc3MgKyB2ZWN0b3IgcmVjYWxsPGJyLz5Ud28gbGF5ZXJzIG9mIGFwcHJveGltYXRpb24iXQogICAgICAgIGVuZAogICAgZW5kCgogICAgY2xhc3NEZWYgZ29vZCBmaWxsOiNmMGZmZjQsc3Ryb2tlOiMyZjg1NWEKICAgIGNsYXNzRGVmIHdhcm4gZmlsbDojZmVmNWU3LHN0cm9rZTojYjc3OTFmCiAgICBjbGFzc0RlZiBiYWQgZmlsbDojZmVkN2Q3LHN0cm9rZTojYzUzMDMwCiAgICBjbGFzcyBxMSBnb29kCiAgICBjbGFzcyBxMiB3YXJuCiAgICBjbGFzcyBxMyB3YXJuCiAgICBjbGFzcyBxNCBiYWQ%3D" alt="flowchart TD" width="730" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interesting failures live in the bottom-right quadrant — &lt;em&gt;lossy + approximate&lt;/em&gt; — because the failures of one axis are &lt;em&gt;invisible to a user evaluating along the other&lt;/em&gt;. The system loses information at write time and approximates at read time, and the user sees a single "answer" that fused both losses. Debugging means asking: was that wrong because the original event was corrupted in compression, or because retrieval surfaced the wrong row? You usually can't tell.&lt;/p&gt;

&lt;p&gt;Most takes conflate "lossy = unreliable" with "vector = powerful." They're orthogonal. You can have lossless + vector (raw logs, vector-indexed — fine but storage-heavy and signal-weak). You can have lossy + exact (compressed summaries, FTS-indexed — works for some applications). Lossy + approximate is what's being marketed as "AI memory," and it's the quadrant most exposed to compounding failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Baseline: Lossless + Exact (&lt;code&gt;CLAUDE.md&lt;/code&gt; + built-in memory)
&lt;/h2&gt;

&lt;p&gt;Claude Code's built-in memory system, paired with a hand-written &lt;code&gt;CLAUDE.md&lt;/code&gt;, sits firmly in the &lt;em&gt;lossless + exact&lt;/em&gt; quadrant. The design choices, made explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verbatim storage.&lt;/strong&gt; What the author wrote is what gets stored. Markdown in, Markdown out. There's no compression step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-loaded index + on-demand body.&lt;/strong&gt; &lt;code&gt;MEMORY.md&lt;/code&gt; (the index) gets injected into every session — capped, deliberately, around 200 lines to avoid context bloat. Individual memory files are read on demand, when the index entry suggests one is relevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curated, not a firehose.&lt;/strong&gt; A human (or a structured prompt) decides what is worth storing. Not every tool call. Not every file read. Only the durable, surprising, cross-session-useful facts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact recall.&lt;/strong&gt; The model reads a specific file. Either it's there and is read verbatim, or it isn't. No fuzzy near-matches; no confidence score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mental model: a hand-written &lt;strong&gt;WAL&lt;/strong&gt; (write-ahead log) plus a &lt;strong&gt;curated index&lt;/strong&gt;. Both are close to a source of truth — the author's deliberate decision — and they recall exactly what was committed.&lt;/p&gt;

&lt;p&gt;Tradeoffs are visible from this framing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Precision&lt;/strong&gt;: 100%. What you stored is what you get back.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Auditability&lt;/strong&gt;: you can read the file yourself. No black box.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Token economics&lt;/strong&gt;: index sits in context; bodies fetched only when needed. Cheap.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Coverage&lt;/strong&gt;: limited to what the author bothered to write down.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Upkeep&lt;/strong&gt;: manual. Memories rot; updating them is a chore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The earlier post in this series, &lt;a href="https://harrisonsec.com/blog/claude-code-memory-first-principles-tradeoffs/" rel="noopener noreferrer"&gt;&lt;em&gt;Claude Code Deep Dive Part 4: Why It Uses Markdown Files Instead of Vector DBs&lt;/em&gt;&lt;/a&gt;, walks through the specific design choices in the publicly circulated build snapshot. Here I'll focus on what happens when you put the lossless+exact baseline next to the lossy+approximate contender.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contender: Lossy + Approximate (&lt;code&gt;claude-mem&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/thedotmack/claude-mem" rel="noopener noreferrer"&gt;&lt;code&gt;claude-mem&lt;/code&gt;&lt;/a&gt; is among the highest-starred entries in the agent-memory category right now (70k+ GitHub stars as of May 2026). I tested v13.2.0. The architecture, summarized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-capture firehose.&lt;/strong&gt; Lifecycle hooks (&lt;code&gt;SessionStart&lt;/code&gt;, &lt;code&gt;UserPromptSubmit&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;, &lt;code&gt;SessionEnd&lt;/code&gt;) fire on essentially everything the model does. The hooks pipe events to a Bun worker on &lt;code&gt;localhost:37701&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression to facts/narrative.&lt;/strong&gt; At session boundaries the worker invokes &lt;strong&gt;Haiku 4.5&lt;/strong&gt; to compress raw observations into structured &lt;em&gt;facts&lt;/em&gt; (a JSON array) and a &lt;em&gt;narrative&lt;/em&gt; (a paragraph). This compression runs &lt;strong&gt;on your own Claude subscription&lt;/strong&gt; — billed to your quota, ~5,150 compression tokens per session in my test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid index.&lt;/strong&gt; Compressed observations are written to two indexes simultaneously: &lt;strong&gt;SQLite-FTS5&lt;/strong&gt; (full-text keyword) and &lt;strong&gt;Chroma&lt;/strong&gt; (vector embeddings). Recall is hybrid — keyword and ANN together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disables built-in memory.&lt;/strong&gt; Installation sets &lt;code&gt;CLAUDE_CODE_DISABLE_AUTO_MEMORY=1&lt;/code&gt; in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. The built-in CLAUDE.md path is turned off; &lt;code&gt;claude-mem&lt;/code&gt; is meant to &lt;em&gt;replace&lt;/em&gt;, not augment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded data dir.&lt;/strong&gt; Despite respecting &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; for plugin config, the data store path (&lt;code&gt;~/.claude-mem&lt;/code&gt;) is hardcoded. Sandboxing is partial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mental model: a &lt;strong&gt;derived, lossy materialized view&lt;/strong&gt; of session events, plus a &lt;strong&gt;similarity cache&lt;/strong&gt; for retrieval. Two layers of approximation: a lossy &lt;em&gt;write&lt;/em&gt; transform (Haiku compression) and an approximate &lt;em&gt;read&lt;/em&gt; transform (ANN). Each compounds the other.&lt;/p&gt;

&lt;p&gt;This is exactly what the bottom-right quadrant looks like in deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test (Real Numbers, 2026-05-20)
&lt;/h2&gt;

&lt;p&gt;To make the comparison concrete I built a small URL-shortener as the test bed: simple enough that the "right answer" was unambiguous, structured enough that real architectural decisions had to be recorded. The setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test arm:&lt;/strong&gt; &lt;code&gt;claude-mem&lt;/code&gt; v13.2.0, sandboxed via &lt;code&gt;CLAUDE_CONFIG_DIR=/tmp/cmem-test/dot-claude&lt;/code&gt;. Built-in memory disabled (per install default). Session 1 established four decisions about the codebase; Session 2 asked for them back. This is the arm I measured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline:&lt;/strong&gt; the same four decisions written into a hand-curated &lt;code&gt;CLAUDE.md&lt;/code&gt; — 1,075 chars, ~269 tokens. Built-in memory intact. The baseline numbers in the table below are deterministic properties of how the built-in &lt;code&gt;CLAUDE.md&lt;/code&gt; path works (verbatim recall, no extra round-trip, no compression bill), not a separately measured session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decisions in Session 1 (so the comparison is fair):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Redis for URL storage.&lt;/li&gt;
&lt;li&gt;Generate short codes with base62.&lt;/li&gt;
&lt;li&gt;Add a 30-day TTL.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"We could refresh the TTL on each access if we want sliding expiration."&lt;/em&gt; (Note the hedge — this is the modality test that matters.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Numbers, side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;claude-mem (v13.2.0)&lt;/th&gt;
&lt;th&gt;bare CLAUDE.md&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per recall-cycle tokens&lt;/td&gt;
&lt;td&gt;~6,700&lt;/td&gt;
&lt;td&gt;~280&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;↳ Passive context injection on session start&lt;/td&gt;
&lt;td&gt;~1,050&lt;/td&gt;
&lt;td&gt;~269 (full file)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;↳ &lt;code&gt;mcp-search&lt;/code&gt; retrieval round-trip&lt;/td&gt;
&lt;td&gt;~502&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;↳ Haiku 4.5 compression cost (charged to your quota)&lt;/td&gt;
&lt;td&gt;~5,150 / session&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra round-trip for details&lt;/td&gt;
&lt;td&gt;yes (~22s)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fidelity of recall&lt;/td&gt;
&lt;td&gt;lossy (see below)&lt;/td&gt;
&lt;td&gt;100% verbatim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in memory state&lt;/td&gt;
&lt;td&gt;disabled (&lt;code&gt;CLAUDE_CODE_DISABLE_AUTO_MEMORY=1&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;intact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compression cost&lt;/td&gt;
&lt;td&gt;on user's Claude subscription&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headless capture (&lt;code&gt;claude -p&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;zero events&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upkeep&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;manual edit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The token gap — 6,700 vs 280 — is meaningful but not the headline. The headline is the &lt;em&gt;fidelity&lt;/em&gt; row.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sharpest Failure: Compression Flattens Modality
&lt;/h2&gt;

&lt;p&gt;The four decisions written in Session 1 included three firm choices and one hedge:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We use Redis... base62 short codes... 30-day TTL... we &lt;em&gt;could&lt;/em&gt; refresh the TTL on each access &lt;strong&gt;if we want&lt;/strong&gt; sliding expiration."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I read the raw observation row that &lt;code&gt;claude-mem&lt;/code&gt; wrote, the four items appeared as a flat JSON array — &lt;code&gt;facts: [...]&lt;/code&gt; — with &lt;strong&gt;no modal marker&lt;/strong&gt; distinguishing the hedge from the decisions. The hedge had been flattened into the same shape as the firm choices.&lt;/p&gt;

&lt;p&gt;Session 2 confirmed it. I asked the recalling agent to describe the TTL design. It cheerfully reported "we refresh the TTL on each access for sliding expiration" — as though that had been decided. When I challenged it directly, its own reply was that it could not distinguish firm decisions from options that had merely been considered. The compressed &lt;code&gt;facts[]&lt;/code&gt; row it was reading from preserved the content of each item but not its modal status — what I'll call its &lt;em&gt;epistemic status&lt;/em&gt; throughout the rest of this post.&lt;/p&gt;

&lt;p&gt;That's the failure. &lt;strong&gt;The lossy layer loses epistemic status, not just bytes.&lt;/strong&gt; A &lt;em&gt;maybe&lt;/em&gt; becomes a &lt;em&gt;decision&lt;/em&gt;. The recalling agent has no way to know it shouldn't trust the row.&lt;/p&gt;

&lt;p&gt;This is worse than no memory. An agent with no memory has to ask, or reread, or check the code. An agent with confident-wrong memory acts. The cost of acting on a fabricated decision compounds: now there's code (or a PR, or an architectural note) committed under the false premise, and &lt;em&gt;that&lt;/em&gt; will be the next round's input.&lt;/p&gt;

&lt;p&gt;The generalization: any LLM compression step that maps "speech-act varieties" (decisions, hypotheses, questions, jokes, hedges) onto a single typed structure — like a &lt;code&gt;facts[]&lt;/code&gt; array — loses the modal axis. To preserve it, you'd need to compress into a &lt;em&gt;richer&lt;/em&gt; schema (with &lt;code&gt;kind: 'decision' | 'option' | 'question'&lt;/code&gt; per item), and you'd need the compression model to reliably tag the modality. Haiku 4.5 didn't tag it. Whether a more careful prompt or schema would is an open question, but it's a &lt;em&gt;design&lt;/em&gt; question the current tool doesn't even pose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Measured Findings, Versioned to v13.2.0
&lt;/h2&gt;

&lt;p&gt;In one place, six things I measured. Versioned because tool behavior changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replaces, not augments.&lt;/strong&gt; Install sets &lt;code&gt;CLAUDE_CODE_DISABLE_AUTO_MEMORY=1&lt;/code&gt;. Built-in memory is turned off. Default deployment is single-system, not hybrid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial sandbox.&lt;/strong&gt; &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; redirects plugin config, but the data store path &lt;code&gt;~/.claude-mem&lt;/code&gt; is hardcoded. Multi-tenant isolation is incomplete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression runs on your subscription.&lt;/strong&gt; Haiku 4.5 compresses observations to ~5,150 tokens per session, billed to &lt;em&gt;your&lt;/em&gt; Anthropic quota. Free tools that consume your paid quota deserve a footnote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invisible to headless mode.&lt;/strong&gt; &lt;code&gt;claude -p&lt;/code&gt; runs (non-interactive) emit &lt;em&gt;zero&lt;/em&gt; capture events in my tests. The lifecycle hooks fire only in interactive sessions. CI users and automation pipelines get no memory at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression flattens modality&lt;/strong&gt; (the sharpest finding, detailed above). A hedge becomes a flat fact, indistinguishable from a firm decision in the compressed &lt;code&gt;facts[]&lt;/code&gt; schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token economics lose on small projects.&lt;/strong&gt; ~6,700 tokens per recall cycle (passive inject + mcp-search round-trip + Haiku compression) versus ~280 deterministic, 100%-faithful tokens for the CLAUDE.md baseline. On a 1,000-line project, the per-token cost gap is wider than any retrieval benefit it provides.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tool-version drift is real — by the time you read this, some of these may have been fixed. The cache-coherence framing in the next section is version-independent and was the actual reason I wrote the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is a Cache Problem, Precisely
&lt;/h2&gt;

&lt;p&gt;The distributed-systems vocabulary for this design is &lt;em&gt;materialized view of a source&lt;/em&gt;, &lt;em&gt;served from a similarity cache&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-time lossy transform&lt;/strong&gt; = a materialized view that can drift from the source of truth (the actual codebase, the actual decisions). The source is the user's intent and the live code; the view is the compressed facts/narrative. Each write step can lose information that the view will never recover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-time ANN&lt;/strong&gt; = approximate retrieval. Top-K nearest neighbors. False positives are structural, not a bug — a sufficiently-similar wrong row will be returned with confidence indistinguishable from the right one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No coherence with the source.&lt;/strong&gt; Classical caches have &lt;strong&gt;invalidation hooks&lt;/strong&gt; — write-through, write-back, snoop protocols, MESI states. They tie cache lines back to the canonical source so that writes propagate and stale lines get evicted or rewritten. &lt;code&gt;claude-mem&lt;/code&gt; has &lt;em&gt;no&lt;/em&gt; tie to the codebase or to user-issued corrections. You reverse a decision in conversation, the memory still believes the original.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staleness without expiry.&lt;/strong&gt; Even without explicit invalidation, classical caches use TTLs to bound staleness. &lt;code&gt;claude-mem&lt;/code&gt; has no TTL on facts. A fact written six months ago competes for retrieval with one written yesterday, and the older one might win the vector hop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the cache-coherence frame is the right frame, the literature is rich and useful. Pat Helland's &lt;em&gt;Immutability Changes Everything&lt;/em&gt; (ACM Queue, 2015) and the broader databases-and-OS literature on cache-coherence protocols (MESI / MOESI), materialized-view invalidation, and write-through vs write-back are the right starting reading. The trades they describe — staleness vs cost, eventual vs strong coherence, when to flush, when to invalidate — are the &lt;em&gt;same trades&lt;/em&gt; the agent-memory community is rediscovering with fresh names.&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%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBzcmNbIlNvdXJjZSBvZiB0cnV0aCJdCiAgICAgICAgczFbIlVzZXIgaW50ZW50PGJyLz5MaXZlIGNvZGU8YnIvPkNvbnZlcnNhdGlvbiJdCiAgICBlbmQKICAgIHN1YmdyYXBoIHdyaXRlWyJXcml0ZSBwYXRoIChsb3NzeSkiXQogICAgICAgIHcxWyJIYWlrdSA0LjU8YnIvPmNvbXByZXNzaW9uIl0KICAgICAgICB3MlsiZmFjdHNbXSDCtyBuYXJyYXRpdmU8YnIvPihubyBtb2RhbGl0eSwgbm8gcHJvdmVuYW5jZSkiXQogICAgZW5kCiAgICBzdWJncmFwaCBzdG9yZVsiTWF0ZXJpYWxpemVkIHZpZXciXQogICAgICAgIHN0MVsiU1FMaXRlIEZUUzUiXQogICAgICAgIHN0MlsiQ2hyb21hIHZlY3RvcnMiXQogICAgZW5kCiAgICBzdWJncmFwaCByZWFkWyJSZWFkIHBhdGggKGFwcHJveGltYXRlKSJdCiAgICAgICAgcjFbInF1ZXJ5IHZlY3Rvcjxici8-KyBrZXl3b3JkIl0KICAgICAgICByMlsidG9wLUsgaGl0cyJdCiAgICBlbmQKCiAgICBzMSAtLT58aG9vayBjYXB0dXJlc3wgdzEKICAgIHcxIC0tPiB3MgogICAgdzIgLS0-IHN0MQogICAgdzIgLS0-IHN0MgogICAgc3QxIC0tPiByMgogICAgc3QyIC0tPiByMgogICAgcjEgLS0-IHIyCiAgICByMiAtLT58ImluamVjdGVkIGFzIGZhY3QifCBzMQoKICAgIGNsYXNzRGVmIHNyYyBmaWxsOiNlNmZmZmEsc3Ryb2tlOiMzMTk3OTUKICAgIGNsYXNzRGVmIGxvc3N5IGZpbGw6I2ZlZDdkNyxzdHJva2U6I2M1MzAzMAogICAgY2xhc3NEZWYgc3RvcmUgZmlsbDojZmVmNWU3LHN0cm9rZTojYjc3OTFmCiAgICBjbGFzc0RlZiByZWFkIGZpbGw6I2ViZjRmZixzdHJva2U6IzVhNjdkOAogICAgY2xhc3MgczEgc3JjCiAgICBjbGFzcyB3MSx3MiBsb3NzeQogICAgY2xhc3Mgc3QxLHN0MiBzdG9yZQogICAgY2xhc3MgcjEscjIgcmVhZA%3D%3D" 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%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBzcmNbIlNvdXJjZSBvZiB0cnV0aCJdCiAgICAgICAgczFbIlVzZXIgaW50ZW50PGJyLz5MaXZlIGNvZGU8YnIvPkNvbnZlcnNhdGlvbiJdCiAgICBlbmQKICAgIHN1YmdyYXBoIHdyaXRlWyJXcml0ZSBwYXRoIChsb3NzeSkiXQogICAgICAgIHcxWyJIYWlrdSA0LjU8YnIvPmNvbXByZXNzaW9uIl0KICAgICAgICB3MlsiZmFjdHNbXSDCtyBuYXJyYXRpdmU8YnIvPihubyBtb2RhbGl0eSwgbm8gcHJvdmVuYW5jZSkiXQogICAgZW5kCiAgICBzdWJncmFwaCBzdG9yZVsiTWF0ZXJpYWxpemVkIHZpZXciXQogICAgICAgIHN0MVsiU1FMaXRlIEZUUzUiXQogICAgICAgIHN0MlsiQ2hyb21hIHZlY3RvcnMiXQogICAgZW5kCiAgICBzdWJncmFwaCByZWFkWyJSZWFkIHBhdGggKGFwcHJveGltYXRlKSJdCiAgICAgICAgcjFbInF1ZXJ5IHZlY3Rvcjxici8-KyBrZXl3b3JkIl0KICAgICAgICByMlsidG9wLUsgaGl0cyJdCiAgICBlbmQKCiAgICBzMSAtLT58aG9vayBjYXB0dXJlc3wgdzEKICAgIHcxIC0tPiB3MgogICAgdzIgLS0-IHN0MQogICAgdzIgLS0-IHN0MgogICAgc3QxIC0tPiByMgogICAgc3QyIC0tPiByMgogICAgcjEgLS0-IHIyCiAgICByMiAtLT58ImluamVjdGVkIGFzIGZhY3QifCBzMQoKICAgIGNsYXNzRGVmIHNyYyBmaWxsOiNlNmZmZmEsc3Ryb2tlOiMzMTk3OTUKICAgIGNsYXNzRGVmIGxvc3N5IGZpbGw6I2ZlZDdkNyxzdHJva2U6I2M1MzAzMAogICAgY2xhc3NEZWYgc3RvcmUgZmlsbDojZmVmNWU3LHN0cm9rZTojYjc3OTFmCiAgICBjbGFzc0RlZiByZWFkIGZpbGw6I2ViZjRmZixzdHJva2U6IzVhNjdkOAogICAgY2xhc3MgczEgc3JjCiAgICBjbGFzcyB3MSx3MiBsb3NzeQogICAgY2xhc3Mgc3QxLHN0MiBzdG9yZQogICAgY2xhc3MgcjEscjIgcmVhZA%3D%3D" alt="flowchart LR" width="1357" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's missing from this diagram, and crucially: a backedge from "source of truth" to the materialized view that fires when the source changes. That's the &lt;strong&gt;invalidation arrow&lt;/strong&gt;. Its absence is the structural reason &lt;code&gt;claude-mem&lt;/code&gt; gets wrong-row retrieval on decisions the user has reversed. Until something supplies that arrow, the system is best understood as a write-only cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Each Wins
&lt;/h2&gt;

&lt;p&gt;Cost-curve thinking (the same frame used in the &lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;companion piece&lt;/a&gt;) gives a clean answer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lossless + Exact wins when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project size is small or scope is clear.&lt;/strong&gt; Curation is cheap; the manual upkeep budget is small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fidelity matters.&lt;/strong&gt; You need to recall the &lt;em&gt;exact&lt;/em&gt; decision, not a vibe of it. Coding agents, design decisions, security policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The author exists and is engaged.&lt;/strong&gt; Someone is willing to write three lines into &lt;code&gt;MEMORY.md&lt;/code&gt; when a decision is made.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lossy + Approximate wins when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;History is too big to hand-curate.&lt;/strong&gt; A year of conversations across multiple contributors, none of whom can be expected to maintain a &lt;code&gt;MEMORY.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage matters more than precision.&lt;/strong&gt; You'd rather have a fuzzy memory that something was discussed than no memory at all. Customer-support agents over a year of tickets; team retrospectives over a quarter of standups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost of acting on a fabricated fact is low.&lt;/strong&gt; A confident-wrong recall in a support agent says "sorry let me check"; the user corrects it. The same recall in a coding agent ships broken code to production. The blast radius of a false positive determines the budget for accepting one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: &lt;strong&gt;fuzzy-but-present beats precise-but-absent&lt;/strong&gt;, but only when the false-positive cost is low enough to absorb. For coding work on a 5,000-LOC project, it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Decision Framework
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Lossless + Exact&lt;/th&gt;
&lt;th&gt;Lossy + Approximate&lt;/th&gt;
&lt;th&gt;Hybrid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single project, &amp;lt; 50k LOC&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-project / multi-year history&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decisions need exact recall&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vague-but-present recall is acceptable&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author is engaged (willing to curate)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No human curator available&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of confident-wrong is high (production code, money)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of confident-wrong is low (suggestion, search)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You can pay the Haiku compression bill from your quota&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You operate headlessly or via CI&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most readers of this blog — engineers working on a single non-trivial codebase, where decisions matter and confident-wrong is expensive — the columns lean hard left.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Coherent Agent Memory System Would Need
&lt;/h2&gt;

&lt;p&gt;The interesting question, once you accept the cache-coherence frame, is: &lt;em&gt;what would the lossy + approximate corner look like if it were built like a real cache instead of a write-only one?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A short list of capabilities the current generation of "AI memory" tools is missing, and which any serious system in this space will eventually have to ship:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source pointer (provenance).&lt;/strong&gt; Every fact carries a back-pointer to the originating event: timestamp, session ID, the raw transcript turn or tool result it was derived from. Without this, you can't audit a wrong recall — you only see the fact, never its lineage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modality tagging.&lt;/strong&gt; Every fact tagged with epistemic status — &lt;code&gt;decision | option_considered | hypothesis | question | observation&lt;/code&gt; — at write time, by the compression model. Without this, the system loses what the failure section above showed: the difference between &lt;em&gt;we will&lt;/em&gt; and &lt;em&gt;we could&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supersedes / invalidation chain.&lt;/strong&gt; A later fact can declare an earlier fact superseded ("decision A was reversed on date T by B"). Recall surfaces the &lt;em&gt;latest applicable&lt;/em&gt; fact, not the most semantically similar one. This is the in-band invalidation classical caches use; agent memory currently has none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry / TTL by class.&lt;/strong&gt; Decisions might be permanent; observations rot fast ("the build was passing this morning" should not influence behavior at 4 PM). Different fact classes get different TTLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalidation hook tied to the source of truth.&lt;/strong&gt; When the underlying codebase or document changes in a way that contradicts a stored fact, the fact gets flagged for re-validation. This is the &lt;em&gt;write-through&lt;/em&gt; arrow in the cache diagram earlier — currently absent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence surfaced to the caller.&lt;/strong&gt; Instead of returning a flat string, return &lt;code&gt;{value, confidence, provenance}&lt;/code&gt;. The recalling agent then knows when to trust, when to double-check, when to ignore.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is novel. All of them are standard in production cache systems, query planners, and event-sourcing stores. They're hard to retrofit onto a system that wasn't designed with provenance and invalidation as first-class concerns. They're not hard to design in from the start — but doing so means giving up the "just drop a hook on everything, ship next week" simplicity that makes the current crop of tools accumulate stars.&lt;/p&gt;

&lt;p&gt;If you're building an agent that has to &lt;em&gt;act&lt;/em&gt; on its memory rather than just &lt;em&gt;display&lt;/em&gt; it, this list is the spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing — "Persistent Memory" Is a Trade, Not a Feature
&lt;/h2&gt;

&lt;p&gt;"Persistent AI memory" gets talked about like a feature you turn on. It isn't. It's a choice on the &lt;em&gt;two-axis&lt;/em&gt; design space above, and every position on that space has known failure modes. The lossless-and-exact corner has the upkeep cost and the coverage limit. The lossy-and-approximate corner has the staleness, the wrong-row retrieval, and — the finding I came away most surprised by — the loss of modality.&lt;/p&gt;

&lt;p&gt;Two takeaways worth carrying out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If someone is selling you "automatic AI memory," ask which quadrant.&lt;/strong&gt; If the answer is &lt;em&gt;lossy + approximate&lt;/em&gt;, ask the six questions from the section above: provenance, modality, supersedes, TTL, invalidation, confidence. If the answer to most of them is "the embeddings handle it," you're being sold a cache wearing the word &lt;em&gt;memory&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The classical literature is the right starting point.&lt;/strong&gt; Cache coherence, write-back vs write-through, eventual vs strong consistency, materialized-view invalidation — the database and OS communities have spent forty years working through these tradeoffs. Reading their writing is more useful than reading the latest agent-memory thread.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The companion to this post — &lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;&lt;em&gt;Agent Retrieval Is a Cost Curve Problem&lt;/em&gt;&lt;/a&gt; — argues a parallel thing about &lt;em&gt;within-session&lt;/em&gt; code retrieval: that Claude Code's "use grep, not RAG" choice isn't romance ("trust the model") but math (cost curves), and that the source code shows Anthropic A/B-testing alternative retrieval architectures (Explore vs Fork) in production. Read together, the two pieces add up to a coherent stance about Anthropic's bets across the &lt;em&gt;fidelity × retrieval&lt;/em&gt; design space: in both within-session code search and cross-session memory, the default is &lt;strong&gt;lossless + exact&lt;/strong&gt;, and the alternative branches are kept gated behind feature flags so the decisions can flip when the cost curves do. The memory side alone has at least four such gates visible in the snapshot I reviewed — &lt;code&gt;tengu_coral_fern&lt;/code&gt;, &lt;code&gt;tengu_herring_clock&lt;/code&gt;, &lt;code&gt;tengu_passport_quail&lt;/code&gt;, &lt;code&gt;tengu_slate_thimble&lt;/code&gt; — plus the build-time &lt;code&gt;KAIROS&lt;/code&gt;, &lt;code&gt;TEAMMEM&lt;/code&gt;, and &lt;code&gt;EXTRACT_MEMORIES&lt;/code&gt; gates in &lt;code&gt;src/memdir/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That stance is not "we trust the model." It's: read the cost curves, build for the curves' current shape, leave the toggles in for when they shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: How I Measured This
&lt;/h2&gt;

&lt;p&gt;For the reader who wants to reproduce — or, more usefully, who wants to know exactly what was and wasn't measured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versions and environment.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;claude-mem&lt;/code&gt; v13.2.0 (npm install via the project's standard install script)&lt;/li&gt;
&lt;li&gt;Claude Code: current public release at time of test (2026-05-20)&lt;/li&gt;
&lt;li&gt;macOS 25.4.0, zsh&lt;/li&gt;
&lt;li&gt;Sandbox: &lt;code&gt;CLAUDE_CONFIG_DIR=/tmp/cmem-test/dot-claude&lt;/code&gt; for the plugin config; data store at &lt;code&gt;~/.claude-mem&lt;/code&gt; (this directory location is hardcoded inside the tool — see Finding #2)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test project.&lt;/strong&gt;&lt;br&gt;
A small URL-shortener spec written from scratch, with four decisions in Session 1: (1) Redis for URL storage, (2) base62 short-code generation, (3) 30-day TTL, (4) the hedged sliding-expiration option that became the modality test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commands and protocol.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fresh &lt;code&gt;claude-mem&lt;/code&gt; install into the sandboxed &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt;. Verified install behavior set &lt;code&gt;CLAUDE_CODE_DISABLE_AUTO_MEMORY=1&lt;/code&gt; in &lt;code&gt;settings.json&lt;/code&gt; (Finding #1).&lt;/li&gt;
&lt;li&gt;Session 1: interactive Claude Code session, walked through the four decisions with the tool actively capturing via lifecycle hooks. Watched the Bun worker on &lt;code&gt;localhost:37701&lt;/code&gt; accept events.&lt;/li&gt;
&lt;li&gt;Session ended; session-boundary compression fired; Haiku-compressed &lt;code&gt;facts[]&lt;/code&gt; and &lt;code&gt;narrative&lt;/code&gt; written to SQLite-FTS5 + Chroma. Token count for the compression call read from the API trace.&lt;/li&gt;
&lt;li&gt;Session 2: fresh interactive session; queried for each of the four decisions; observed the recall path (mcp-search round-trip; ~22s extra latency).&lt;/li&gt;
&lt;li&gt;Compared retrieved content against the original Session 1 transcript byte-by-byte to identify the modality flattening (Finding #5).&lt;/li&gt;
&lt;li&gt;Repeated the install + Session 1 pattern in headless &lt;code&gt;claude -p&lt;/code&gt; mode to confirm Finding #4 (no events captured).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I'm explicitly &lt;em&gt;not&lt;/em&gt; claiming.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is a single test run on a deliberately small project. The N is 1.&lt;/li&gt;
&lt;li&gt;The CLAUDE.md baseline column in the test table is &lt;em&gt;not&lt;/em&gt; a separately measured comparison session. It reflects deterministic properties of the built-in &lt;code&gt;CLAUDE.md&lt;/code&gt; path (verbatim recall, no compression bill, no extra round-trip) that follow from the design — not a measured outcome.&lt;/li&gt;
&lt;li&gt;I didn't benchmark Chroma vector recall quality across many queries. The modality finding came from a single targeted probe (the TTL question); the cache-coherence framing predicts the same class of failure across many queries, but predicting and measuring are different.&lt;/li&gt;
&lt;li&gt;I tested v13.2.0. The tool is actively developed; specific findings may have been addressed in later releases by the time you read this. The cache-coherence framing is version-independent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Artifacts.&lt;/strong&gt; Session 1 / Session 2 transcripts, the raw &lt;code&gt;claude-mem&lt;/code&gt; SQLite + Chroma snapshots, and the token-cost API traces from the test run are kept locally and can be made available on reasonable request — get in touch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Companion piece: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/agent-retrieval-cost-curve-claude-code-grep-vs-rag/" rel="noopener noreferrer"&gt;Agent Retrieval Is a Cost Curve Problem: Why Claude Code Doesn't Use RAG&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;Background: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/consistency-scenarios-and-approaches-production/" rel="noopener noreferrer"&gt;Consistency in Distributed Systems: Scenarios, Trade-offs, and What Actually Works&lt;/a&gt;***&lt;br&gt;
&lt;em&gt;For the design rationale behind Claude Code's built-in memory in particular: *&lt;/em&gt;&lt;a href="https://harrisonsec.com/blog/claude-code-memory-first-principles-tradeoffs/" rel="noopener noreferrer"&gt;Claude Code Deep Dive Part 4: Why It Uses Markdown Files Instead of Vector DBs&lt;/a&gt;***&lt;/p&gt;

</description>
      <category>ai</category>
      <category>memory</category>
      <category>claudecode</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
