<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Butter Blog]]></title><description><![CDATA[Butter Blog]]></description><link>https://blog.butter.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1759297144070/538b4434-ea00-445a-bd30-09fdfd0b8ad7.png</url><title>Butter Blog</title><link>https://blog.butter.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 07:06:24 GMT</lastBuildDate><atom:link href="https://blog.butter.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[On Automatic Template Induction for Response Caching]]></title><description><![CDATA[As of last week, Butter’s proxy now offers automatic template induction for its response cache! We’ve prepared the following blog post to help explain its significance and potential to help you serve more LLM responses from cache. You can also read t...]]></description><link>https://blog.butter.dev/on-automatic-template-induction-for-response-caching</link><guid isPermaLink="true">https://blog.butter.dev/on-automatic-template-induction-for-response-caching</guid><category><![CDATA[templates]]></category><category><![CDATA[caching]]></category><category><![CDATA[agents]]></category><category><![CDATA[proxy]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Wed, 07 Jan 2026 04:51:07 GMT</pubDate><content:encoded><![CDATA[<p>As of last week, Butter’s proxy now offers automatic template induction for its response cache! We’ve prepared the following blog post to help explain its significance and potential to help you serve more LLM responses from cache. You can also read through our <a target="_blank" href="https://docs.butter.dev/concepts/template-induction">documentation on template-induction</a>.</p>
<hr />
<p><a target="_blank" href="https://butter.dev">Butter</a> is a cache for LLM responses, sitting as an HTTP proxy between clients and LLM inference endpoints.</p>
<p>One of Butter’s central goals is to develop a system of serving LLM responses from cache in a way that is:</p>
<ul>
<li><p><em>Fast</em> at the time of request,</p>
</li>
<li><p><em>Accurate</em> enough to avoid both false positives and false negatives, and</p>
</li>
<li><p><em>Powerful</em> enough to achieve a high cache hit rate.</p>
</li>
</ul>
<p>Our main strategy for doing so is via <em>template-aware response caching</em>, something we discussed in <a target="_blank" href="https://blog.butter.dev/template-aware-caching">an earlier post</a>. We’ll cover it again here, as well as talk about the challenges involved in automating it.</p>
<p>Rather than storing user-agent messages verbatim, a template-aware response cache stores templated messages, or <em>templates</em>. This allows messages in the cache to generalize, thanks to the introduction of variable placeholders.</p>
<p>A message is considered an instance of a template if that template could be populated (that is, <em>hydrated</em>) according to some <em>bindings</em> which specify the hard values to substitute for each template variable.</p>
<p>See the figure below for a simple example of inducing a template and bindings from a query.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760564756/261f4b0f-42e1-4c3f-97f3-599896ad64c1.png" alt class="image--center mx-auto" /></p>
<p>Templates lend themselves to expanding the reach of Butter’s cache. Currently, when Butter receives a new query, it attempts to match that query to all the available templates by direct, syntactic comparison (i.e., by comparing against an appropriate regex pattern).</p>
<p>Below, we illustrate how this syntactic comparison works on a followup query which matches the template we induced in the previous figure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760572796/fceed433-0d95-4475-b71f-bdec8c1c291f.png" alt class="image--center mx-auto" /></p>
<p>Importantly, this kind of templated matching algorithm employed by Butter is both deterministic and syntactic, avoiding calling extra language models during request time. We continue to create powerful templating and data transformation tools which ensure users’ request-time hotpaths to be served truly deterministically and LLM-free.</p>
<h2 id="heading-butters-cache-is-a-tree">Butter’s Cache is a Tree</h2>
<p>We should take a moment to clarify how Butter organizes its response cache (and hence, how it serves from cache). For simplicity, we’ll treat all messages in a user-agent interaction as if they either come from the user or the agent; ignoring tool calls and system prompts.</p>
<p>Butter’s caching is tailored to the turn-based structure of user-agent interactions: user queries are met with assistant responses, constituting a single “turn” of the interaction. Models like GPT-4o or Sonnet 4.5 are technically <em>stateless</em> between requests. So, in order for second, third, or later turns to operate under the context of prior turns, the user must make sure to pass along this prior context along with their latest request, all appended together in chronological order. We’ll call this style of managing context: <em>append-only</em>.</p>
<p>Append-only context management is the de-facto default for facilitating messages between users, agents, and tools, and is expected in the now-standardized Open AI Chat Completions format. Note that newer APIs such as Responses handles appending for you.</p>
<p>Under the append-only context management style, Butter’s cache may be thought of as a tree: each node in the tree is a new message in the thread; and distinct branches may spawn from the same node whenever Butter encounters distinct ways of continuing on from a shared context.</p>
<p>This helps us understand what happens when Butter “compares to cache:” It begins at the top-level of the tree and looks for a template matching with the first message in the context. It then seeks a child template of that node matching the second message, etc. Eventually, this process ends whenever a matching child cannot be found, or once it’s exhausted the context. If Butter indeed has the full context stored in the tree, it is ready to serve the corresponding response from cache.</p>
<p>Below is an example of how Butter compares an incoming query with context to its own cache. In this case, we observe a full cache hit.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760584121/8f110c0a-9ac7-4d7c-a9ee-d28d533196b6.png" alt class="image--center mx-auto" /></p>
<p>Beyond the placeholders, the contents of a template essentially capture the structural content of a query: the tokens that inform the model <em>how</em> to respond to the query. Under template-aware response caching, differences in structural content are exactly what trigger branching in Butter’s tree. And so, diverging paths can really be interpreted as distinct workflows followed by agents. Extracting the structural content of a query is thus central to Butter’s caching process. Let us now see what stands in the way of doing this.</p>
<h2 id="heading-noise">Noise</h2>
<p>LLM-based agents operate in messy environments. Their context may get cluttered with extraneous information or artifacts which do not serve in performing tasks. We’ll call these artifacts: <em>noise</em>.</p>
<p><strong>Noisy Query:</strong></p>
<pre><code class="lang-plaintext">&lt;?xml version="1.0"?&gt; &lt;div&gt;&lt;/div&gt;

&lt;!-- output --&gt;

[2024-10-17 14:32:01] User logged in

====================

[AD] Special offer! &lt;div&gt;&lt;/div&gt;
</code></pre>
<p>Noisy environments pose a real obstacle to cache-based responding. If Butter caches a noisy query, there is little chance that a later instance of that query will contain exactly the same noise as the former, impeding Butter’s ability to recognize it as a cache hit.</p>
<p>Ideally, Butter’s cache stores the “ideal” (i.e., not noisy) version of the query, and any incoming query gets “de-noised” before getting compared to Butter’s cache.</p>
<p><strong>Possible De-Noised Query:</strong></p>
<pre><code class="lang-plaintext">[2024-10-17 14:32:01] User logged in

Special offer!
</code></pre>
<p>One could identify certain punctuation, whitespace, or HTML tags as noisy, and manage to detect them by purely syntactic means. E.g., construct an appropriate regex pattern and filter all instances of these tokens from the query. We could use such noise detectors to <em>syntactically</em> filter out any noise present in queries.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Example: filter out any instance of the HTML tag &lt;!DOCTYPE ...&gt;</span>

noisy_text = <span class="hljs-string">"&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;body&gt;Hello&lt;/body&gt;&lt;/html&gt;"</span>

regex_pattern = <span class="hljs-string">r'&lt;!DOCTYPE[^&gt;]*&gt;'</span>
denoised_text = re.sub(regex_pattern, <span class="hljs-string">""</span>, noisy_text)

<span class="hljs-keyword">assert</span> denoised_text == <span class="hljs-string">"&lt;html&gt;&lt;body&gt;Hello&lt;/body&gt;&lt;/html&gt;"</span>
</code></pre>
<p>Together, a de-noised query would ultimately be the form in which Butter prepares queries for comparison with (and storage into) its cache.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760642880/c7a17b51-f79f-43da-b9a2-b27d846c469e.png" alt class="image--center mx-auto" /></p>
<p>We can now show exactly how Butter serves from cache: once we find a template which matches the (de-noised) query, Butter syntactically deduces the bindings that would make this template hydrate to the query. Then, it may use those deduced bindings to hydrate the cached response template, yielding a full response.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760648939/f33ae6fa-8e0b-42cc-a12e-faee8987cfcf.png" alt class="image--center mx-auto" /></p>
<p>Understanding noise to be any information which is not relevant to completing a task, we should point out that noise may be context-dependent. Exhibit A: <strong>timestamps</strong>.</p>
<p>Suppose a computer-use agent includes timestamps with every browser event or interaction it witnesses. Many of the workflows developed by the agent will not be time-dependent: the same “Submit” button that was clickable on <code>2025-11-10T11:34:00</code> should still be present on <code>2025-11-10T11:34:01</code>. So, these timestamps included in the response only muddy the context and prevent the corresponding cache entry from applying to future instances of the same workflow.</p>
<p>However, some other timestamps <em>could</em> indeed be relevant to the agent’s task. Any workflow that distinguishes between a weekday and the weekend, or between morning and night, will require some type of timestamp in order to proceed in its flow logic. Therefore, it is not appropriate to syntactically filter out timestamps indiscriminately.</p>
<p>Context-dependent noise (otherwise called <em>semantic noise</em>) requires more sophisticated methods to detect and discern. So, for now at Butter, we choose <em>not</em> to apply any syntactic de-noising, and pass off the job of semantic de-noising until <a target="_blank" href="https://www.notion.so/On-Automatic-Template-Induction-for-Response-Caching-29a96babc1b28013b9ead23a461b960a?pvs=21"><strong>Variable Induction</strong></a>, below.</p>
<h2 id="heading-template-induction">Template Induction</h2>
<p>We imagine every user query as begging for an LLM response. A proper response should make use of any relevant information found in that query (as well as in any previous context). Surely, not <em>all</em> content in the query is guaranteed to be relevant to responding. Templates should be robust to any of this irrelevant content. Moreover, some content in the query might indeed be relevant to <em>answering</em> the query, but not relevant towards deciding <em>how</em> to produce an answer to the query.</p>
<p>With this in mind, before Butter may add a message to the cache, we ask that it split the message into structural content (the template) and dynamic content (the bound variables). We sometimes describe this as <em>separating data from code</em>, or more concretely as performing <em>template induction</em>. [See here for <a target="_blank" href="https://bramble-recess-9ca.notion.site/why-induction?source=copy_link">Why “Induction?”</a>]</p>
<p>Any information which may be abstracted out and bound to a variable (without affecting the workflow’s logic) acts like <em>data</em> in the message, whereas the rest comprises the <em>code</em> of the message.</p>
<ul>
<li><p><strong>Data (dynamic content)</strong>: tokens which are essential towards building a response but not essential towards deciding an algorithm for generating the response.</p>
</li>
<li><p><strong>Code (structural content)</strong>: tokens which are essential for fixing a method for responding to the query.</p>
</li>
</ul>
<p>Entirely structural messages might look like:</p>
<pre><code class="lang-plaintext">Find the topmost element on the page and interact with it.
</code></pre>
<p>Templating parts of the above query wouldn’t make sense, since any changes would likely impact the workflow chosen by the agent for completing the task. For example, with a few swaps, the above command could have instead looked like: <code>Anthropomorphize the largest icon on the page and argue with it.</code></p>
<p>Whereas highly dynamic messages might look like:</p>
<pre><code class="lang-plaintext">Send Erik at erik@butter.dev the message: "Hey, nice to see you!"
</code></pre>
<p>Where portions like <code>Erik</code> and <code>erik@butter.dev</code> and <code>”Hey, nice to see you!”</code> are mostly safe to templatize: most replacements maintain the same response method: to try to send an email containing some contents to some address and named recipient.</p>
<p>Structural messages require no extra templating before getting stored into Butter’s cache. It is the dynamic content which needs to be detected and get bound to variables.</p>
<p>Thus, template induction boils down to variable induction.</p>
<h3 id="heading-variable-induction">Variable Induction</h3>
<p>We’ve just mentioned how variables are useful as placeholders for dynamic content in a message.</p>
<p>But variables also serve a second purpose: as placeholders for semantic noise. This way, only some of the variables specified in the bindings may be useful for generating responses. The rest “mask out” any irrelevant information that can’t be screened syntactically from the message.</p>
<p>It is also possible that what registers as semantic noise at one turn may become relevant for responding in subsequent turns. So, it’s good that we keep semantic noise around in the bindings even if it isn’t useful presently.</p>
<p>Luckily, unlike in the case of syntactic de-noising, we can afford to employ some (slower) semantic analyses when inferring the variables of a message. This is because the caching process happens asynchronously from request time:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767760925898/7ae085dc-b20d-48c8-8729-9c439320eabb.png" alt class="image--center mx-auto" /></p>
<p>Variable induction asks “What to include in the bindings?” We’ll see below how our desire to more generally respond from cache—as well as our need to hydrate templates into full responses—restrict how we may do this.</p>
<h3 id="heading-bindings">Bindings</h3>
<p>The bindings carry all of the variable assignments. Naïvely, we could hope that each unit of data be assigned to a variable via the form: <code>{{var}} ↦ literal</code>. But, consider the following example query:</p>
<pre><code class="lang-plaintext">Erik Dunteman works at Butter. Erik loves to code and to cook with butter.
</code></pre>
<p>The naïve approach might produce separate bindings for each piece of dynamic content:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>full_name</code></td><td>Erik Dunteman</td></tr>
</thead>
<tbody>
<tr>
<td><code>company</code></td><td>Butter</td></tr>
<tr>
<td><code>first_name</code></td><td>Erik</td></tr>
<tr>
<td><code>activity</code></td><td>code</td></tr>
<tr>
<td><code>ingredient</code></td><td>butter</td></tr>
</tbody>
</table>
</div><p>But we know <code>full_name</code> and <code>first_name</code> are not independent. We could even <em>derive</em> one’s first name from their full name:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>full_name</code></td><td>Erik Dunteman</td></tr>
</thead>
<tbody>
<tr>
<td><code>company</code></td><td>Butter</td></tr>
<tr>
<td><code>first_name</code></td><td><code>full_name.strip()[0]</code></td></tr>
<tr>
<td><code>activity</code></td><td>code</td></tr>
<tr>
<td><code>ingredient</code></td><td>butter</td></tr>
</tbody>
</table>
</div><p>That is, variables can be (syntactically and/or semantically) related. (But it’s not always obvious! Consider that the company “Butter” and the ingredient “butter” are nearly identical strings but semantically independent).</p>
<p>It is vital that we track these interdependencies, especially when a user or agent quietly applies a transformation to existing data in their message. For example:</p>
<pre><code class="lang-plaintext">**User**: On what day of the week did the 1900s start?
****
**Agent**: The first day of the 20th century was a Monday.
</code></pre>
<p>While the user asked about “the 1900s,” the agent distinctly referenced the “20th century.” If we wished to treat the century as dynamic content in this exchange, we would have to explain how “20th” derives from “1900s.” Otherwise, the workflow would fail to properly generalize to other time periods.</p>
<p>So, our bindings might not only be storing literal assignments to some named variables. Instead, they may contain code specifying how to derive its value from other variables’ values. In practice, we make use of a coding agent to generate the code for all such derivations, and verify/sandbox that code appropriately. Importantly, a derivation must return a literal value given literal assignments for all its arguments.</p>
<p>For example, consider the following prompt, from which we have inferred some dynamic content.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767761053444/ef80c2eb-8221-43dd-ad64-df375c220c8a.png" alt class="image--center mx-auto" /></p>
<p>We might propose a few inter-variable derivations where appropriate. For simplicity, we only provided the function signatures of each of the proposed derivations below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767761060363/e44cabea-b86b-4f6b-8245-f501fda62bda.png" alt class="image--center mx-auto" /></p>
<p>In order for this system of interdependent derivations to always be resolvable, the bindings must satisfy a <em>DAG</em> (directed, acyclic graph) structure, since no variable should have a derivation implicitly depending on itself! The nodes of the bindings DAG would consist of all the bound variables. And a node <code>x</code> connects to another node <code>y</code> in this DAG if the variable corresponding to <code>x</code> is used in the derivation of the variable corresponding to <code>y</code>. In our example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767761072556/1f5875d7-6e7e-47b7-800a-83dfa2ab886a.png" alt class="image--center mx-auto" /></p>
<p>Any finite DAG will possess at least one root node (i.e., a node having no “incoming” arrows), meaning our bindings should always have at least one variable which does not depend on any other variables in order to be hydrated. So-called <em>independent variables</em> are necessarily bound to literals: <code>{{independent var}} ↦ literal</code>. The remaining <em>dependent variables</em> depend on other variables in order to be hydrated, and are thus bound to derivations treating those free variables as arguments.</p>
<p>Given a bindings DAG, it is straightforward to hydrate a template making use of variables from those bindings: we use topological sort to fix a <em>hydration order</em> for the variables in the bindings, starting with the independent variables, then any variables depending only on those independent ones, and so on. We hydrate all the variables to literal values, and then populate those values into the template.</p>
<h2 id="heading-automating-template-induction">Automating Template Induction</h2>
<p>Recall that whenever Butter observes novel contexts, it forwards the request along to the provider, and then asynchronously attempts to add the query-response pair as templates into the cache:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767761131910/37cb7a39-3c48-4630-b2ec-f31e353e1741.png" alt class="image--center mx-auto" /></p>
<p>Our job at Butter is to automate the template induction process so we may respond to a variety of requests from cache. What might this take?</p>
<p>Separating data from code appears infeasible by purely deterministic means. In general, queries specified in natural language require some level of intelligence or high-level reasoning in order to be templated. [See my <a target="_blank" href="https://blog.butter.dev/template-aware-caching">previous post</a> for some examples.]</p>
<p>That is, we expect automatic template induction to be a harder task than simply implementing a syntactic pattern matcher, grammar constructor, or text-embedding.</p>
<p>In particular, we should have a robust system for extracting variable data from messages.</p>
<h3 id="heading-possible-induction-algorithm">Possible Induction Algorithm</h3>
<p>For instance, one could break this task down into the following algorithm:</p>
<p><strong>Algorithm for Inducing Variables</strong>:</p>
<ol>
<li><p><strong>Set up Bindings</strong> [<em>deterministic</em>]: Inherit any bindings that may have been inferred from messages earlier in the query’s context. Otherwise, start with empty bindings.</p>
</li>
<li><p><strong>Identify Dynamic Content</strong> [<em>classification task</em>]: Identify any substrings of this message which should qualify as dynamic content (either as data or as semantic noise).</p>
</li>
<li><p><strong>Label</strong> <strong>Dynamic Content</strong> [<em>naming task</em>]: Propose semantically-relevant variable names for these substrings (consistent with any inherited variable names).</p>
</li>
<li><p><strong>Arrange all Variables</strong> [<em>reasoning task</em>]: Fixing all inherited bindings as independent/literally bound, incorporate any newly-induced variables to arrange all variables into a DAG structure.</p>
</li>
<li><p><strong>Derive each Variable</strong> [<em>code-gen task</em>]: For each dependent variable in the DAG, propose the code relevant to deriving that variable from its arguments.</p>
</li>
</ol>
<p>Each of the above steps involving intelligence (i.e., Identify, Label, Arrange, and Derive) will require slightly different skills, and hence could be handled by distinct models and approaches. We might set up specialized agents called the <strong>Identifier</strong>, <strong>Namer</strong>, <strong>Arranger</strong>, and <strong>Deriver</strong>, to accomplish each step, respectively.</p>
<p>One of our prerogatives is ensuring that such a pipeline for performing variable induction is not prohibitively expensive: e.g., minimizing the cost and compute required for any LLM call we make.</p>
<h3 id="heading-worked-examples">Worked Examples</h3>
<p>In the following demos, I show off some cute examples of Butter’s automatic template induction at work. It cleanly performs one-shot generalization for messages involving arithmetic, string manipulation, and form parsing.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/bMppsIgOc8U">https://youtu.be/bMppsIgOc8U</a></div>
<p> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ORDfPnk9rCA">https://www.youtube.com/watch?v=ORDfPnk9rCA</a></div>
<p> </p>
<h3 id="heading-possible-tweaks">Possible Tweaks</h3>
<p>The version of automatic template induction we’ve developed in this blog post insists on doing so <em>for every novel query-response pair</em>. Should this prove to be too expensive, we might still deploy a modified, few-shot version of template induction more cheaply. That is, Butter may begin by directly caching exact messages throughout its cache. Then, there might come a time that Butter decides it is worthwhile to attempt to merge many children under a common node into one or more templates. Merges will attempt to induce generic templates from several examples of query-response pairs. And merges might be triggered by reaching a critical number of children under a single node, or by judging the similarity between the existing examples by way of evidence generated by lightweight language models or other inexpensive methods.</p>
<p>We may further save on costs by taking advantage of <a target="_blank" href="https://platform.openai.com/docs/guides/prompt-caching">Prompt Caching</a> when designing the prompts used by the various semantic agents described above. That would involve front-loading their contexts with all the instructions that appear consistently across runs, and leaving the rest until the end of their prompts.</p>
<hr />
<p>We continue to shed weight from and calibrate our template induction pipeline. Expect to see more from us as we observe how much it benefits our users’ cache hit rates.</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0009]]></title><description><![CDATA[Happy Friday!
Nothing user-facing to report in this week’s changelog, so hang tight.
We continue to invest in internal tooling: evals, infra rewrite, and prepping last week’s automatic template induction POC for production.
For fun, here’s a shout-ou...]]></description><link>https://blog.butter.dev/changelog-0009</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0009</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[llm-agents]]></category><category><![CDATA[caching]]></category><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Sat, 13 Dec 2025 05:14:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765602703481/bc597a5d-13cb-432e-8127-2203195d501a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Happy Friday!</p>
<p>Nothing user-facing to report in this week’s changelog, so hang tight.</p>
<p>We continue to invest in internal tooling: evals, infra rewrite, and prepping last week’s <a target="_blank" href="https://blog.butter.dev/changelog-0008#heading-automatic-template-induction">automatic template induction POC</a> for production.</p>
<p>For fun, here’s a shout-out for some of the tools we’ve been using and loving:</p>
<ul>
<li><p><a target="_blank" href="http://bun.sh">Bun</a> for runtime and tests.</p>
</li>
<li><p><a target="_blank" href="http://e2b.dev">E2B</a> for TS sandboxing.</p>
</li>
<li><p><a target="_blank" href="http://braintrust.dev">Braintrust</a> for evals.</p>
</li>
<li><p><a target="_blank" href="https://ai-sdk.dev/docs/introduction">Vercel</a> AI SDK for structured generation.</p>
</li>
<li><p>And of course, <a target="_blank" href="http://butter.dev">Butter</a> for caching tests and evals - it’s really sped up our iteration cycles to have LLM requests return 20x faster.</p>
</li>
</ul>
<p>Stay tuned next week for more updates!</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0008]]></title><description><![CDATA[Welcome to Butter’s eighth changelog! Just like grandma on Thanksgiving morning, we’ve spent a whole lot of time cooking.
Starting with:
Gramma’s Recipe Book
As a Thanksgiving treat, we launched cookwithbutter.com, which uses Butter’s LLM response ca...]]></description><link>https://blog.butter.dev/changelog-0008</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0008</guid><category><![CDATA[General Programming]]></category><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[llm]]></category><category><![CDATA[caching]]></category><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Sat, 06 Dec 2025 00:21:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764976339290/693097a6-517a-4d6d-82f2-c681c2f8b1f5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Butter’s eighth changelog! Just like grandma on Thanksgiving morning, we’ve spent a whole lot of time cooking.</p>
<p>Starting with:</p>
<h2 id="heading-grammas-recipe-book">Gramma’s Recipe Book</h2>
<p>As a Thanksgiving treat, we launched <a target="_blank" href="https://cookwithbutter.com">cookwithbutter.com</a>, which uses Butter’s LLM response caching to help generate, and cache, popular holiday cooking recipes.</p>
<p>Look up popular pre-computed recipes, or try your own! Gramma wants all your favorite recipes, from standard <a target="_blank" href="https://cookwithbutter.com/?input=stuffing">stuffing</a> to creative <a target="_blank" href="http://cookwithbutter.com/?input=butter%2C%20covered%20in%20butter%2C%20with%20a%20butter%20garnish">butter, covered in butter, with a butter garnish</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764978034633/49aee0b9-5ecf-4d7b-b10d-746a7d88ae6c.png" alt class="image--center mx-auto" /></p>
<p>The remainder of our time has been focused on internal tech improvements, which are still in-progress so we’ll only briefly highlight them below:</p>
<h2 id="heading-data-engine-rewrite">Data Engine Rewrite</h2>
<p>Part necessity and part premature optimization, we’ve been rewriting our data backend to structure the cache tree in a much more efficient, S3-centric way.</p>
<p>This project, once completed, will include:</p>
<ul>
<li><p>As low as 0 round trips to S3 for serving hot-path responses.</p>
</li>
<li><p>Truly stateless servers able to horizontally replicate and achieve high availability.</p>
</li>
<li><p>The ability to say we “rewrote in Rust” (meme).</p>
</li>
</ul>
<h2 id="heading-automatic-template-induction">Automatic Template Induction</h2>
<p>The goal of <a target="_blank" href="https://blog.butter.dev/template-aware-caching">template-aware caching</a> is to expand the generalizability of cache entries by converting literal text messages into a more powerful composition of templates and dynamic variables.</p>
<p>Currently, this is a manual process, where users must explicitly flag their dynamic content in the <a target="_blank" href="https://docs.butter.dev/concepts/bindings">butter-bindings</a> request headers in order for those variables to be stripped into template placeholders. Powerful, but cumbersome, especially when agent intent isn’t known in at the time of programming.</p>
<p>Our goal is to make the process automatic, using hints such as attention values to map out which parts of the context window are noise (ignored), which are dynamic (templated), and which are structural (cached).</p>
<p>Two days ago, thanks to hard work from teammate Raymond, we’ve got our first proof-of-concept working end to end:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764979670492/88555229-d100-4977-ac13-5f2abbfecafa.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764979652084/5873d25b-cda5-4098-9a13-af2ad5c2dd21.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764979715689/36dcccd7-1852-4a5e-9edf-1b7b9b446cb4.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764979689783/817864d8-98c5-42ca-aa83-353a7be44559.png" alt class="image--center mx-auto" /></p>
<p>That’s it for this week! We’re excited to get this above work out in a more public form so you can see the magic of it.</p>
<p>Until then, keep on cooking!</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0007]]></title><description><![CDATA[Hi all, this week’s changelog is quick and simple, nothing user-visible to announce.
We’ve been making R&D progress towards better template-aware-caching, and infra progress rethinking the storage system to work with higher availability.
We’ve also p...]]></description><link>https://blog.butter.dev/changelog-0007</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0007</guid><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Sat, 22 Nov 2025 04:16:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763782480913/d2002b25-1393-4ba2-9e02-ab72f61d18df.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi all, this week’s changelog is quick and simple, nothing user-visible to announce.</p>
<p>We’ve been making R&amp;D progress towards better <a target="_blank" href="https://blog.butter.dev/template-aware-caching">template-aware-caching</a>, and infra progress rethinking the storage system to work with higher availability.</p>
<p>We’ve also published a <a target="_blank" href="https://github.com/butter-dot-dev/legal/blob/main/TOS.md">Terms of Service</a> and <a target="_blank" href="https://github.com/butter-dot-dev/legal/blob/main/PrivacyPolicy.md">Privacy Policy</a>.</p>
<p>Stay tuned next week for more updates!</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0006]]></title><description><![CDATA[Welcome to Butter’s latest weekly changelog. Today’s log is light, as we strengthen our focus on the R&D-side of templated caching.
This week, we’ve also seen some more signups, which have brought about a higher volume of requests through Butter’s pr...]]></description><link>https://blog.butter.dev/changelog-0006</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0006</guid><category><![CDATA[proxy]]></category><category><![CDATA[app]]></category><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Sat, 15 Nov 2025 02:59:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763174459919/691a267a-b727-43f1-b318-d79bb5fdc34f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Butter’s latest weekly changelog. Today’s log is light, as we strengthen our focus on the R&amp;D-side of templated caching.</p>
<p>This week, we’ve also seen some more signups, which have brought about a higher volume of requests through Butter’s proxy and impressive cache hit rates!</p>
<h2 id="heading-unsupported-request-handling">Unsupported Request Handling</h2>
<ul>
<li><p>Butter now detects and tracks requests with unsupported content (i.e., images, audio, files)</p>
</li>
<li><p>Unsupported requests are forwarded directly to the provider (preventing any downtime)</p>
</li>
<li><p>Butter’s app transparently shows this with a “bypassed” notice, storing none of the original message content</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Changelog #0005]]></title><description><![CDATA[Welcome to Butter’s fifth weekly changelog. This week, we’ve seen a promising amount of new signups, as well as have tightened up our onboarding and helped users check Butter’s status.
Onboarding Experience

Cleaned up the onboarding experience we de...]]></description><link>https://blog.butter.dev/changelog-0005</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0005</guid><category><![CDATA[onboarding]]></category><category><![CDATA[Status]]></category><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Sat, 08 Nov 2025 00:32:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762557081862/57de0184-d578-4866-a3e4-dd61b9f3f5d7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Butter’s fifth weekly changelog. This week, we’ve seen a promising amount of new signups, as well as have tightened up our onboarding and helped users check Butter’s status.</p>
<h2 id="heading-onboarding-experience">Onboarding Experience</h2>
<ul>
<li><p>Cleaned up the onboarding experience we designed last week to give better feedback.</p>
</li>
<li><p>Pointed more clearly to Butter’s documentation and app.</p>
</li>
</ul>
<h2 id="heading-status-page">Status Page</h2>
<ul>
<li>Set up a public-facing uptime monitor for Butter’s proxy at <a target="_blank" href="https://status.butter.dev">status.butter.dev</a>.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Changelog #0004]]></title><description><![CDATA[Welcome to Butter’s fourth weekly changelog. This week we celebrate our launch, with a focus on the first time onboarding experience.
Better Onboarding Experience

Added an onboarding flow to guide first-time users through their first cache miss and ...]]></description><link>https://blog.butter.dev/changelog-0004</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0004</guid><category><![CDATA[onboarding]]></category><category><![CDATA[landing page]]></category><category><![CDATA[documentation]]></category><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Sat, 01 Nov 2025 01:09:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761943594347/9074e97b-35bb-42b9-8968-86ac797d7c91.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Butter’s fourth weekly changelog. This week we celebrate our launch, with a focus on the first time onboarding experience.</p>
<h2 id="heading-better-onboarding-experience">Better Onboarding Experience</h2>
<ul>
<li>Added an onboarding flow to guide first-time users through their first cache miss and cache hit</li>
</ul>
<h2 id="heading-new-landing-page">New Landing Page</h2>
<ul>
<li><p>Shipped our new landing page, live at <a target="_blank" href="http://butter.dev">butter.dev</a></p>
</li>
<li><p>Users will find their dashboard (previously at <a target="_blank" href="http://app.butter.dev">app.butter.dev</a>) now merged into the main <a target="_blank" href="http://butter.dev">butter.dev</a> site behind the <a target="_blank" href="http://butter.dev/auth">Login</a> button</p>
</li>
</ul>
<h2 id="heading-minor-ui-fixes">Minor UI Fixes</h2>
<ul>
<li><p>Made integration guides more obvious in our documentation</p>
</li>
<li><p>Improved scroll through the Request preview</p>
</li>
<li><p>Fixed bug with Request pagination</p>
</li>
<li><p>Cleaned up pagination URL parameters</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Changelog #0003]]></title><description><![CDATA[Welcome to Butter’s third weekly changelog. This week we’ve been focused on content, documentation, and examples.
New Examples
Examples section added to Docs, highlighting how to repoint popular AI tools through the Butter proxy endpoint. These inclu...]]></description><link>https://blog.butter.dev/changelog-0003</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0003</guid><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Fri, 24 Oct 2025 19:00:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761330989332/1b88a163-5b62-48ca-992a-1082973cf008.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Butter’s third weekly changelog. This week we’ve been focused on content, documentation, and examples.</p>
<h2 id="heading-new-examples">New Examples</h2>
<p>Examples section added to <a target="_blank" href="https://docs.butter.dev">Docs</a>, highlighting how to repoint popular AI tools through the Butter proxy endpoint. These include:</p>
<ul>
<li><p>LLM Clients &amp; Agent Frameworks</p>
<ul>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/langchain">Langchain / Langgraph</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/vercel">Vercel AI SDK</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/mastra">Mastra</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/pydantic_ai">Pydantic AI</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/crew_ai">Crew AI</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/ai_suite">AI Suite</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/litellm#litellm-sdk">LiteLLM SDK</a></p>
</li>
</ul>
</li>
<li><p>Other HTTP proxies/gateways</p>
<ul>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/helicone">Helicone</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/litellm#litellm-proxy">LiteLLM</a></p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/martian">Martian</a></p>
</li>
</ul>
</li>
<li><p>Specialized Tools</p>
<ul>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/browser_use">Browser Use</a> (custom fork)</p>
</li>
<li><p><a target="_blank" href="https://docs.butter.dev/examples/dspy">DSPy</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-bugfixes-amp-improvements">Bugfixes &amp; Improvements</h2>
<ul>
<li><p>Fixed race condition which caused thrashing in server’s disk cache</p>
</li>
<li><p>Added internal load tests, currently hitting ~120rps</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[The Messy World of “Deterministic Agents”]]></title><description><![CDATA[With the daily use of “agentic” tools like Cursor and Claude Code, we’re all becoming deeply familiar with the feeling of working with autonomous agents, and increasingly aware of their shortcomings.
I’m here to talk about one of those shortcomings: ...]]></description><link>https://blog.butter.dev/the-messy-world-of-deterministic-agents</link><guid isPermaLink="true">https://blog.butter.dev/the-messy-world-of-deterministic-agents</guid><category><![CDATA[agents]]></category><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[Workflow Automation]]></category><category><![CDATA[agentic workflow]]></category><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Thu, 23 Oct 2025 20:26:26 GMT</pubDate><content:encoded><![CDATA[<p>With the daily use of “agentic” tools like <a target="_blank" href="https://cursor.com">Cursor</a> and <a target="_blank" href="https://www.claude.com/product/claude-code">Claude Code</a>, we’re all becoming deeply familiar with the feeling of working with autonomous agents, and increasingly aware of their shortcomings.</p>
<p>I’m here to talk about one of those shortcomings: <strong>non-determinism</strong>.</p>
<p>You’ve probably run into it yourself, it goes as follows:</p>
<ol>
<li><p>You have a relatively repeatable task to do.</p>
</li>
<li><p>The agent does it right the first time. Awesome!</p>
</li>
<li><p>You ask the agent to do that same task again, with slightly different input.</p>
</li>
<li><p>The agent chooses a dramatically different approach. Weird!</p>
</li>
</ol>
<p>This sense of randomness can be unsettling, and lead to a <strong>lack of trust in agentic systems</strong>, which is a strict hurdle to get over before widespread adoption. We want to think of agents as employees we can delegate to, but <strong>employees learn skills</strong> which builds trust, whereas agents do not. Yet.</p>
<h3 id="heading-what-well-be-covering">What we’ll be covering</h3>
<p>This blog is an overview of nine different attempts at introducing “determinism” (skills, trust, reliability, repeatability) into agents. These approaches range from nascent ideas and theoretical concepts to full products being used in the enterprise.</p>
<p>For each approach, I’ll be highlighting products or research that’s relevant. We’ll walk through them from highest abstraction down to the lowest abstraction.</p>
<p>Note that this coverage will include our own product <a target="_blank" href="https://butter.dev">Butter</a>, but it’s not my intention to shill our own thing or draw any “us-vs-them” comparisons. Those blogs always suck.</p>
<p>The goal is to inform, and paint a clear picture for the many ways this world could end up going.</p>
<p>Let’s start from the top!</p>
<h2 id="heading-workflow-builders">Workflow Builders</h2>
<p>Workflow Builders are Zapier-like canvases which allow technical and non-technical users to chain prebuilt integrations together, including LLM blocks for operations such as data transformation and classification-based routing.</p>
<p>They are <em>not</em> agents by the strict definition, but are embraced by the more cautious enterprise user due to their explainability and true determinism. No magic, just smarter data processing. If an agent block is included, it’s opt-in and generally well scoped.</p>
<p>These tools have exploded in usage, with <a target="_blank" href="https://n8n.io/">n8n</a> being a popular choice.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761249824044/c5190ba9-a77a-4266-9c19-b64ee61cef14.png" alt class="image--center mx-auto" /></p>
<p>And earlier this month, OpenAI launched their <a target="_blank" href="https://platform.openai.com/docs/guides/agents/agent-builder">Agent Builder</a> in the same category:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761249845666/deb03300-cf42-4299-98d7-bac9dbfaa0e1.png" alt class="image--center mx-auto" /></p>
<p>Following the OpenAI launch, many were disappointed to see a workflow builder use the “agent” name. Regardless of if it’s truly an agent, users are happy and workflow builders deserve their place in this blog as today’s most viable route to “determinism.”</p>
<p>You may have wondered why I said they’re not agents, which warrants a quick definition:</p>
<p><strong>An LLM agent runs tools in a loop to achieve a goal.</strong> – <a target="_blank" href="https://simonwillison.net/2025/Sep/18/agents/">Simon Willison</a></p>
<pre><code class="lang-python"><span class="hljs-keyword">while</span> task_incomplete:
    tool_choice = llm()
    do(tool_choice)
</code></pre>
<p>Under this definition, the control flow is dictated by the LLM, allowing agents to perform tasks they were never explicitly programmed to do.</p>
<p>All of the following approaches apply specifically to agents by this definition, exploring what it means to bring (pseudo)determinism into fundamentally random LLM branching decisions.</p>
<p>As we walk through them, remember:</p>
<ul>
<li><p>The architecture: there’s always a model, and it’s always choosing tools.</p>
</li>
<li><p>The goal: “determinism” is defined as the consistent reproduction of a trajectory of tool calls given a repeat task.</p>
</li>
</ul>
<h2 id="heading-context-engineering">Context Engineering</h2>
<p>Many view skills and repeatability as a <em>context</em> problem, focusing on the fact that an LLM performing an automation does not have the accumulated knowledge from prior runs.</p>
<p>So… some people simply put the successful run in the context.</p>
<p>Products in this camp still use LLMs at every turn, but they aim to increase the reliability and skill-retention by injecting additional content into the context window.</p>
<p>Dynamic context engineering is nothing new, tracing its lineage back to few-shot prompting (hardcoding examples into your prompt) and <a target="_blank" href="https://python.langchain.com/docs/concepts/rag/">RAG</a> (appending semantically relevant content into the prompt). Notable products in the space, dubbed “memory layers,” include <a target="_blank" href="https://github.com/mem0ai/mem0">mem0</a> and <a target="_blank" href="https://supermemory.ai/">Supermemory</a>.</p>
<p>The space has historically focused on user context, such as remembering birthdays, but applied to deterministic replay, a memory layer would be repurposed to store and retrieve content related to <strong>instructions &amp; behaviors,</strong> which may include:</p>
<ul>
<li><p>User preferences</p>
</li>
<li><p>User-written SOPs (standard operating procedures)</p>
</li>
<li><p>Recorded prior agent trajectories</p>
</li>
<li><p>An LLM-summarization of prior trajectories</p>
</li>
<li><p>Reasoning traces or summaries explaining why certain branches are taken.</p>
</li>
</ul>
<p>This does not force determinism by the strict definition, but it does <em>guide</em> the model.</p>
<h3 id="heading-explicit-skills">Explicit Skills</h3>
<p>One pattern is to build up a knowledge-base in advance, similar to an employee onboarding document, which may be selectively referenced by the agent during operation.</p>
<p>Most recently, you can see this approach used by Anthropic’s newly released <a target="_blank" href="https://www.anthropic.com/news/skills">Claude Skills</a>, which seems to be RAG across SOPs and docs.</p>
<blockquote>
<p>While working on tasks, Claude scans available skills to find relevant matches. When one matches, it loads only the minimal information and files needed—keeping Claude fast while accessing specialized expertise.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761250093971/ae149a96-8a54-4365-ad37-5c9f115d45c5.png" alt class="image--center mx-auto" /></p>
<p>These skills are generated in-advance by a user via interacting with a separate “skill-creator” agent. Doing so requires advance knowledge of the types of tasks your agents will perform.</p>
<h3 id="heading-learned-skills">Learned Skills</h3>
<p>The process of “skill creation” could be done after the fact, inferred from the message history.</p>
<p>You can see this in action with <a target="_blank" href="https://cursor.com/docs/context/memories">Cursor’s Memory</a> feature, which uses a special “save to memory” tool which it can invoke if it detects a behavior would be useful in the context of all future runs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761250124368/3fbfb045-ce01-4d04-bb09-a142efb2c512.png" alt class="image--center mx-auto" /></p>
<p>Another creative approach for learned skills is Letta’s <a target="_blank" href="https://docs.letta.com/guides/agents/sleep-time-agents">Sleep-Time Agents</a>, which use async agents to continuously overwrite earlier message history context with more compressed summaries, allowing agents to resume from their prior history rather than needing to start from fresh states.</p>
<h2 id="heading-code-generation">Code Generation</h2>
<p>With the goal of consistently reproducing tool calls, the most deterministic tool we could reach for is code itself.</p>
<p>Rather than using LLMs in the hot-loop, interpreting every case and choosing a tool to invoke, what if they were used more like compilers, generating optimized code in advance? LLMs are especially well suited for this, given strong programming language representation in their training sets.</p>
<p>In its most basic form, <strong>codegen could produce one-off disposable scripts</strong>, which when interpreted in our client environment would call tool functions directly.</p>
<p>This bypasses the indirection of the ToolCall response type, and allows a single LLM generation to invoke as many tools as it needs.</p>
<p>The team at <a target="_blank" href="https://www.cloudflare.com/">Cloudflare</a> recently launched “<a target="_blank" href="https://blog.cloudflare.com/code-mode/">Code Mode</a>” (great blog), and <a target="_blank" href="https://browser-use.com/">Browser Use</a> recently launched <a target="_blank" href="https://x.com/gregpr07/status/1981116900223701091">Code Use</a>, both of which do exactly this concept. The scripts are ephemeral, but the concept of calling tools from code is the building block for the next topic.</p>
<h3 id="heading-meta-tools">Meta-Tools</h3>
<p>Sometimes, the code generated to invoke multiple tools is worth storing as its own tool.</p>
<p>In doing this, we’re allowing the models to not only use tools, but to build their own abstractions, making each tool call decision to be that much more powerful.</p>
<p>There’s no common name for this, so we’ll just call them “meta-tools.”</p>
<p>What’s most worth highlighting here is how well it fits into the “agents are loops with tools” architecture. The model keeps using tools, it’s just that those tools perform increasingly long (and deterministic) tasks.</p>
<p>The universally agreed pioneer of this concept is the <a target="_blank" href="https://arxiv.org/abs/2305.16291">Voyager paper</a>, which used on-the-fly tool generation to evolve primitive Minecraft bot APIs into higher-abstracted tools:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761250210109/efcf9647-db4c-4478-853a-38a93879ba93.png" alt class="image--center mx-auto" /></p>
<p>Voyager was well ahead of its time, published in 2023, and there’s yet to be a clear follow-up paper or product that expands on it.</p>
<h3 id="heading-script-agent-fallback">Script-Agent Fallback</h3>
<p>Script-agent fallback refers to systems whose default operating mode is pure-software, with agent loops only being used for initial discovery and self-healing.</p>
<p>To generate these scripts, it’s usually done post-hoc, after seeing multiple examples. In these cases, you employ an agent (or human!) to perform a multi-step workflow, and use the tool-call trace from that to generate reusable scripts. Note that “generate” is loosely defined here, ranging from full-blown LLM codegen to simple json runbooks.</p>
<p>This approach is especially popular in computer automation, where humans can be there to describe the task, perform or monitor the learning runs, leave comments, and iterate on the produced scripts.</p>
<p>Stars in this space include <a target="_blank" href="https://www.director.ai/">Browserbase’s Director</a> (congrats on the recent <a target="_blank" href="https://x.com/pk_iv/status/1980653648310071663">v2 launch</a>) and <a target="_blank" href="https://browser-use.com/">Browser Use’s</a> <a target="_blank" href="https://github.com/browser-use/workflow-use">Workflow Use</a>.</p>
<p>We also experimented at this level, with a tool tracing and replay SDK called <a target="_blank" href="https://github.com/pig-dot-dev/muscle-mem">Muscle Mem</a>. Read <a target="_blank" href="https://erikdunteman.com/blog/muscle-mem">here for why we started it</a>, and <a target="_blank" href="https://blog.butter.dev/muscle-mem-as-a-proxy">here for why we moved on</a>.</p>
<p>Similar to the workflow builder UIs, script-agent fallback systems require you to know in advance which workflow you’re about to run. The branching behavior does not need to be known in advance, but the task does need to be discrete and namable.</p>
<h3 id="heading-script-generators">Script Generators</h3>
<p>This is “Lovable for Automations,” where technical or nontechnical users work with codegen agents to produce pure-software scripts. No agents at runtime.</p>
<p>This ahead-of-time generation can be quite tricky, as it’s hard to know in advance exactly which tools will get run in a particular workflow, but with enough iteration and end user feedback, you can get there.</p>
<p>Particularly creative approaches in this space even build DSLs to represent the automation, and force generation using custom grammars, reducing the surface area for error and hallucinations.</p>
<p>Notable teams in this space are <a target="_blank" href="https://withforge.com/">Forge</a> and <a target="_blank" href="https://www.sola.ai/">Sola</a>.</p>
<p>As with workflow builder UIs, it’s unclear if this fits under the strict definition of an agent, but worth shouting out, as these products tend to be happily adopted.</p>
<h2 id="heading-response-caching">Response Caching</h2>
<p>Response Caching means running an HTTP proxy in front of the LLM provider and caching responses as they flow through. On repeat requests, the cache can serve responses, as if a model had generated them, resulting in deterministic behavior.</p>
<p>By spoofing the LLM layer, the agent loop remains simple, unaware that the endpoint is guiding it down a deterministic path.</p>
<p>This is coincidentally what we’re building at <a target="_blank" href="http://Butter.dev">Butter.dev</a>.</p>
<p>To have any meaningful cache-hit rate (more than the rare exact context matches) you’d need to figure out how to correctly group subtly different prompts, identify dynamic data, ignore noisy contexts, handle complex conditional control flows, etc. Much is yet to be solved, which we’ve <a target="_blank" href="https://blog.butter.dev/template-aware-caching">written more about here</a>.</p>
<h2 id="heading-llm-layer-improvements">LLM-Layer Improvements</h2>
<p>The obvious VC question is “won’t the big labs just do this?”</p>
<p>Quite possibly! As taught in the <a target="_blank" href="https://en.wikipedia.org/wiki/Bitter_lesson">The Bitter Lesson</a>, one must not ignore the consistent trend that one-size-fits-all LLMs have always ended up superseding incremental progress made outside of the models.</p>
<p>If the goal is to make models more deterministic, surely there’s a way it could happen in the architecture of the models themselves.</p>
<p>I’m in no way an expert in these topics, and there’s probably some secret other thing being cooked up in the labs or a paper I haven’t seen, but here are a few model-level shoutouts.</p>
<h3 id="heading-action-models">Action Models</h3>
<p>Action Models are special language models where the decoder is trained to emit tool calls rather than tokens, allowing them to map input stimuli to actions without text as an intermediate.</p>
<p>These models are used heavily in robotics, where the specialized domain allows them to be a quite a bit smaller and runnable on-device.</p>
<p>Adjacent work has been done in computer use by the team at <a target="_blank" href="http://generalagents.com/ace/">General Agents</a>, resulting in shockingly fast fully-agentic computer automation:</p>
<blockquote>
<p>Ace leverages a new behavioral training paradigm. Unlike language and vision models which are trained on text and images, Ace is trained on behavior.</p>
</blockquote>
<h3 id="heading-reinforcement-learning">Reinforcement Learning</h3>
<p>Many process automation tasks have quick feedback for success/fail, which makes these domains optimal for using that feedback as a reward function in RL.</p>
<p>It’s quite early, but <a target="_blank" href="https://x.com/brendanh0gan/status/1923135962789364206">early tinkerers</a> are seeing promising results.</p>
<h1 id="heading-mapping-them-all-together">Mapping them all together</h1>
<p>Below are all of the topics we’ve discussed, highlighting how well each approach satisfies the important aspects of reliable “deterministic replay”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761250415193/5bcb0f3c-27e1-4a5a-a6b5-73f21690e935.png" alt class="image--center mx-auto" /></p>
<p>And below is a map showing where in the stack the approaches sit, and how explicitly tasks must be known in advance:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761250430183/44a1782f-2440-49e7-bfe9-010f5ab406de.png" alt class="image--center mx-auto" /></p>
<p>I trust this overview helps untangle the mess of different approaches, breaking them down into clearer sub-groups, so we all can make more informed tradeoffs in our own building.</p>
<p>Cheers!</p>
<p>Erik</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0002]]></title><description><![CDATA[Hi there! Welcome to our second weekly changelog.
Performance Improvement
Average cache hit response times are now 8.9x faster due to data caching on the server.
Fixes
Fixed a bug related to traversing cache entries with 100+ children.
We’re Hiring
W...]]></description><link>https://blog.butter.dev/changelog-0002</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0002</guid><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Sat, 18 Oct 2025 01:03:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760748324789/bbe635fa-2b66-4898-8535-04b41d590ae6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi there! Welcome to our second weekly changelog.</p>
<h2 id="heading-performance-improvement">Performance Improvement</h2>
<p>Average cache hit response times are now 8.9x faster due to data caching on the server.</p>
<h2 id="heading-fixes">Fixes</h2>
<p>Fixed a bug related to traversing cache entries with 100+ children.</p>
<h2 id="heading-were-hiring">We’re Hiring</h2>
<p><a target="_blank" href="https://docs.butter.dev/careers">We’re hiring a Systems Engineer</a> to work on the performance problems you see above.</p>
]]></content:encoded></item><item><title><![CDATA[Changelog #0001]]></title><description><![CDATA[Hi everyone! Welcome to our first of many weekly changelogs, set to run every Friday. Follow along for weekly updates and improvements.
This week was primarily focused on ensuring compatibility with upstream providers (OpenAI, for the time being) and...]]></description><link>https://blog.butter.dev/changelog-0001</link><guid isPermaLink="true">https://blog.butter.dev/changelog-0001</guid><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Fri, 10 Oct 2025 07:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760410464906/784e4d33-32e7-4f2e-86f2-c1a73913d149.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi everyone! Welcome to our first of many weekly changelogs, set to run every Friday. Follow along for weekly updates and improvements.</p>
<p>This week was primarily focused on ensuring compatibility with upstream providers (OpenAI, for the time being) and downstream agent frameworks.</p>
<h2 id="heading-features">Features</h2>
<h3 id="heading-brotli-compression-support">Brotli compression support</h3>
<p>Added support to handle large compressed responses from OpenAI.</p>
<h3 id="heading-transparent-forwarding">Transparent forwarding</h3>
<p>All requests to unsupported endpoints, such as Responses or Encodings, are now transparently forwarded without involving the cache. This allows popular agent libraries pointed at Butter proxy to operate as expected, with caching only on the Chat Completions requests.</p>
<h3 id="heading-request-pagination">Request pagination</h3>
<p>Dashboard home-page loads are now much faster for users with moderate request traffic.</p>
<h2 id="heading-security-amp-uptime">Security &amp; Uptime</h2>
<ul>
<li><p>Data access controls improved on UI backend</p>
</li>
<li><p>Uptime monitors and alerts configured</p>
</li>
</ul>
<h2 id="heading-fixes">Fixes</h2>
<ul>
<li><p>Various UI rendering/flickering bugs</p>
</li>
<li><p>Stale-segfault bug in proxy</p>
</li>
<li><p>Broken links in cache graph UI</p>
</li>
<li><p>Mobile reactivity improved*</p>
<ul>
<li>*with a message that says “use desktop please”</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Rethinking Muscle Mem as an LLM Proxy]]></title><description><![CDATA[I believe two things:

AI is an incredible technology,

AI will be used less* over the long term.


(*) proportionally speaking…
Over the last few months, I’ve been working on building “Muscle Memory for AI”: tooling that specifically removes AI from...]]></description><link>https://blog.butter.dev/muscle-mem-as-a-proxy</link><guid isPermaLink="true">https://blog.butter.dev/muscle-mem-as-a-proxy</guid><dc:creator><![CDATA[Erik Dunteman]]></dc:creator><pubDate>Wed, 01 Oct 2025 19:25:44 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-i-believe-two-things">I believe two things:</h3>
<ol>
<li><p><strong>AI is an incredible technology,</strong></p>
</li>
<li><p><strong>AI will be used <em>less</em>* over the long term.</strong></p>
</li>
</ol>
<p>(*) <em>proportionally speaking…</em></p>
<p>Over the last few months, I’ve been working on building “Muscle Memory for AI”: tooling that specifically removes AI from places where deterministic scripts would serve just fine.</p>
<p>In May, I launched a Python package called <code>Muscle Mem</code>.</p>
<p>Titled <em>A Behavior Cache for Agents</em>, <code>Muscle Mem</code> <strong>was an SDK for instrumenting and replaying sequences of tool calls</strong>.</p>
<p>It’d bolt on top of your existing agents as decorators on their tools, running snapshots every time the tool was invoked to record not only the action taken, but the environment in which it was taken (for safe cache validation). For more context such as its use cases, API design, and a demo, <a target="_blank" href="https://erikdunteman.com/blog/muscle-mem/">the original launch blog</a> is worth a read.</p>
<p><code>Muscle Mem</code> was well-received, gaining healthy intrigue on <a target="_blank" href="http://news.ycombinator.com/item?id=43988381">Hacker News</a> and <a target="_blank" href="https://github.com/pig-dot-dev/muscle-mem">GitHub</a>.</p>
<p>I sure felt clever!</p>
<p>Fast-forward a couple days: we had a more than 700 Github stars, but strangely… seemingly no users. While a few real users did eventually trickle in over the following month(s), it was clear something had to change.</p>
<p>Thankfully (or, not), it’s hard to talk me out of ideas that I consider to be inevitabilities.</p>
<p>I genuinely believe in the thesis that <strong>many classes of automation are best expressed with deterministic software, and we’re wasting intelligence and reducing trust by performing them with agents</strong>.</p>
<p>The road to AI is understandably tempting; there’s an long tail of cases to handle in automation software, and agents can confidently trailblaze across those edge cases into the unknown to figure it out. But what I assert is that: <strong>for those repeat executions, we ought to be using software.</strong></p>
<h2 id="heading-the-flaws-of-muscle-mem">The Flaws of Muscle Mem</h2>
<p>(<em>You would not believe how many failed attempts I’ve put into writing on this topic</em>)</p>
<p><code>Muscle Mem</code> did not last long in the wild before it started hitting limitations. Its flaws fell into one of four buckets:</p>
<ol>
<li><p>Shit UX</p>
</li>
<li><p>Rigid definition of a workflow</p>
</li>
<li><p>Recording the wrong signals</p>
</li>
<li><p>Ignoring the dynamic nature of data</p>
</li>
</ol>
<h3 id="heading-shit-ux">Shit UX</h3>
<p>Conceptually cool, but a head scratcher to try to use.</p>
<p><code>Muscle Mem</code> focused heavily on tool-calling as the integration point, along with user-defined callbacks on generic types to guard the execution against edge cases.</p>
<p>You’d decorate tools with a pre-<code>Check</code> , a guard that would run a <code>capture()-&gt;T</code> callback to capture some data <code>T</code> that you’ve deemed matters for later determining cache validity (yet another <code>compare(T, T)</code> callback to pass/fail relative to the current <code>T</code>). Simple!</p>
<p>Confusingly to many, <code>Muscle Mem</code> did not ask users to decorate their tools which <em>read</em> from the environment: it didn’t care about replaying those. Just decorate the <em>write</em> tools. And the capture function would greatly depend on <code>T</code>. Even for us, generating examples proved difficult in this setup, which is pretty apparent in an example where we make use of a literal timestamp in <code>T</code>, resorting to TTL based cache invalidation.</p>
<p>If you’re struggling to follow, rest assured. For, in my excitement to make the system “bolt onto” your existing agents, I created a whole new set of hoops to jump through. Tools had to be manually instrumented, and you had to know in advance which ones mattered for replay. Cache validation was forced to be a user concern, and users didn’t seem terribly keen on spending their time writing such abstract callback handlers.</p>
<h3 id="heading-rigid-definition-of-workflow">Rigid definition of workflow</h3>
<p>Following the assertion that <code>Muscle Mem</code> was automating “discrete” workflows, it became the responsibility of the user to group workflows into discrete buckets, such as:</p>
<ul>
<li><p>“Fill out the intake form,” or</p>
</li>
<li><p>“Process a refund.”</p>
</li>
</ul>
<p>This would require all workflows to be known in advance. It afforded no natural drift in workflows. It left no room for subagents and subtrajectories, which are usually powerful tools for abstraction in agents.</p>
<p>Moreover, to enter a workflow was a black box. A supposed cache-hit that would break mid-run wouldn’t have any way of letting the agent resume from partway through; its only option was to start from the very beginning.</p>
<p>This clashed with the callback structure as well. If a tool were to return a cache-breaking piece of data, that signal would certainly be present in the messages, but it’s possible the user couldn’t have anticipated having to read that data when designing <code>T</code>. Or, what if the new branch in behavior subtly involved an entirely different workflow? Well, it would all get baked into the cache under “Fill out the intake form.”</p>
<h3 id="heading-recording-the-wrong-signals">Recording the wrong signals</h3>
<p>The role of Muscle Memory is to accurately reproduce the same series of tool calls that an LLM would when in the same situation.</p>
<p>The artifact of an agent run in <code>Muscle Mem</code> was a linear list of function invocations with arguments, as well as any user-retrieved environment data which <code>T</code> would tag onto it. This differs from the content visible to the LLM: the LLM isn’t looking at tool traces and user data <code>T</code>. Instead, it only sees the context window to make branching logic decisions. Thus, <code>Muscle Mem</code> shared no overlap in signal with the models it was meant to proxy.</p>
<p>In reproducing tool calls in a model-like way, it is critical to have a 100% overlap in signal, else succumb to the final point:</p>
<h3 id="heading-ignoring-the-dynamic-nature-of-data">Ignoring the dynamic nature of data</h3>
<p>This was, and is, the biggest challenge in this entire domain of Muscle Memory. No two LLM calls are exactly alike: they’re the result of structural templating, nondeterministic tool results, and chaotic user input.</p>
<p>If you require exact matches, as did <code>Muscle Mem</code>, your hit rate will be 0%.</p>
<p>We must be able to separate out “variables” from the code.</p>
<p>The easiest example of this is a form-filling bot navigating a browser. The button coordinates are static, as the buttons will almost always remain at their respective locations. But, typing into the “First Name” field would be dynamic: entering a unique string from run to run. Still, you could (and should) assert that form-filling with dynamic data constitutes the same workflow; it just involves variable data which the model is regurgitating into a tool call argument.</p>
<p>I will talk in great depth on this topic, so we’ll just say that <code>Muscle Mem</code> had nearly zero mechanics accounting for this.</p>
<h2 id="heading-the-realization-everything-is-code">The Realization: Everything is Code</h2>
<p>All of these concepts map to software systems.</p>
<h3 id="heading-llms-are-pure-functions-tools-are-the-runtime">LLMs are pure functions, tools are the runtime</h3>
<p>The <a target="_blank" href="https://en.wikipedia.org/wiki/Chinese_room">Chinese Room Experiment</a> questions our ability to judge the intelligence of an agent locked away in a black box offering communication only by basic inputs and outputs. For us, we aren’t so much concerned with determining the intelligence of the agent inside. Instead, Muscle Memory attempts to learn from and emulate the apparent “intelligence” inside.</p>
<p>An interesting aspect of the Chinese Room setup is that it is <em>stateless</em>, with a text-in/text-out messaging interface. Peaceful, quiet, one request at time, and a pure function even by a Haskell programmer’s standards. At least until LLM API providers pull tool callings behind their API wall, <strong>we may think of LLMs as pure functions</strong>, interpreting inputs and transforming them into outputs per instructions without side effects.</p>
<p><strong>Tools are the runtime</strong></p>
<p>A pure function on its own has no use without side-effects. Tools are the runtime, the “hands” for models to reach out and read/write into the stateful world. They’re also the means by which caches get broken.</p>
<p><strong>If it quacks like a duck…</strong></p>
<p>Poorly summarized in polymorphism terms, the lesson we take from Chinese Room Experiment is:</p>
<blockquote>
<p>If it looks like a duck and quacks like a duck, it calls into question if ducks are real.</p>
</blockquote>
<p>Whether or not ducks are real, the Chinese Room scenario says that as long as a black box is producing a valid sequence of outputs, it’s a viable system.</p>
<p>This system, executing tools into the external world, cares not for what’s in the black box, so long as that black box produces tool calls in the right order with the right arguments. That is, users of LLMs aren’t so much interested in the mechanics of the computation used to generate a response to their prompts. They’re mainly looking for responses they can trust.</p>
<h3 id="heading-data-vs-code-separation">Data-vs-code separation</h3>
<p>Programming languages (turning a blind eye to the lisps) generally draw a strict line between data and code. The code lays out a map of all possible branches a system can take, and the data slots into known shapes (types) to ultimately dictate which branches get taken.</p>
<p><strong>Context windows are effectively interwoven data + code</strong>. This is especially the case with the workflow agents we’re targeting, which are usually triggered by some software system without a human chat so it carries structurally consistent data.</p>
<p>The separation of data and code introduces a concept of variables, which we’ll call <em>symbols</em>. In programming languages, symbols map to data, functions, etc. In context windows, it’s more nebulous, but for simplicity: think of symbols as pointers to substrings within the context, like <code>first_name</code>.</p>
<h3 id="heading-the-exact-line-of-intelligence-is-variable-scope">The exact line of “intelligence” is variable scope</h3>
<p>In interpreted programming languages, executing a line depends on all the used symbols being in scope by the time of execution, else raising an <code>Undefined Symbol</code> error.</p>
<pre><code class="lang-python">a = <span class="hljs-number">1</span> <span class="hljs-comment"># brings a into scope</span>
b = a <span class="hljs-comment"># a already in scope, brings b into scope</span>
c = d <span class="hljs-comment"># Undefined Symbol: d!</span>
</code></pre>
<p>Imagine a perfect system for separating data and code from an LLM’s context window, one where you accumulate a vast trove of symbols and their respective data.</p>
<p>If that context is sent to a model, and it produces new symbols that have no computable origin from the context, we might call this the result of “intelligence.”</p>
<p>That is, the model pulled that variable out of thin air.</p>
<p>This is the equivalent of the <code>c = d</code> undefined symbol error above. Variables simply do not materialize out of thin air in deterministic programs. This helps us identify the intractable land of “intelligence” which we couldn’t possibly serve. Sadly, this is where we must draw the line for how helpful Butter can be.</p>
<h3 id="heading-can-it-be-muscle-memory">Can it be Muscle Memory?</h3>
<p>Given this, the cleanest heuristic for whether an automated workflow might benefit from Muscle Memory systems is this: whenever you could, in theory, with infinite time, sit down and write heuristic-based software handling every case of the workflow.</p>
<p>If you could not write the workflow as software, if you couldn’t massage dynamic data of known shapes through a conditional branching logic without hitting some data transformation that can only be described as “an LLM pulling something out of thin air,” then you would be working with a fundamentally intelligent workflow.</p>
<p>So long as every single token output of a model is either static (inherent to that run, including planning traces) or a regurgitated derivation of dynamic data from earlier in the context window, we consider the generation to be a candidate for Muscle Memory.</p>
<p>It’s that simple. <strong>Can it be code?</strong></p>
<h2 id="heading-can-it-be-code-so-were-doing-codegen">“Can it be code” – So we’re doing codegen?</h2>
<p>Not exactly. In addition to the proxy approach, which I’ll discuss in a moment, I believe a <a target="_blank" href="https://arxiv.org/abs/2305.16291">Voyager-style</a> codegen approach models the data-vs-code separation well.</p>
<p>Under this model, LLMs are used to write actual software which hooks into a low-level primitive set of functions, giving itself an on-the-fly generated tool it can call.</p>
<p>I have plenty of qualms with client-side codegen (which I’ve omitted from this blog for clarity), but all around I believe it can perform well, especially as codegen models improve. We’ve decided to keep focused on our current approach, but it’s worth acknowledging codegen as a hopeful alternative.</p>
<h2 id="heading-actually-were-building-an-llm-proxy">Actually, we’re building an LLM proxy</h2>
<p>LLM proxies, or “gateways,” are an increasingly common class of product that take advantage of the widespread support for <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat">OpenAI’s Chat Completions format</a>, by even non-OpenAI model providers or self-hosted servers like vLLM.</p>
<p>These gateways are HTTP reverse proxies which sit between any LLM client and server supporting the chat completions format. They are simply integrated with one line of change by repointing your LLM client to call the proxy instead of the provider’s default API: <code>BASE_URL=</code><a target="_blank" href="https://proxy.butter.dev"><code>https://proxy.butter.dev</code></a> .</p>
<p>Most gateways are used for analytics, or for intelligently routing across multiple providers.</p>
<p>The Butter proxy does something special: <strong>it spoofs LLM responses</strong> back to clients.</p>
<p>The proxy models all trajectories taken using a tree keyed at each level by the message content. When a response can be served deterministically, it is. If not, the request forwards to the LLM provider for an actual generation, and novel results are stored as a fresh branch in the tree, to be used as muscle memory in the future.</p>
<p>It’s not obvious at first, but a proxy cache serving back spoofed responses to a tool-calling agent achieves, in a heavy-handed way, the same thing that codegen does: we hold the branching logic of a system in the form of a tree-like cache, and guide the agent into invoking functions in the runtime (tools) to read and write data, which goes on to inform even more branching logic in the subsequent requests.</p>
<p>Again, <strong>as long as those tools get called, does it matter who called them</strong>?</p>
<p>Remember the Chinese Room.</p>
<p>As far as the agent client is concerned, it’s always in “AI” mode. The duck keeps quacking, the results come back in valid format, and the world keeps going round.</p>
<p>I’m obviously sold, but let’s walk through how this Proxy approach solves many of the issues with our first <code>Muscle Mem</code> approach:</p>
<h3 id="heading-not-shit-ux">Not-Shit UX</h3>
<p>It’s dead-simple to use: just repoint your <code>BASE_URL</code> to the proxy. It’s a drop-in endpoint for any client system that speaks the chat completions API, as most do.</p>
<p>Users need not concern themselves with special APIs to record this vague concept of a <code>T</code>.</p>
<p>Cache invalidation is built into the way the tree is traversed, on the messages itself. Cache-breaking data from tool call results simply branch the tree and gracefully continue from there with the guidance of an LLM. This provides a completely smooth on/off-ramp between software systems (spoofing the agent down a proven route) and AI systems (generating novel responses).</p>
<h3 id="heading-fluid-definition-of-a-workflow">Fluid definition of a workflow</h3>
<p>Grouping of trajectories is automatic and structural: “Does it follow an existing branch in the tree? Boom, that’s an instance of a workflow.”</p>
<p>This way, workflows taken on a form we can visualize: of a path or set of adjacent paths down the tree. This also allows for a “natural drift” as the workflows evolve: new branches growing, and stale branches eventually pruning.</p>
<h3 id="heading-recording-the-right-signals">Recording the right signals</h3>
<p>It hooks into the messages level, containing all of the data needed for Muscle Memory:</p>
<ul>
<li><p>Message content for cache returns,</p>
</li>
<li><p>Tool call invocations,</p>
</li>
<li><p>Tool call results, which carry cache-branching external data, and</p>
</li>
<li><p>Any other context “symbols” or data that would push the trajectory down a specific branch.</p>
</li>
</ul>
<p>A 100% overlap with what the model sees.</p>
<h3 id="heading-tbh-still-struggling-with-the-dynamic-nature-of-data">(TBH still struggling with) The dynamic nature of data</h3>
<p><a target="_blank" href="https://blog.butter.dev/template-aware-caching">We’re working on this one</a><strong>.</strong> At the very least, we have access to every piece of “scope” that the model does, plus more if we grow our API surface area. In addition, the centralized nature of the cache allows trajectory-vs-trajectory comparison. This gives us the most complete shot at deconstructing data and code from context windows and building a cache that’s more structural than literal.</p>
<h2 id="heading-conclusion-if-only-it-were-easier">Conclusion: If only it were Easier</h2>
<p>The pivot to the proxy form factor was no obvious path to take, and it’s substantially harder to do this correctly than a Python client library.</p>
<p>Despite the challenges, I’m confident that it is the cleanest and most theoretically correct route to building Muscle Memory into AI systems, so we’ll be giving it an enthusiastic swing!</p>
<p>You can sign up and try Butter today <a target="_blank" href="https://docs.butter.dev">using our quickstart guide</a>.</p>
<p>Or reach out – <a target="_blank" href="mailto:erik@butter.dev">erik@butter.dev</a> – we’d love to hear your use-cases, and will try our best to prioritize the patterns that most quickly get you to an agentic system you can trust.</p>
<p>Cheers,</p>
<p><strong>Erik</strong></p>
]]></content:encoded></item><item><title><![CDATA[Template-Aware Caching]]></title><description><![CDATA[Motivation
Butter is designed not only to memorize static trajectories, but also workflows involving dynamic variables. Here, we think of dynamic variables as placeholders for the data which may change from run-to-run: information which might derive ...]]></description><link>https://blog.butter.dev/template-aware-caching</link><guid isPermaLink="true">https://blog.butter.dev/template-aware-caching</guid><category><![CDATA[caching]]></category><category><![CDATA[bindings]]></category><category><![CDATA[llm]]></category><category><![CDATA[agentic AI]]></category><dc:creator><![CDATA[Raymond Tana]]></dc:creator><pubDate>Wed, 01 Oct 2025 01:12:56 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-motivation">Motivation</h2>
<p>Butter is designed not only to memorize static trajectories, but also workflows involving dynamic variables. Here, we think of dynamic variables as placeholders for the <em>data</em> which may change from run-to-run: information which might derive externally from tools, creativity, or the environment. Everything else in the workflow we consider to be the <em>code</em> or structure: how to use the data to complete the task.</p>
<p>Most real world automations include dynamic variables (e.g., names, dates, addresses, etc.), and are thus designed to handle a range of inputs. Workflows may make use of dynamic variables for a number of reasons:</p>
<ol>
<li><p><strong>Storing information about the specific run</strong>. E.g., storing the user’s name, or storing the root directory in the file system.</p>
</li>
<li><p><strong>Tracking information about the environment</strong>. E.g., checking the current time, or reading all the filenames in the current directory.</p>
</li>
<li><p><strong>Determining relevant tools to employ</strong>. E.g., choosing the appropriate parser to run on a given file.</p>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">We assert that in our domain of workflow automation, the desire for a repeatable workflow implies the notion of “code” in the context window; i.e., the instructions that influence the model’s branching logic decisions to complete a known task. This code is intermixed with runtime unique data. It is our job to distinguish the two.</div>
</div>

<p>Each chat between a user and a model represents a single trajectory through an abstract workflow. As Butter observes these trajectories, it is confronted with the problem of distinguishing between dynamic data and structural content within the messages. The better Butter can do at extracting “data from code,” the broader the set of future queries which will result in a cache hit. This way, Butter can more robustly serve from its cache and avoid another LLM call.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">In this setup, the broader the set of queries a single cache entry is meant to handle, the greater the burden there is on Butter to correctly model how this data is derived and transformed.</div>
</div>

<h2 id="heading-obstacles-to-separating-data-from-code">Obstacles to Separating Data from Code</h2>
<p>The general problem of separating data from code involves many considerations.</p>
<h3 id="heading-how-to-detect-when-an-argument-to-a-tool-is-intended-to-change-run-to-run"><strong>How to detect when an argument to a tool is intended to change run-to-run?</strong></h3>
<p>As we observe LLMs making use of provided tools to complete tasks, we might expect the model to make use of some dynamic data when providing arguments to its tool calls. Indeed, it seems reasonable to assume that whenever an argument is passed into a tool call, it represents dynamic data.</p>
<p>For instance, suppose that the user specifies that they live in Boston, and asks about the weather there. With access to the tool <code>get_weather(city : str, state : str) -&gt; str</code>, the LLM could produce the tool call <code>get_weather('Boston', 'MA')</code>.</p>
<p>Here, both the arguments for city and state require some dynamic data, and it will be the job of Butter to figure out how to derive their values. As the user already explicitly wrote that they live in Boston, the substring <code>'Boston'</code> is already present in the model’s context, so we know from where in the prompt to source the city information. But nowhere did the user specify Massachusetts as their state; so while the string <code>'MA'</code> might be theoretically derivable from the city of Boston, Butter has no clear source from which to have derived it syntactically. Really, the argument <code>'MA'</code> was generated by the LLM, understanding Boston to be the city in Massachusetts. How is Butter to understand this relationship between <code>Boston</code> and <code>MA</code>?</p>
<p>On the other hand, some tool calls might involve variables which should not change run-to-run. For example, an encoding format like <code>png</code> may not depend on any dynamic data, and instead is a structural part of the workflow.</p>
<h3 id="heading-how-to-avoid-associating-identical-data-to-the-same-variable-when-theyre-truly-unrelated">How to avoid associating identical data to the same variable when they’re truly unrelated?</h3>
<p>Coincidences happen, but recognizing them is not so straightforward. For instance, which data in the following query are related?</p>
<p><em>Today is September 30, 2025. Find the third Python script in the directory</em> <code>/source</code> when sorted alphabetically, interpret it with <code>python3</code>, and save the output in <code>09/output_3.txt</code>.</p>
<p>A naïve approach to separating data from code might replace all instances of the number <code>3</code> with the same variable, ignoring the various, distinct roles played by the number three in this query. Any time we group together unrelated data, our cached template will fail to generalize to other situations.</p>
<h3 id="heading-how-to-recognize-instances-of-the-same-underlying-variable-throughout-a-message"><strong>How to recognize instances of the same underlying variable throughout a message?</strong></h3>
<p>While the previous point might be described as a concern for false positives, we might also be concerned about false negatives. That is, we worry about failing to recognize how the same variable was used across a conversation.</p>
<p>Data may undergo transformations which are syntactically simple: e.g., turning a string into all lower-case (<code>Erik</code> → <code>erik</code>), rewriting a number (<code>1</code>→ <code>1.0</code>), or stringifying a JSON object.</p>
<p>Data may also be filtered: e.g., a variable corresponding to the user’s full name will necessarily contain the data required to obtain both that user’s first name and last name. Either of these partial names may be used throughout the context without writing out the full name.</p>
<p>Other transformations involve more intelligence to execute. For instance, we might view <code>'MA'</code> from the example above as the result of a transformation of the form <code>state_from_city('Boston')</code>. Plenty of other examples require the same level of insight, such as knowing the industry in which a given company operates, computing the date associated with a given holiday, expanding a given acronym, naming the artist behind a given song, producing antonyms for given words, etc.</p>
<p>No matter the transformations that may have been applied to some data, we still consider each of its representations as being associated to the same underlying variable. But recognizing that the variable <code>leader = 'Napolean'</code> explains both <code>'France'</code> and <code>'1821'</code> in a given chat is not trivial. Just as before, failure to recognize relationships between data makes our cache less generalizable.</p>
<h2 id="heading-bindings">Bindings</h2>
<p>Butter performs a number of symbolic manipulations when augmenting, comparing to, and generating from its cache. This functionality is necessary for Butter to make use of variables in its stored workflows. Instead of filling the cache with observed messages written verbatim (e.g., <code>'Say hello to Erik'</code>), it stores a combination of <strong>bindings</strong> paired with a message <strong>template</strong>. The bindings specify how each variable maps to a corresponding value (e.g., <code>{'name': 'Erik'}</code>). The template is generated by substituting all instances of dynamic data with their corresponding variable’s name (e.g., <code>"Say hello to {{name}}"</code>). This way, applying the bindings to the template reproduces the original message.</p>
<p>We describe this approach as <strong>template-aware caching</strong>. Templates do not limit our ability to compare new queries to the cache: an incoming query is compared to an existing template using regex and exact-matching. If regex recognizes the query to follow the same structure as the template, it is straightforward to read off the values that each of the expected variables from the bindings should take on in this query. Assuming this process goes through without any contradictory assignments, it is now straightforward to use these bindings to populate the cached response to this query. This is how Butter adds determinism to LLM calls which follow a recognized structure and contain dynamic data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759294658832/cda3abde-f678-4de4-9221-bf869e3cff0f.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-inferring-bindings">Inferring Bindings</h2>
<p>In practice, bindings must either be specified or inferred. Currently, Butter’s <code>Butter-Bindings</code> allows users to specify bindings explicitly from the start. These so-called <em>top-level bindings</em> help to avoid guesswork, but are not always feasible to provide.</p>
<p>For our method to be effective, we should also be able to infer bindings from chats. We have discussed how arguments passed to tool calls are very likely to have made use of dynamic data. For instance, in the tool call: <code>read_latest_email_from(sender = '</code><a target="_blank" href="mailto:example@butter.dev"><code>example@butter.dev</code></a><code>')</code>, we find an email address which almost surely contains some dynamic data.</p>
<p>Bindings may also be derived deterministically via regex or substring matches. By comparing multiple observed trajectories, we might identify locations in which data was used in the same manner across each run.</p>
<p>Still, large language models may be best suited for the job of automatically detecting (in post) any relevant bindings that were not already detected via the other deterministic methods.</p>
<h2 id="heading-what-butter-already-does">What Butter Already Does</h2>
<p>As an LLM proxy, Butter forwards requests to inference providers and caches responses. On repeat requests, responses are served immediately, bypassing wasteful generations. In its current implementation, Butter performs template-aware caching, serving responses based on structural similarity rather than requiring exact matches.</p>
<p>You can simplify modify your LLM client or your curl command to Butter’s custom base URL:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os, json, httpx
<span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI

client = OpenAI(
    base_url=<span class="hljs-string">"https://proxy.butter.dev/v1"</span>,
    http_client= httpx.Client(
        headers={<span class="hljs-string">"Butter-Auth"</span>: <span class="hljs-string">f"Bearer <span class="hljs-subst">{os.getenv(<span class="hljs-string">'BUTTER_API_KEY'</span>)}</span>"</span>},
    ),
)

<span class="hljs-comment"># Specify any bindings</span>
bindings = {
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Erik"</span>
}

<span class="hljs-comment"># Create cache</span>
response = client.chat.completions.create(
    model=<span class="hljs-string">"gpt-4o"</span>,
    messages=[{<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"say hello to Erik"</span>}],
    extra_headers={<span class="hljs-string">"Butter-Bindings"</span>: json.dumps(bindings)},
)

print(response)
</code></pre>
<pre><code class="lang-bash">curl -X POST <span class="hljs-variable">$BASE_URL</span>/v1/chat/completions \
-H <span class="hljs-string">"Content-Type: application/json"</span> \
-H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">$OPENAI_API_KEY</span>"</span> \
-H <span class="hljs-string">"Butter-Auth: Bearer <span class="hljs-variable">$BUTTER_API_KEY</span>"</span> \
-H <span class="hljs-string">"Butter-Bindings: {\"name\": \"Erik\"}"</span> \
-d <span class="hljs-string">'{"messages":[{"content":"say hello to Erik","role":"user"}],"model":"gpt-4o"}'</span>
</code></pre>
<p>The above code examples show how the user can tell Butter to cache templates rather than exact messages by specifying top-level <em>Butter bindings</em>.</p>
<p>Whenever Butter caches a new message which involved some bindings, it builds the template by replacing all instances of bound values by their corresponding variables. These replacements can occur anywhere in a message, including in tool calls. Additionally, whenever Butter recognizes a match between a cached template and an incoming query, it uses regex substring matching to infer the proper bindings as expected for that template.</p>
<h2 id="heading-known-bugs">Known Bugs</h2>
<p>Let’s review a few bugs users should expect to run into with Butter’s current implementation.</p>
<ol>
<li><p>The exact string matching that Butter currently uses poses a few challenges.</p>
<ul>
<li><p><em>False negatives</em>: Even slight modifications to a letter’s case (<code>Erik</code> vs. <code>erik</code>) or a number’s precision (<code>1</code> vs <code>1.0</code>) will fail to match.</p>
</li>
<li><p><em>False positives</em>: Whenever building a template from a query, the matcher will replace any string that matches the bound value—an error if those values had no semantic relationship.</p>
</li>
<li><p><em>Naming conflicts</em>: Wrapper types for naming variables in templates could conflict with other agent frameworks.</p>
</li>
</ul>
</li>
<li><p>Another issue follows from how exact matching is implemented with regex: <em>bound values should be separated by delimiters</em>. Otherwise, Butter’s cache responses might diverge from the expected behavior. For instance, suppose we were to request the model to <code>Repeat butterfly 3 times</code> while specifying the bindings <code>prefix = butter</code> and <code>suffix = fly</code>:</p>
<pre><code class="lang-bash"> curl -X POST <span class="hljs-variable">$BASE_URL</span>/v1/chat/completions \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">$OPENAI_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Auth: Bearer <span class="hljs-variable">$BUTTER_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Bindings: {\"prefix\": \"butter\", \"suffix\": \"fly\"}"</span> \
 -d <span class="hljs-string">'{"messages":[{"content":"say butterfly 3 times","role":"user"}],"model":"gpt-4o"}'</span>

 <span class="hljs-comment"># response: "butterfly butterfly butterfly"</span>
</code></pre>
<p> In this case, Butter will add a node into its cache with the specified bindings <code>{prefix: butter, suffix: fly}</code>, and the corresponding template <code>{{prefix}}{{suffix}} {{prefix}}{{suffix}} {{prefix}}{{suffix}}</code>. Now, if we try running the command again:</p>
<pre><code class="lang-bash"> curl -X POST <span class="hljs-variable">$BASE_URL</span>/v1/chat/completions \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">$OPENAI_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Auth: Bearer <span class="hljs-variable">$BUTTER_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Bindings: {\"prefix\": \"butter\", \"suffix\": \"fly\"}"</span> \
 -d <span class="hljs-string">'{"messages":[{"content":"say butterfly 3 times","role":"user"}],"model":"gpt-4o"}'</span>

 <span class="hljs-comment"># error: failed to query tree</span>
</code></pre>
<p> This error occurs because, as Butter compares the query <code>"say butterfly 3 times"</code> to the existing template, regex must default to some way of decomposing <code>butterfly</code> into <code>{{prefix}}{{suffix}}</code> (the current implementation uses non-greedy regex, which chooses <code>prefix = b</code> and <code>suffix = utterfly</code>). These assignments then disagree with the specified bindings of <code>prefix = butter</code> and <code>suffix = fly</code>, leading to this error.</p>
<p> Instead, we could have run the above command again <em>sans</em> any bindings:</p>
<pre><code class="lang-bash"> curl -X POST <span class="hljs-variable">$BASE_URL</span>/v1/chat/completions \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">$OPENAI_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Auth: Bearer <span class="hljs-variable">$BUTTER_API_KEY</span>"</span> \
 -d <span class="hljs-string">'{"messages":[{"content":"say butterfly 3 times","role":"user"}],"model":"gpt-4o"}'</span>

 <span class="hljs-comment"># response: "butterutterfly, butterfly, butterfly"</span>
</code></pre>
<p> This time, Butter succeeds matching this query to the stored template, and so it makes use of the inferred bindings <code>prefix = b</code> and <code>suffix = utterfly</code> to produce <code>"butterutterfly, butterfly, butterfly"</code>. This isn’t quite what we had in mind.</p>
</li>
<li><p>Butter may fail to recognize the underlying interdependencies between data, making it worse at generalizing to unseen trajectories. In the example described above about getting the weather in Boston, Butter would fail to recognize that the tool argument <code>MA</code> was generated by the LLM in lieu of the city being Boston. Consider what this means for the following command:</p>
<pre><code class="lang-bash"> curl -X POST <span class="hljs-variable">$BASE_URL</span>/v1/chat/completions \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -H <span class="hljs-string">"Authorization: Bearer <span class="hljs-variable">$OPENAI_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Auth: Bearer <span class="hljs-variable">$BUTTER_API_KEY</span>"</span> \
 -H <span class="hljs-string">"Butter-Bindings: {\"city\": \"Boston\"}"</span> \
 -d <span class="hljs-string">'{"messages":[{"content":"Tell me the weather in Boston","role":"user"}],"model":"gpt-4o"}'</span>

 <span class="hljs-comment"># the model next chooses to call the tool: get_weather('Boston', 'MA')</span>
</code></pre>
<p> Butter would naively cache the template <code>get_weather({{city}}, 'MA')</code>for the tool call, which would fail to generalize to cities outside of Massachusetts.</p>
</li>
</ol>
<h2 id="heading-what-comes-next">What Comes Next</h2>
<p>There are many ways in which Butter could be improved. Here are some of the directions we will explore.</p>
<ol>
<li><p><em>Inferring dynamic data from tool calls</em>: one easy way to infer new dynamic variables is by reading the arguments passed into tool calls. Any value not already specified in the Butter bindings could be bound to a new variable; any other instance of that value could then be associated to that same variable.</p>
</li>
<li><p><em>Revising the cache in light of new observations</em>: There is power in seeing more examples: extra trajectories can either reveal different roles played by distinct variables, or add to our confidence that a certain piece of data is being used in several places throughout a workflow.</p>
</li>
<li><p><em>Intelligence in separating data from code</em>: Many of the issues we’ve cited regarding separating data from code call for a more intelligent way of manipulating data. For this, we propose <em>code-generation for resolvers</em>. Resolvers could be generated to handle many of the complex data transforms discussed above such as formatting, combining, filtering, or associating data.</p>
</li>
<li><p><em>Building more sophisticated matchers</em>: Some of the limitations of exact matchers could be addressed with deterministic fuzzy matchers that more flexibly handle case, precision, punctuation, or whitespace. Still, we anticipate some intelligence is required to generally match messages in a chat to cached templates.</p>
<p> For example, we might hope that in learning how to respond to the prompt <code>"Do I have any unread emails?"</code>, to not store separate workflows for each possible value for <code>number_of_unread_emails</code> being <code>1</code> vs <code>2</code> vs <code>3</code>, etc. Instead, an appropriate matcher in this case would switch on the condition: <code>number_of_unread_emails &gt; 0</code>.</p>
<p> So, building matchers for each query may in general require some creativity.</p>
</li>
<li><p><em>Sub-workflows</em>: Many agent workflows will perform data transformation or planning operations that are impossible without intelligence or creativity. This disqualifies them for deterministic replay. Accurately detecting these operations would allow us to still cache the deterministic sub-workflows between them. Sub-workflows could be implemented into Butter by pointing to other entry-points in the cache.</p>
</li>
</ol>
<p>We would love to hear any feedback, ideas, or experiences you have related to Butter!</p>
]]></content:encoded></item></channel></rss>