<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dawncoral</title>
    <description>The latest articles on DEV Community by Dawncoral (@dawncoral).</description>
    <link>https://dev.to/dawncoral</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4182309%2F64d1963f-cb15-49cd-aa03-a65e3137d569.png</url>
      <title>DEV Community: Dawncoral</title>
      <link>https://dev.to/dawncoral</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dawncoral"/>
    <language>en</language>
    <item>
      <title>We grouped 31 agent memory gates into 9. No checks were deleted.</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Wed, 04 Nov 2026 07:54:12 +0000</pubDate>
      <link>https://dev.to/dawncoral/we-grouped-31-agent-memory-gates-into-9-no-checks-were-deleted-j43</link>
      <guid>https://dev.to/dawncoral/we-grouped-31-agent-memory-gates-into-9-no-checks-were-deleted-j43</guid>
      <description>&lt;p&gt;We grouped 31 agent memory control points into 9 groups, kept all 31 detailed checks, and put the new map through a two-week usage review instead of treating the smaller index as proof of equivalent coverage.&lt;/p&gt;

&lt;p&gt;Agent memory is often discussed as a retrieval problem. Our recent work was about the less visible control plane, the checks that decide how a memory system reads and changes project state. We borrowed the outcome-level interface idea from an AWS MCP rebuild and applied it to that control plane.&lt;/p&gt;

&lt;p&gt;The system had accumulated session-loading checks, write validators, evidence rules, delivery checks, periodic scans, and human approvals. Each one had a job. The flat list was the problem.&lt;/p&gt;

&lt;p&gt;Fifteen entries merged into five groups, four became conditional checks inside a group, one moved back to ordinary reading discipline, and eleven kept their own identity. All 31 detailed rows remain available. No safety check was deleted.&lt;/p&gt;

&lt;p&gt;Sarvar Nadaf's recent AWS MCP rebuild offered a useful comparison. He cut 15 tools to 4 outcome-level tools, and the same question took 2 tool calls instead of 7 in his test. The lesson is larger than MCP: an interface should be organized around the decision someone is trying to make, with detail available one level down.&lt;/p&gt;

&lt;p&gt;Two mechanisms made that possible without pretending that grouping is the same as safety. The first is an outcome-level control map: start with the failure in front of you, find its group, then open the detailed check. The second is a shadow projection: derive a state view from the append-only event log, compare it with the handwritten view, and change the write path only after the differences have mechanical explanations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoreboard: 31 entries, 9 groups, 0 deleted checks
&lt;/h2&gt;

&lt;p&gt;The analogy with &lt;a href="https://dev.to/aws-builders/mcp-token-cost-i-cut-my-mcp-server-from-15-tools-to-4-cek"&gt;Nadaf's MCP rebuild&lt;/a&gt; has a limit. His main costs were tool schemas, response size, and tool-selection confusion. Our control points also consume attention, but a missing memory check can leave two versions of the truth behind. We did not treat a smaller count as evidence of equivalent coverage.&lt;/p&gt;

&lt;p&gt;The coverage argument has to come from the mapping itself:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What changed&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flat control entries before&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;Every check competed for attention at the same level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groups after&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Each group answers one kind of operational question&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries merged into five groups&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Their actions still run inside those groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries made conditional&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;They now run at a named trigger inside their host group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries reclassified&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A source-priority rule moved back to reading discipline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries kept independent&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Their timing or decision authority requires a separate identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detailed rows retained&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;The group is an index, not a replacement for the detail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety checks deleted&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Consolidation changed navigation, not the required work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row matters. A control map can hide a check behind a friendly group name just as easily as an oversized &lt;code&gt;AGENTS.md&lt;/code&gt; can hide it behind three hundred lines. We kept the detailed rows beside the group index so a reviewer can trace every consolidated entry to its new home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7k4zuky9p3wl9dvrhlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7k4zuky9p3wl9dvrhlg.png" alt="A control map grouping 31 agent memory control points into nine groups while retaining all 31 detailed checks" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The groups are addresses, not nine new rituals
&lt;/h2&gt;

&lt;p&gt;We grouped controls by the moment at which they act and the failure they catch. We did not group them by which script or document happened to own them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;The question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intake&lt;/td&gt;
&lt;td&gt;Did the session read current long-term facts, active state, and the relevant domain record?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admission&lt;/td&gt;
&lt;td&gt;Should this action exist, and does it have an owner or task identity?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;td&gt;Did the change pass through the approved writer, contract checks, evidence rules, and location checks?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change conduction&lt;/td&gt;
&lt;td&gt;Did this change reach every dependent document, with contradictions recorded?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closeout&lt;/td&gt;
&lt;td&gt;Is there an explicit account of unfinished artifacts and uncommitted work?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Periodic checks&lt;/td&gt;
&lt;td&gt;What do the daily scan, freshness checks, projections, and weekly reconciliation say?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human review&lt;/td&gt;
&lt;td&gt;Does this action cross a red line, require authorization, touch money or credentials, or expose private data?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery self-check&lt;/td&gt;
&lt;td&gt;Does every status claim have evidence, and does every surprise have a classification?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback&lt;/td&gt;
&lt;td&gt;Is the failure registered for recurrence checks, and is the capability still earning its place?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first five groups roughly follow a task. The other four cut across tasks. A reviewer can say “this is a write-group failure” or “this belongs in change conduction,” then open the exact validator or receipt underneath.&lt;/p&gt;

&lt;p&gt;This is the same reason &lt;code&gt;find_waste&lt;/code&gt; is more useful than seven near-identical AWS wrappers in the reference article. The caller starts with an outcome. The workflow lives behind the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some controls do not get merged
&lt;/h2&gt;

&lt;p&gt;Grouping works only when it preserves decision boundaries. Five kinds of human review stayed distinct inside the human-review checklist: red lines, pre-authorization, irreversible actions, data classification, and monthly audit. Sharing a checklist does not turn five decisions into one.&lt;/p&gt;

&lt;p&gt;The periodic checks also kept separate schedules. A daily diff cannot prove that a weekly semantic review happened. A freshness light cannot stand in for either one. Closeout kept its own group because no other control accounts for unfinished files. Failure registration stayed visible because a repeated problem needs an identity before anyone can count its recurrence.&lt;/p&gt;

&lt;p&gt;Our rule is simple: merge the lookup, not the judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A shadow projection tests the source before the switch
&lt;/h2&gt;

&lt;p&gt;The control-map change reduced navigation overhead. The shadow system addressed a different question: if a state view can be derived from events, why maintain that view by hand?&lt;/p&gt;

&lt;p&gt;We started with one high-frequency view. It had collected 111 rows in 11 days, which made manual drift easy to observe and gave the experiment enough material to be useful. The existing handwritten view stayed in place. A deterministic projector read the append-only event log, matched events to rows using shared anchors, and wrote a separate shadow view.&lt;/p&gt;

&lt;p&gt;The pipeline has three stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extract: read timestamped events from the append-only log
project: match events to view rows with slugs, task IDs, or text anchors
diff:    report coverage, lag, and unexplained drift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No model calls sit in this path. The projector checks that the source hash has not changed before writing, rotates five backups, and skips rows it cannot parse instead of deleting them. A person reads the diff.&lt;/p&gt;

&lt;p&gt;The first run produced a useful, unglamorous table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;First shadow run&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Handwritten rows in scope&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows matched to an event&lt;/td&gt;
&lt;td&gt;129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;89.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strong-signal lag&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows without an event anchor&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 15 unmatched rows had a mechanical explanation: the activity existed in the handwritten view but had never entered the event log. Increasing the matcher's vocabulary would only hide that structural gap. The better fix was to make the event log the single write entrance before asking the projection to replace the handwritten view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnujn8n9pjquz58ry2aup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnujn8n9pjquz58ry2aup.png" alt="The shadow projection pipeline comparing an append-only event log and handwritten view before promoting a generated view" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Promotion changes who writes the view
&lt;/h2&gt;

&lt;p&gt;A publishing view moved first. The projector produced 41 entries that matched the existing handwritten publication list. A separate activity view followed with 30 generated rows, each carrying a pointer back to its source event. Its reconciliation run reported 100% coverage and zero drift.&lt;/p&gt;

&lt;p&gt;Those numbers describe agreement between the event log and the derived views. They do not measure whether an agent remembered the right fact, made a better decision, or saved money. A projection can be perfectly consistent with an incomplete event log. That is why the write entrance and the external audit remain separate controls.&lt;/p&gt;

&lt;p&gt;After promotion, people stop editing the generated view. They write the event; the projector refreshes the view. The append-only log stays intact, and the generated rows retain their evidence pointers. Rollback means restoring the handwritten view, not reconstructing history from the projection.&lt;/p&gt;

&lt;p&gt;Three kill rules stayed armed after promotion:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Two or more unexplained drift cases trigger rollback.&lt;/li&gt;
&lt;li&gt;One dispute over whether the log or the projection is authoritative triggers rollback.&lt;/li&gt;
&lt;li&gt;Maintenance time exceeding the old handwritten workflow triggers rollback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A shadow system needs a way to lose. Otherwise it is just a second production path with a reassuring name.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 9-group map is still under review
&lt;/h2&gt;

&lt;p&gt;The shadow results above are completed reconciliation checks. The control-map consolidation is earlier in its life. It is currently in a two-week review with three tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;five sample failures must be traceable to the right group within 30 seconds;&lt;/li&gt;
&lt;li&gt;the four newly conditional checks must have zero missed executions;&lt;/li&gt;
&lt;li&gt;the nine groups must be used in at least three real deliveries or reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any test fails, the detailed 31-row map becomes the primary reference again. We have not redrawn the architecture diagram around the new groups yet. A diagram would make the consolidation look settled before the usage evidence exists.&lt;/p&gt;

&lt;p&gt;We also have not measured a token reduction for the 31-to-9 change, and we are not claiming one. The review measures lookup speed, missed conditional checks, and actual use. That is a narrower claim than “71% less governance,” which would confuse a smaller index with less work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to copy before you consolidate anything
&lt;/h2&gt;

&lt;p&gt;Start with a control inventory that names each trigger, owner, and evidence artifact. Group entries only when they share a decision moment or failure mode. Keep the detailed inventory one level below the group index, and leave human decisions individually accountable.&lt;/p&gt;

&lt;p&gt;Then pick one derived view, not the whole memory system. Keep its source append-only. Run the projector beside the existing view and count coverage, lag, and unexplained drift. Change the write path only when every remaining difference has a mechanical explanation. Keep the rollback criteria active after the switch.&lt;/p&gt;

&lt;p&gt;Our earlier posts cover the adjacent layers: &lt;a href="https://wallabytoken.com/blog/p/memory-is-audited?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-memory-gates-shadow" rel="noopener noreferrer"&gt;why memory needs an audit between its log and current state&lt;/a&gt;, &lt;a href="https://wallabytoken.com/blog/p/ai-agent-memory-governance?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-memory-gates-shadow" rel="noopener noreferrer"&gt;how change-conduction receipts verify dependent documents&lt;/a&gt;, and &lt;a href="https://wallabytoken.com/blog/p/ai-memory-write-failures?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-memory-gates-shadow" rel="noopener noreferrer"&gt;why a saved status is not evidence of a durable write&lt;/a&gt;. The public &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-memory-gates-shadow" rel="noopener noreferrer"&gt;wallaby-agent-rules repository&lt;/a&gt; contains the file-based memory and governance layers. This post describes our internal control map and shadow-projection work; it is not a release announcement for that projector.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token builds open-weight inference infrastructure and develops &lt;code&gt;wallaby-agent-rules&lt;/code&gt;. We use the memory controls described here on our own projects.&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://wallabytoken.com/blog/p/agent-memory-gates-shadow?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-memory-gates-shadow" rel="noopener noreferrer"&gt;Wallaby Dispatch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>memory</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Kimi K3.1 Pro: 5,315 multilingual test results</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Tue, 03 Nov 2026 02:19:31 +0000</pubDate>
      <link>https://dev.to/dawncoral/kimi-k31-pro-5315-multilingual-test-results-17o9</link>
      <guid>https://dev.to/dawncoral/kimi-k31-pro-5315-multilingual-test-results-17o9</guid>
      <description>&lt;p&gt;Kimi K3.1 Pro scored &lt;strong&gt;90.9% on Japanese idioms&lt;/strong&gt; and &lt;strong&gt;99.5% on Turkish paraphrase selection&lt;/strong&gt;. We ran 5,315 source-dataset entries across Japanese, German and Turkish to see where those strengths hold, what they do not prove, and why the difference matters for business AI.&lt;/p&gt;

&lt;p&gt;German conversational meaning reached &lt;strong&gt;87.7%&lt;/strong&gt;. Turkish figurative-versus-literal usage reached &lt;strong&gt;99.0%&lt;/strong&gt;. The complete run returned 1,551,208 tokens and cost an estimated &lt;strong&gt;$6.169655&lt;/strong&gt; at list price, with every response, prediction and score preserved for review.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn2q5s61s628an6us59a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn2q5s61s628an6us59a1.png" alt="Kimi K3.1 Pro multilingual evaluation: 5,315 entries, with Japanese OpenMWE at 90.9%, German MultiPragEval at 87.7% and Turkish paraphrase selection at 99.5%" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby builds open-weight inference infrastructure. We ran this evaluation on Moonshot's official API on 2 November 2026. It is not a Wallaby production benchmark or a customer result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers worth talking about
&lt;/h2&gt;

&lt;p&gt;Japanese OpenMWE asks whether a marked phrase is an idiom or literal language. Kimi K3.1 Pro answered &lt;strong&gt;270 of 297&lt;/strong&gt; cases correctly.&lt;/p&gt;

&lt;p&gt;German MultiPragEval tests conversational meaning. The model answered &lt;strong&gt;263 of 300&lt;/strong&gt; cases correctly.&lt;/p&gt;

&lt;p&gt;The two Turkish idiom tasks were stronger still: &lt;strong&gt;199 of 200&lt;/strong&gt; on paraphrase selection and &lt;strong&gt;195 of 197&lt;/strong&gt; on figurative-versus-literal usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxy560iu7zh5h7pn7lyj0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxy560iu7zh5h7pn7lyj0.png" alt="Accuracy for six objective tasks: German MultiPragEval 87.7%, German InQA+ 66.7%, Japanese OpenMWE 90.9%, Turkish ImplicaTR 56.0%, Turkish paraphrase selection 99.5%, Turkish figurative versus literal usage 99.0%" width="799" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those are useful results for teams building terminology checks, document classification and local-language workflows. They are also deliberately specific. Each percentage belongs to a named dataset, not to an entire language.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 5,315 entries actually include
&lt;/h2&gt;

&lt;p&gt;The full set covers public conversational, idiom and workplace tasks, plus nine small custom tasks for structured extraction and context reversal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9vyw6phwzixot3741d3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9vyw6phwzixot3741d3.png" alt="Evaluation coverage by language: German 687 entries, Japanese 340 entries and Turkish 4,288 entries" width="800" height="908"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dataset&lt;/th&gt;
&lt;th&gt;Entries&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;German MultiPragEval&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;Conversational meaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;German InQA+&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;Indirect answers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japanese OpenMWE&lt;/td&gt;
&lt;td&gt;297&lt;/td&gt;
&lt;td&gt;Idiom versus literal meaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japanese JaSocial&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Public speech-act samples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japanese JWCAS&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Open workplace scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish ImplicaTR&lt;/td&gt;
&lt;td&gt;3,888&lt;/td&gt;
&lt;td&gt;Conversational implication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish Idioms&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;Paraphrase and figurative usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom tasks in three languages&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Extraction and context reversal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5,315&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Turkish makes up 80.7% of the entries because ImplicaTR alone contributes 3,888. That is why we do not average the six task scores into a single multilingual number. A large test set should not hide which tasks dominate it.&lt;/p&gt;

&lt;p&gt;How we counted responses and prompts&lt;/p&gt;

&lt;p&gt;The 5,315 source-dataset entries contain 5,311 unique prompts. Four repeated prompts remain separate source-dataset rows. The 60 InQA+ supplement samples are part of the 384-entry InQA+ set, not an extra 60.&lt;/p&gt;

&lt;p&gt;We recorded 5,368 successful API responses in total. The difference comes from earlier duplicate sampling. Duplicate responses count toward execution cost, not toward the scored total. Every recorded response returned HTTP 200 and passed a content-hash check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The more useful finding sits between the high scores
&lt;/h2&gt;

&lt;p&gt;Turkish idiom selection reached 99.5% and 99.0%. Turkish ImplicaTR scored &lt;strong&gt;2,179 of 3,888, or 56.0%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;German conversational meaning reached 87.7%. German InQA+ scored &lt;strong&gt;256 of 384, or 66.7%&lt;/strong&gt;. The label breakdown is more informative than the average:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrxssut9w4xxdg4txkqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrxssut9w4xxdg4txkqw.png" alt="German InQA+ accuracy by label: label 1 at 73.3%, label 2 at 73.3%, label 3 at 46.7%, label 4 at 85.5%, label 5 at 22.2% and label 6 at 0.0%" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Label 4, the non-answer category, reached 85.5%. Label 6, insufficient context, reached &lt;strong&gt;0 of 22&lt;/strong&gt;. A team using the model to decide when a conversation lacks enough information should keep an explicit review step for that decision.&lt;/p&gt;

&lt;p&gt;The same caution applies to Japanese workplace writing. We generated 30 JWCAS open-ended responses, but they still need native-speaker review. JaSocial covered 10 public samples with keyword-based function checks, not a naturalness score. We did not turn either one into a made-up accuracy number.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for local-language products
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Japanese:&lt;/strong&gt; idiom and literal-meaning classification is a promising fit for structured document workflows. Open-ended business writing needs a separate native review process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;German:&lt;/strong&gt; conversational-meaning tasks are useful, but indirect answers and missing-context decisions need task-specific tests and escalation rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turkish:&lt;/strong&gt; terminology and idiom workflows show strong results. Open-ended implication should be evaluated on representative customer text rather than inferred from idiom accuracy.&lt;/p&gt;

&lt;p&gt;For a product team, the practical question is not “Is this model multilingual?” It is “Does it work on this document, in this language, under these rules?” That is the evaluation we would run before choosing a model for a customer workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the evaluation cost
&lt;/h2&gt;

&lt;p&gt;The run returned &lt;strong&gt;1,551,208 tokens&lt;/strong&gt;. Our list-price estimate is &lt;strong&gt;$6.169655&lt;/strong&gt;, including duplicate sampling and different execution modes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Execution stage&lt;/th&gt;
&lt;th&gt;Estimated cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial 4,207 entries&lt;/td&gt;
&lt;td&gt;$4.701157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supplementary 90 entries&lt;/td&gt;
&lt;td&gt;$0.348890&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Professional-set direct calls&lt;/td&gt;
&lt;td&gt;$1.119608&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$6.169655&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first 1,000 Batch calls were estimated at 60% of standard price; the other stages used standard list rates. This is an evaluation-cost estimate, not an invoice, and it excludes taxes and any separate cache-write charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence and sources
&lt;/h2&gt;

&lt;p&gt;Download the evaluation artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wallabytoken.com/blog/assets/kimi-k31-pro-multilingual-test/complete_results.csv" rel="noopener noreferrer"&gt;Per-item results CSV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wallabytoken.com/blog/assets/kimi-k31-pro-multilingual-test/complete_summary.json" rel="noopener noreferrer"&gt;Summary JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wallabytoken.com/blog/assets/kimi-k31-pro-multilingual-test/complete_cost_actual.json" rel="noopener noreferrer"&gt;Cost breakdown JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wallabytoken.com/blog/assets/kimi-k31-pro-multilingual-test/complete_evidence_manifest.json" rel="noopener noreferrer"&gt;Evidence manifest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public datasets: &lt;a href="https://github.com/DojunPark/MultiPragEval" rel="noopener noreferrer"&gt;MultiPragEval&lt;/a&gt;, &lt;a href="https://github.com/mainlp/Multilingual-IQA" rel="noopener noreferrer"&gt;Multilingual-IQA&lt;/a&gt;, &lt;a href="https://github.com/nlp-waseda/OpenMWE" rel="noopener noreferrer"&gt;OpenMWE&lt;/a&gt;, &lt;a href="https://github.com/kursathalat/ImplicaTR" rel="noopener noreferrer"&gt;ImplicaTR&lt;/a&gt;, &lt;a href="https://github.com/coltekin/turkish-idioms" rel="noopener noreferrer"&gt;Turkish Idioms&lt;/a&gt;, &lt;a href="https://github.com/ja-email-eval/JaSocial" rel="noopener noreferrer"&gt;JaSocial&lt;/a&gt;, &lt;a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0338524" rel="noopener noreferrer"&gt;JWCAS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We used &lt;code&gt;kimi-k3.1-pro&lt;/code&gt; with low reasoning effort and the same output-format requirements within each task. Objective answers were scored against their labels; open-ended generations were left unscored. We did not run a same-prompt, same-condition Claude comparison, so this article makes no Kimi–Claude superiority claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try your own local-language workflow
&lt;/h2&gt;

&lt;p&gt;Wallaby provides OpenAI-compatible access to curated open-weight models, itemized usage billing and automatic provider failover. &lt;strong&gt;Kimi K3 is live on Wallaby today&lt;/strong&gt;; the K3.1 Pro evaluation above does not imply that K3.1 Pro is available on Wallaby.&lt;/p&gt;

&lt;p&gt;New accounts receive &lt;strong&gt;$0.50 in free credit&lt;/strong&gt;, with no card required until top-up. You can &lt;a href="https://wallabytoken.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=kimi-k31-pro-multilingual-test" rel="noopener noreferrer"&gt;start your first API call&lt;/a&gt;, compare it with your own documents and see every token itemized. Self-serve top-ups start at $20.&lt;/p&gt;

&lt;p&gt;For the commercial details, see the current &lt;a href="https://wallabytoken.com/blog/p/kimi-k3-pricing?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=kimi-k31-pro-multilingual-test" rel="noopener noreferrer"&gt;Kimi K3 pricing&lt;/a&gt; and our broader &lt;a href="https://wallabytoken.com/blog/p/open-weight-llm-api-pricing?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=kimi-k31-pro-multilingual-test" rel="noopener noreferrer"&gt;open-weight API pricing comparison&lt;/a&gt;. If you are estimating a longer evaluation, our &lt;a href="https://wallabytoken.com/blog/p/agent-context-cost-audit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=kimi-k31-pro-multilingual-test" rel="noopener noreferrer"&gt;agent context cost audit&lt;/a&gt; shows how repeated context changes the bill.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>multilingual</category>
      <category>benchmarks</category>
    </item>
    <item>
      <title>We stopped grading agent prose and counted 240 tool calls</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Sun, 01 Nov 2026 13:04:18 +0000</pubDate>
      <link>https://dev.to/dawncoral/we-stopped-grading-agent-prose-and-counted-240-tool-calls-162a</link>
      <guid>https://dev.to/dawncoral/we-stopped-grading-agent-prose-and-counted-240-tool-calls-162a</guid>
      <description>&lt;p&gt;Our old experiment gave a footer instruction 0/20; the new one gave the same position 20/20 with three different models.&lt;/p&gt;

&lt;p&gt;The sentence did not suddenly become persuasive. We changed the experiment.&lt;/p&gt;

&lt;p&gt;In the old runs, the named feedback tool did not exist, and a judge scored the output prose. In the new runs, the agent could call a real &lt;code&gt;skill_feedback&lt;/code&gt; function. Every valid call wrote a row that we could retrieve from a ledger after the run. Across 240 executions, we counted tool calls instead of grading 240 explanations.&lt;/p&gt;

&lt;p&gt;A dev.to reader's criticism of our earlier statistics shaped the other half of the design. We pre-registered the step-versus-footer comparison, kept the per-task pairs and reported discordant cases rather than asking readers to interpret overlapping intervals.&lt;/p&gt;

&lt;p&gt;The new result contradicts our old story, but it also shows why the old number was weaker than we realized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero was not twenty refusals
&lt;/h2&gt;

&lt;p&gt;The original instruction sat at the end of the skill file. It named &lt;code&gt;skill_feedback&lt;/code&gt;, specified the arguments, and told the model to skip silently if the tool was unavailable.&lt;/p&gt;

&lt;p&gt;There was no such tool in that environment.&lt;/p&gt;

&lt;p&gt;A judge read each transcript and scored whether the output recorded a failure. Pooled across two forge-built Flash arms and one Pro arm, the score was 0/20. We discussed that result as if twenty agents had declined to act.&lt;/p&gt;

&lt;p&gt;Later, we found eight zero-byte files among the twenty outputs. Only twelve were non-empty. A model with no tool, an empty response and a text-only judge cannot support the behavioral claim we attached to that zero.&lt;/p&gt;

&lt;p&gt;The old dataset remains what it is. We cannot turn it into a tool-call experiment after the fact.&lt;/p&gt;

&lt;p&gt;For the next run, we made the action exist and gave it a record. &lt;code&gt;skill_feedback&lt;/code&gt; became a real harness function. A call counted only when it had valid arguments, matched the execution identity and appeared in the ledger afterwards.&lt;/p&gt;

&lt;p&gt;No judge read the prose to decide whether a call had happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reader had already challenged our statistics
&lt;/h2&gt;

&lt;p&gt;Before this follow-up, we published a different experiment comparing one-shot prompting with Skill Forge. One-shot recalled 118/120 fire-question judgments and Skill Forge recalled 110/120. Our first write-up leaned on overlapping Wilson intervals when discussing the gap.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/dawncoral/we-open-sourced-the-tool-that-failed-its-own-kill-rule-bfd"&gt;the comments&lt;/a&gt;, arhancanli explained why that was the wrong comparison. Both arms had answered the same questions, so the informative cases were the discordant pairs. The comment also challenged the independence assumption behind counting two trials of one question as separate draws.&lt;/p&gt;

&lt;p&gt;We recomputed the results. By question and trial, one-shot won 8 discordant pairs and Skill Forge won 0, giving p = 0.0078 in an exact two-sided sign test. Collapsed to unique questions, the result was 6-0 with p = 0.031.&lt;/p&gt;

&lt;p&gt;That correction was public. Its more useful effect showed up in this experiment's design.&lt;/p&gt;

&lt;p&gt;We registered step versus footer as the primary comparison before execution. We retained the pairing by task. We also decided how to handle engineering failures before looking at the result table.&lt;/p&gt;

&lt;p&gt;The reader did not supply the new hypothesis. The comment changed how we were willing to test it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 240-run matrix
&lt;/h2&gt;

&lt;p&gt;The models were DeepSeek V4.1 Flash 1017, Kimi K3.1 Pro and DeepSeek V4 Pro. Our local Flash arm name was &lt;code&gt;ds-v41-flash-1017-unverified&lt;/code&gt;, while the API returned &lt;code&gt;deepseek-flash&lt;/code&gt;; the build identifier is therefore not confirmed by the API.&lt;/p&gt;

&lt;p&gt;Each model received the same twenty anomaly tasks under four conditions. The feedback sentence was absent from the skill, placed in the footer, placed in its own section, or inserted as step 3 of 5 immediately after base step 2.&lt;/p&gt;

&lt;p&gt;Every condition shared a system prompt saying to call &lt;code&gt;skill_feedback&lt;/code&gt; when feedback was needed. Even the no-sentence arm therefore had a mandatory feedback instruction outside the skill.&lt;/p&gt;

&lt;p&gt;The new sentence was mandatory. It did not repeat the old silent-skip fallback, which is one more reason this was not a controlled rerun of the original 0/20 experiment.&lt;/p&gt;

&lt;p&gt;Here are the real-call counts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction inside the skill&lt;/th&gt;
&lt;th&gt;V4.1 Flash 1017&lt;/th&gt;
&lt;th&gt;K3.1 Pro&lt;/th&gt;
&lt;th&gt;V4 Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No sentence&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;td&gt;0/20&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Footer&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Own section&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step 3 of 5&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;17/20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three footer arms reached 20/20. Both Flash positioned arms reached 20/20 as well, and K3.1 Pro matched them. V4 Pro reached 20/20 in the footer and own-section arms, then 17/20 in the step arm.&lt;/p&gt;

&lt;p&gt;The old draft said the numbered step was the contract and the footer was a comment. The real-call matrix did not reproduce that advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the pairs before the percentages
&lt;/h2&gt;

&lt;p&gt;The pre-registered comparison was step against footer. For Flash, all 20 pairs had calls in both arms, giving p = 1. K3.1 Pro had the same 20 double-call pairs and p = 1. Excluding its footer API-error pair for the sensitivity analysis left 19 pairs, again with calls in both arms and p = 1.&lt;/p&gt;

&lt;p&gt;V4 Pro had 17 double-call pairs and three footer-only pairs, giving p = 0.25. The three step-arm failures were SSL and transport errors with zero completion. They were not cases where a completed run declined to call the tool.&lt;/p&gt;

&lt;p&gt;We kept the primary step count at 17/20 because that was the pre-registered rule. The sensitivity analysis removed the engineering failures and produced 17 double-call pairs, p = 1.&lt;/p&gt;

&lt;p&gt;Presented this way, the result does not depend on whether two confidence intervals happen to overlap. The reader can see the pairs, the exclusions and the exact test.&lt;/p&gt;

&lt;p&gt;That still buys a narrow conclusion: this experiment did not detect an advantage for step 3 of 5 over the footer. It does not prove equivalence, and it does not show that instruction placement is irrelevant elsewhere.&lt;/p&gt;

&lt;p&gt;Ceilings are bad places to look for small differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  What could have produced the ceiling
&lt;/h2&gt;

&lt;p&gt;The shared system prompt may have done much of the work. It named the tool in every arm, including the one without a sentence in the skill. Once the model knew about &lt;code&gt;skill_feedback&lt;/code&gt;, adding a mandatory mention in the footer may have been enough to make the call reliable.&lt;/p&gt;

&lt;p&gt;The contrast between absence and presence is descriptive. No-sentence arms scored 1/20, 0/20 and 1/20, while all footer arms scored 20/20. This matrix did not isolate why that happened, and it did not score whether the ending prose mentioned the instruction.&lt;/p&gt;

&lt;p&gt;There are practical limits too. The business actions in the tasks were simulated; only the feedback call was a real tool. Fixed arm order, unshuffled tasks and one execution per cell leave room for time and server-side drift.&lt;/p&gt;

&lt;p&gt;The 240 executions produced 240 unique terminal states, zero ledger mismatches and zero empty final outputs. Eight API errors remained in the record: three in V4 Pro's step arm, four in K3.1 Pro's no-sentence arm and one in its footer arm. The footer error followed a valid ledger row, which is why its call remained in the primary count.&lt;/p&gt;

&lt;p&gt;The new measurement is more concrete. It is not a universal theory of agent instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that survived the reversal
&lt;/h2&gt;

&lt;p&gt;We would still rather see an important action in a task plan than hope a closing note survives the run. Step 3 of 5 remains a reasonable implementation choice. This experiment simply does not give us evidence that it outperforms the footer.&lt;/p&gt;

&lt;p&gt;The stronger rule sits below placement. Build the tool before grading the sentence, then count the side effect where it lands. In this harness, that side effect was a ledger row with a skill name, scenario and failure line, tied back to a specific execution.&lt;/p&gt;

&lt;p&gt;That is also why the new 20/20 and the old 0/20 can coexist without choosing whichever number makes the better headline. One came from judged prose without the named tool. The other came from real calls in a harness whose shared prompt may have pushed positioned arms toward a ceiling.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/tree/main/extras/skill-forge?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=reader-feedback-redesigned-experiment" rel="noopener noreferrer"&gt;harness, pre-registration and raw results&lt;/a&gt; are open for inspection. The previous reader improved this run by recomputing our comparison; the same kind of review is welcome here.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token sells API access to Kimi K3, one of the models in this comparison.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>A dev.to comment changed how we test agent instructions</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Sun, 01 Nov 2026 13:03:48 +0000</pubDate>
      <link>https://dev.to/dawncoral/a-devto-comment-changed-how-we-test-agent-instructions-5997</link>
      <guid>https://dev.to/dawncoral/a-devto-comment-changed-how-we-test-agent-instructions-5997</guid>
      <description>&lt;p&gt;A reader named arhancanli caught a problem in our last experiment: we had used overlapping Wilson intervals to compare two arms that answered the same questions. The intervals said less than we thought. The paired recount said more: 8-0 against our tool, p = 0.0078.&lt;/p&gt;

&lt;p&gt;That comment changed the next experiment.&lt;/p&gt;

&lt;p&gt;For the follow-up, we registered step versus footer as the primary comparison before running it. We kept the per-task pairs. We also replaced judged prose with a real tool and a ledger. After 240 executions, the result was inconvenient in a new way: the footer scored 20/20 with all three models, and moving the instruction into the numbered steps did not improve it.&lt;/p&gt;

&lt;p&gt;This is the chain from a comment to a different experimental design, and then to a result we did not expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comment that forced the recount
&lt;/h2&gt;

&lt;p&gt;The earlier post compared one-shot prompting with Skill Forge, our pipeline for building skill files. One-shot recalled 118 of 120 fire-question judgments. Skill Forge recalled 110. We reported overlapping Wilson intervals and initially treated the overlap as evidence that the gap was unclear.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/dawncoral/we-open-sourced-the-tool-that-failed-its-own-kill-rule-bfd"&gt;the dev.to discussion&lt;/a&gt;, arhancanli pointed out two problems. First, two trials of the same question were not independent draws. Second, overlapping intervals were the wrong comparison for arms that had answered identical questions.&lt;/p&gt;

&lt;p&gt;The useful unit was the discordant pair: cases where one arm passed and the other failed. We went back to the per-question results and counted them. By question and trial, one-shot won 8 pairs and Skill Forge won 0. The exact two-sided sign test gave p = 0.0078. Collapsing to unique questions gave 6-0 and p = 0.031.&lt;/p&gt;

&lt;p&gt;The correction made our result worse. It also made the analysis better.&lt;/p&gt;

&lt;p&gt;We updated the public write-up with the paired counts. More importantly, we carried the criticism into the next experiment instead of leaving it in a correction paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three decisions before the next run
&lt;/h2&gt;

&lt;p&gt;The next question concerned instruction placement. If a skill tells an agent to report a failure, does it matter whether that instruction sits in a closing footer or inside the numbered task steps?&lt;/p&gt;

&lt;p&gt;Our first design decision came straight from the comment: compare paired outcomes, not two separate success rates with overlapping intervals. We pre-registered step versus footer as the primary comparison before executing the matrix.&lt;/p&gt;

&lt;p&gt;The second decision was to count an action rather than ask a judge whether the transcript sounded compliant. The harness exposed a real &lt;code&gt;skill_feedback&lt;/code&gt; function. A valid call appended a row containing the skill name, scenario and failure line. After each run, we could re-read the ledger and match the row to the execution.&lt;/p&gt;

&lt;p&gt;The third decision was to register the exclusions before seeing the results. Transport failures could still occur, but the primary analysis would follow the pre-registration rather than a rule chosen after the table looked interesting.&lt;/p&gt;

&lt;p&gt;These changes did not make the experiment perfect. They did make three criticisms easy to answer: which comparison was primary, what counted as a pair, and which failures could be removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The older zero had a measurement problem too
&lt;/h2&gt;

&lt;p&gt;There was another reason to change the measurement. In an older round, a footer instruction had scored 0/20. We had treated that number as twenty examples of the model failing to act.&lt;/p&gt;

&lt;p&gt;The environment told a different story. No &lt;code&gt;skill_feedback&lt;/code&gt; tool existed in those runs. The instruction itself said to skip silently if the tool was missing. A judge then read the output text and scored whether it recorded a failure.&lt;/p&gt;

&lt;p&gt;Eight of the twenty output files were also zero bytes. The non-empty denominator was twelve, a detail nobody had counted when we first quoted the zero.&lt;/p&gt;

&lt;p&gt;So the old 0/20 was a text score collected without the named tool. It was not twenty observed refusals to call that tool.&lt;/p&gt;

&lt;p&gt;The new run could not repair that old dataset. It could stop us from repeating the same measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three models, four positions, 240 executions
&lt;/h2&gt;

&lt;p&gt;We tested DeepSeek V4.1 Flash 1017, Kimi K3.1 Pro and DeepSeek V4 Pro. The Flash arm carried the local label &lt;code&gt;ds-v41-flash-1017-unverified&lt;/code&gt;; the API returned &lt;code&gt;deepseek-flash&lt;/code&gt;, so the API did not confirm the 1017 build identifier.&lt;/p&gt;

&lt;p&gt;Each model ran twenty anomaly tasks in four arms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No feedback sentence inside the skill.&lt;/li&gt;
&lt;li&gt;The sentence in the footer.&lt;/li&gt;
&lt;li&gt;The sentence in its own section.&lt;/li&gt;
&lt;li&gt;The sentence as step 3 of 5, immediately after base step 2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The mandatory sentence was otherwise unchanged across the positioned arms. Every arm also shared a system prompt: if you need feedback, you must call &lt;code&gt;skill_feedback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last detail matters. The no-sentence arm had no feedback sentence in the skill, but the model still had an instruction in the system prompt.&lt;/p&gt;

&lt;p&gt;Three models, four arms and twenty tasks produced 240 executions. The ledger, not a judge reading the final prose, supplied the counts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;V4.1 Flash 1017&lt;/th&gt;
&lt;th&gt;K3.1 Pro&lt;/th&gt;
&lt;th&gt;V4 Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No sentence in the skill&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;td&gt;0/20&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Footer&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Own section&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step 3 of 5&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;17/20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The footer worked everywhere.&lt;/p&gt;

&lt;p&gt;The numbered step did not beat it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The paired result, reported first
&lt;/h2&gt;

&lt;p&gt;Flash produced 20 step-footer pairs with calls in both arms, so p = 1. K3.1 Pro also produced 20 double-call pairs and p = 1. Its sensitivity analysis excluded one footer API-error pair, leaving 19 double-call pairs and p = 1.&lt;/p&gt;

&lt;p&gt;V4 Pro produced 17 double-call pairs and three footer-only pairs. The primary paired result was p = 0.25. Those three missing step calls were SSL and transport errors with zero completion, not model refusals. The pre-registered primary count nevertheless stayed at 17/20 for the step arm. Removing the engineering failures in the sensitivity analysis left 17 pairs with calls in both arms and p = 1.&lt;/p&gt;

&lt;p&gt;This is where the earlier comment changed our behavior. Instead of printing two percentages and inviting the reader to guess whether they agreed, we could show the pair structure and the exact test first.&lt;/p&gt;

&lt;p&gt;The result still needs a narrow reading. It did not detect a step advantage in this environment. It does not establish that footers and steps are equivalent in general, or that instruction position never matters.&lt;/p&gt;

&lt;p&gt;A null result at a ceiling has limited reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the ceiling may be there
&lt;/h2&gt;

&lt;p&gt;Every arm carried the shared system instruction about &lt;code&gt;skill_feedback&lt;/code&gt;. That may have made the action so salient that any mention inside the skill was enough to push the run toward 20/20. The no-sentence arm remained near zero, while footer, own section and step reached the ceiling or approached it.&lt;/p&gt;

&lt;p&gt;The tool also changed what a skip looked like. In the old environment, a missing tool and a declined call could both end as prose with no verifiable action. In the new harness, a missing ledger row was visible. Valid arguments and a matching execution identity could be checked after the run.&lt;/p&gt;

&lt;p&gt;We changed more than the measurement. The old instruction allowed a silent skip when the tool was missing; the new sentence was mandatory. The new run was therefore not a controlled rerun of the old experiment, and it cannot isolate sentence wording, placement and tool availability as separate causes.&lt;/p&gt;

&lt;p&gt;The business actions in the anomaly tasks were simulated. Only &lt;code&gt;skill_feedback&lt;/code&gt; was a real tool. This shows whether the models called a feedback tool they were told about, not whether they completed a real business workflow.&lt;/p&gt;

&lt;p&gt;The arm order was fixed and the tasks were not shuffled. Each cell ran once, so time and server-side drift remain possible confounds. Across the 240 executions there were eight API errors: three in the V4 Pro step arm, four in the K3.1 Pro no-sentence arm and one in its footer arm. The footer error occurred after a valid ledger row, so the primary count retained that cell.&lt;/p&gt;

&lt;p&gt;Those constraints belong beside the 20/20, not below a triumphant conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What survived from both critiques
&lt;/h2&gt;

&lt;p&gt;The statistical critique left us with a concrete rule: when two arms answer the same tasks, preserve the pairs and report the discordant cases. In this run, the primary comparison and its sensitivity analyses were visible before anyone had to ask for them in the comments.&lt;/p&gt;

&lt;p&gt;The measurement critique left us with another: grade the side effect you can retrieve. A &lt;code&gt;skill_feedback&lt;/code&gt; row either matched the execution or it did not. There were 240 unique terminal states, zero ledger mismatches and zero empty final outputs in the new matrix.&lt;/p&gt;

&lt;p&gt;Together, those rules explain why we now trust this null result more than the old zero. The old number required a judge to infer behavior from prose in an environment missing the tool. The new number came from a pre-registered comparison over readable tool records.&lt;/p&gt;

&lt;p&gt;This time, all 60 footer executions left a matching ledger row.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/tree/main/extras/skill-forge?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=reader-feedback-redesigned-experiment" rel="noopener noreferrer"&gt;harness, pre-registration and raw results&lt;/a&gt; are public. If you find another design flaw, the useful response is the one arhancanli gave us: name the unit, show the comparison and, if you can, recompute it.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token sells API access to Kimi K3, one of the models in this comparison.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>A reported $2.3 API bill doesn't tell you what the agent costs.</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Sat, 31 Oct 2026 03:14:33 +0000</pubDate>
      <link>https://dev.to/dawncoral/a-reported-23-api-bill-doesnt-tell-you-what-the-agent-costs-20i2</link>
      <guid>https://dev.to/dawncoral/a-reported-23-api-bill-doesnt-tell-you-what-the-agent-costs-20i2</guid>
      <description>&lt;p&gt;The spend reported to us for this line of work was ¥16.8, about $2.3 at roughly 7.2 RMB to the dollar. In a separate client-side reading for 28 October, the median request carried about 158k cache-read tokens. Neither reading answers the question people actually ask, which is what the agent cost per useful output.&lt;/p&gt;

&lt;p&gt;This piece is about the gap between them, and why we are not going to close it with arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three numbers that look like the same number
&lt;/h2&gt;

&lt;p&gt;There are three denominators in any agent cost story.&lt;/p&gt;

&lt;p&gt;The invoice: money, in the currency you were charged, for a period of time. It is the number a finance person needs and the number every cost post quotes.&lt;/p&gt;

&lt;p&gt;Wire volume: tokens the client sends and receives. Cache reads, fresh input, output, retries. It is the number an engineer can measure locally, per request, without asking anyone.&lt;/p&gt;

&lt;p&gt;The unit cost: money per accepted output. It is the only one of the three that tells you whether a change to the agent was worth making.&lt;/p&gt;

&lt;p&gt;You cannot convert one into another without three extra facts: the rate charged for each token class, which stages of work the invoice covers, and a quality gate that decides what counts as accepted. We have one of the three numbers cleanly, one partially, and the third not at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reported figure covers, and what it cannot
&lt;/h2&gt;

&lt;p&gt;The figure is reported, not verified. It came to us from the person who paid it, and our repository holds no independent receipt or stage-level breakdown.&lt;/p&gt;

&lt;p&gt;That is the first limit. The second is coverage. This program ran a pre-registered feedback experiment with 240 executions, a structure experiment with 180 executions and 180 judge calls, a calibration pass, smoke tests, and retries caused by transport errors. Nothing in our logs assigns a share of the spend to any of those stages, and we cannot verify which of them the figure covers.&lt;/p&gt;

&lt;p&gt;So we do not use about $2.3 as the cost of either experiment, and we do not treat it as a verified total for both. It is a reported amount attached to a line of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the client carries on every request
&lt;/h2&gt;

&lt;p&gt;Our metering script reads the client session logs and reports, per request, how much of the prompt was already-seen context.&lt;/p&gt;

&lt;p&gt;For 28 October it counted 795 requests with a median of 158k cache-read tokens per request and a 90th percentile of 421k. Cache reads summed to 151,017,550 tokens. Fresh input summed to 9,146,736. In this client log, re-read context is the largest input-token class: 16.5 times the fresh-input volume. That is a volume ratio, not evidence about which token class dominated the bill.&lt;/p&gt;

&lt;p&gt;Two cautions before anyone quotes that number.&lt;/p&gt;

&lt;p&gt;It is not fresh input, not total context, and not a bill. That number is one class of token, from one meter, on one machine.&lt;/p&gt;

&lt;p&gt;An earlier saved note records 768 requests and a 159k median for the same date; the later rescan returned 795 and 158k. We cannot reconstruct the cause of that change from the saved evidence, so we treat the readings as diagnostics, not accounting figures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the two experiments record
&lt;/h2&gt;

&lt;p&gt;P1 has 240 terminal executions. All 411 recorded completions include usage, but seven API-error cells have no completion or usage record, so usage coverage is not 240 out of 240 executions. Where usage exists, output totals were 99,654 tokens for the arm labelled DeepSeek V4.1 Flash 1017, 110,401 for DeepSeek V4 Pro, and 98,266 for Kimi K3.1 Pro. The API returned only deepseek-flash for the first of those, so the 1017 build is unverified.&lt;/p&gt;

&lt;p&gt;That is a token total, not money. We never stored a rate or a currency on those runs. The token column is partial and the money column does not exist.&lt;/p&gt;

&lt;p&gt;P2 recorded 180 terminal judge runs across the 90 ambiguous cells: 179 parsed successfully and one ended in a judge-parse error. One third of the executions lack execution-usage records. Judge usage exists, but the missing execution usage prevents a complete P2 token total.&lt;/p&gt;

&lt;p&gt;There is a smaller distortion on top. Eight P1 cells ended with API or transport errors. Seven have no completion record, so their token use is unknown; the eighth had already written a valid feedback ledger before the final error. The three V4 Pro step errors had no completion, which is why that arm reads 17/20 in the primary count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two numbers that look alike
&lt;/h2&gt;

&lt;p&gt;P2 records 159,215 execution output tokens for V4 Pro in aggregate. The client meter reports about 158k cache-read tokens per request for 28 October. The digits are similar and the quantities have nothing to do with each other: one is generated text, the other is re-read input.&lt;/p&gt;

&lt;p&gt;The same discipline applies to the older figures. Our skill furnace program cost ¥10.6 in API fees. A separate Claude review cost $10.04. Neither is comparable to the reported $2.3 figure here: different programs, different models, different currencies, different coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost per accepted output
&lt;/h2&gt;

&lt;p&gt;If you want one number for design decisions, it is money per accepted output. There is a longer argument for it on the blog; this program is a case where the denominator is unusually well defined.&lt;/p&gt;

&lt;p&gt;P2 has a quality gate: two judges for each of the 90 ambiguous cells, each scoring against a pre-registered rubric, for 180 terminal judge runs. That is the kind of gate an accepted-output count needs.&lt;/p&gt;

&lt;p&gt;It also has a problem. In our last piece, we did not detect a full-versus-shell quality difference. If two configurations produce indistinguishable quality, cost per accepted output becomes the thing that separates them. We cannot compute it: the numerator is a reported figure with no stage split, and one third of the executions have no usage record.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number we do not have
&lt;/h2&gt;

&lt;p&gt;We are not going to reverse-engineer it.&lt;/p&gt;

&lt;p&gt;Multiplying the token totals by our public rate card would produce a number with the shape of a cost and none of the substance: the wrong currency, the wrong rate class, and no stage attribution. We would rather say the number is missing.&lt;/p&gt;

&lt;p&gt;What it would take is three small changes, and we are starting on them.&lt;/p&gt;

&lt;p&gt;Price each stage in the run log, at the rate actually charged, not at the list rate we publish.&lt;/p&gt;

&lt;p&gt;Keep execution usage and judge usage in the same record, so a token total can be checked against a completion count.&lt;/p&gt;

&lt;p&gt;Keep a ledger that says which outputs passed, so the denominator is a query and not a memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in our pipeline
&lt;/h2&gt;

&lt;p&gt;We are adopting two rules after this program.&lt;/p&gt;

&lt;p&gt;A cell without usage is marked incomplete, never zero. P2 taught us that the hard way: sixty executions carried no usage, and a zero would have looked like a free run.&lt;/p&gt;

&lt;p&gt;Every cost claim carries its stage scope in the same line. A reported total says reported total. A one-day meter reading says one day and one machine.&lt;/p&gt;

&lt;p&gt;Neither rule makes the analysis cheaper. They make it impossible to quote a number we cannot defend.&lt;/p&gt;

&lt;p&gt;The reported figure tells you something about what the work cost to run. It does not tell you what the agent costs. For that you need the wire reading and the accepted-output denominator. We have one and a half of the three, and publishing the half we have would be the easy mistake.&lt;/p&gt;

&lt;p&gt;The metering script, the experiment harness and the raw results live in the open repository: &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/tree/main/extras/skill-forge?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=api-bill-vs-agent-cost" rel="noopener noreferrer"&gt;Dawncoral/wallaby-agent-rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Related: &lt;a href="https://wallabytoken.com/blog/p/skill-contracts-vs-ceremony" rel="noopener noreferrer"&gt;the two structure arms this program compared&lt;/a&gt; and &lt;a href="https://wallabytoken.com/blog/p/kimi-k3-monthly-cost" rel="noopener noreferrer"&gt;a real monthly bill from our own workloads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token sells access to Kimi K3, and K3.1 Pro spend is the reported figure discussed here.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>cost</category>
      <category>programming</category>
    </item>
    <item>
      <title>The footer scored zero. The real tool log gave it 20/20.</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Sat, 31 Oct 2026 03:13:49 +0000</pubDate>
      <link>https://dev.to/dawncoral/the-footer-scored-zero-the-real-tool-log-gave-it-2020-27d4</link>
      <guid>https://dev.to/dawncoral/the-footer-scored-zero-the-real-tool-log-gave-it-2020-27d4</guid>
      <description>&lt;p&gt;In our old scoring, the closing footer fired zero times out of twenty. We moved the same sentence into the numbered steps, the score moved, and we wrote a post about addresses: the footer is a comment, the step is a contract.&lt;/p&gt;

&lt;p&gt;Then we built a real tool with a ledger you can re-read, and the footer scored twenty out of twenty.&lt;/p&gt;

&lt;p&gt;Before you file that under contradiction: the old number was not an action. It was a text score, produced in an environment where no feedback tool existed. And the sentence we were testing ended with an instruction to skip silently if the tool was missing. The model may have been obeying us.&lt;/p&gt;

&lt;p&gt;This was not a controlled rerun of the old experiment. The tool and the scoring rule changed, and P1 also used a different feedback sentence. The old silent-skip fallback was not retested; P1 used a mandatory sentence without that clause.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed: tool, scoring rule and sentence
&lt;/h2&gt;

&lt;p&gt;The old run had no &lt;code&gt;skill_feedback&lt;/code&gt; tool. The skill files ended with a closing line naming the tool, its arguments, and the fallback. A judge then read each transcript and scored the text: did the output record a failure.&lt;/p&gt;

&lt;p&gt;Pooled across the two forge-built Flash arms and one Pro arm, that score was 0/20. Of the twenty output files behind it, eight were zero bytes. The non-empty denominator was twelve. Nobody counted that at the time. We reported 0/20 as if it were twenty observations of a refusal.&lt;/p&gt;

&lt;p&gt;The new run has a tool. &lt;code&gt;skill_feedback&lt;/code&gt; is a real function in the harness. Every call appends a row to a ledger with the skill name, the scenario and the failure line. A call counts only if it appears as a real tool call, with valid arguments and a matching identity, and can be re-read from the ledger afterwards. No judge reads the prose. 240 executions, zero judgment calls.&lt;/p&gt;

&lt;p&gt;The measurement now counts a real side effect where it lands; the comparison with the old run does not isolate sentence quality or position as the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three models, four positions, 240 executions
&lt;/h2&gt;

&lt;p&gt;Three models, four arms, twenty anomaly tasks per model. The models were DeepSeek V4.1 Flash 1017, Kimi K3.1 Pro and DeepSeek V4 Pro. The first is labelled 1017 only in our local arm name, ds-v41-flash-1017-unverified; the API returned deepseek-flash, so the build is not confirmed by the API. The arms differ only in where the same mandatory sentence sits: nowhere inside the skill, in the footer, in its own section, or as step 3 of 5, inserted right after base step 2. Every arm, including the one with no sentence, carries a shared system prompt: if you need feedback, call &lt;code&gt;skill_feedback&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;V4 Pro&lt;/th&gt;
&lt;th&gt;V4.1 Flash 1017&lt;/th&gt;
&lt;th&gt;K3.1 Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No sentence in the skill&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;td&gt;1/20&lt;/td&gt;
&lt;td&gt;0/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Footer&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Own section&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step 3 of 5&lt;/td&gt;
&lt;td&gt;17/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;td&gt;20/20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pre-registered comparison was step versus footer. Flash: 20 pairs, all double-called, p = 1. K3.1 Pro: 20 pairs, all double-called, p = 1; the sensitivity analysis excludes the footer API-error pair and gives 19 pairs, all double-called, p = 1. V4 Pro: 17 double calls and 3 footer-only, p = 0.25, and the sensitivity analysis that drops engineering failures gives 17 pairs, p = 1.&lt;/p&gt;

&lt;p&gt;The three V4 Pro gaps deserve their own line. All three are SSL and transport errors with zero completion. They are not the model declining to call the tool. We keep the primary number at 85% because the pre-registration says so, and we report the 17-of-17 sensitivity beside it.&lt;/p&gt;

&lt;p&gt;Footer reached 20/20 in all three models, and the pre-registered comparison did not detect a step advantage in this environment. That null result does not establish equivalence or show that position is generally irrelevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the footer works now
&lt;/h2&gt;

&lt;p&gt;The obvious answer is that the tool exists, so a sentence that names it has something to point at. A skip is also visible now: no ledger row. That lets us separate declined from could not call, and it is one reason the old number and this one are not comparable. P1 did not retest the old fallback clause itself.&lt;/p&gt;

&lt;p&gt;Position may still do something. It decides whether the action sits inside the plan the model executes, or outside it as advice about how to work. The tool alone did not account for the ceiling: none stayed at 1/20, 0/20 and 1/20 while the footer reached 20/20 in every model. That presence-versus-absence contrast is descriptive, and P1 did not score whether the ending mentioned the instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part of the old lesson that survives
&lt;/h2&gt;

&lt;p&gt;One claim from the old draft still holds, and the new data sharpens it instead of erasing it. A rule that lives only in prose is not verifiable. You cannot tell compliance from narration by reading the transcript, because a model that narrates well produces the same surface as a model that acted.&lt;/p&gt;

&lt;p&gt;We learned this the expensive way in our memory work: writes that reported success and never landed, discovered only when someone re-read the store instead of the summary. The report is not the action. The footer is not the call.&lt;/p&gt;

&lt;p&gt;What the new run adds is the fix, and the fix is boring. Make the action write something you can re-read, then count that. Once the ledger exists, the argument about footers and steps becomes a secondary question.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this corrects
&lt;/h2&gt;

&lt;p&gt;Two statements from the old draft need to go.&lt;/p&gt;

&lt;p&gt;The first is the headline number read as behavior. "The instruction fired zero times" was true of a text score in a tool-less environment. It was not twenty observations of a model refusing. Eight of the twenty outputs were empty, and the instruction we wrote told the model to skip when the tool was missing.&lt;/p&gt;

&lt;p&gt;Statement two is the final line, "We have not run a follow-up." We have. It is this piece.&lt;/p&gt;

&lt;p&gt;Published posts are not being rewritten. Our rule is that live posts stay as published, and this article carries the correction instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Every arm, including the no-sentence arm, carried the same line in the system prompt: if you need feedback, call &lt;code&gt;skill_feedback&lt;/code&gt;. That line is not a variable in this experiment. It plausibly pushes every arm toward the ceiling, and a ceiling is what a null result looks like. The no-sentence arm means the skill contains no such sentence. It does not mean the model had no feedback instruction.&lt;/p&gt;

&lt;p&gt;Only &lt;code&gt;skill_feedback&lt;/code&gt; is a real tool. The business actions inside the tasks are simulated, so this measures whether the model calls a tool it was told about. It does not measure whether it can complete a real workflow.&lt;/p&gt;

&lt;p&gt;The arm order was fixed, the tasks were not shuffled, and each cell ran once, so time and server-side drift cannot be ruled out. The Flash build label is our own, as noted above; the API never returned a build identifier.&lt;/p&gt;

&lt;p&gt;240 executions produced 240 unique terminal states, with zero ledger mismatches and zero empty finals. There were eight API errors: three in the V4 Pro step arm, four in the K3.1 Pro no-sentence arm, one in its footer arm. The footer error landed after a valid ledger row, which is why the primary count keeps that cell.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule we now run
&lt;/h2&gt;

&lt;p&gt;Build the tool before you grade the sentence. If the action cannot be observed, you are grading prose, and prose is the one thing a good model always produces.&lt;/p&gt;

&lt;p&gt;Then count the side effect instead of the self-report. One readable row per action, no judge.&lt;/p&gt;

&lt;p&gt;Then put the instruction where the model's plan is, and expect the tool to do the work. Step 3 of 5 is a reasonable place for it. This run does not show that place matters more than the footer.&lt;/p&gt;

&lt;p&gt;The footer did not become persuasive. It became auditable.&lt;/p&gt;

&lt;p&gt;The harness, the pre-registration and the raw results are open: &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/tree/main/extras/skill-forge?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=skill-feedback-footer-vs-step" rel="noopener noreferrer"&gt;Dawncoral/wallaby-agent-rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Related from the same series: &lt;a href="https://wallabytoken.com/blog/p/skill-engineering-three-regimes" rel="noopener noreferrer"&gt;the three-regimes map&lt;/a&gt; and &lt;a href="https://wallabytoken.com/blog/p/ai-memory-write-failures" rel="noopener noreferrer"&gt;writes that report success&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token sells API access to Kimi K3, one of the models in this comparison.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>Skill contracts vs. ceremony: a narrow retest</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Sat, 31 Oct 2026 03:09:22 +0000</pubDate>
      <link>https://dev.to/dawncoral/skill-contracts-vs-ceremony-a-narrow-retest-3l0j</link>
      <guid>https://dev.to/dawncoral/skill-contracts-vs-ceremony-a-narrow-retest-3l0j</guid>
      <description>&lt;p&gt;Kimi K3.1 Pro failed six of ten clear tasks with no skill file. Give it a skill and it failed one. That is the number we expected. The number we did not expect: a thin shell scored the same nine out of ten as the full scaffold.&lt;/p&gt;

&lt;p&gt;We ran 180 executions and 180 judgments across three models and three arms to check a claim we had published ourselves. The claim was that full structure starts to drag on strong models once the task gets fuzzy. Over 28 valid pairs on the main metric, we did not reproduce it.&lt;/p&gt;

&lt;p&gt;If you maintain a SKILL.md or an AGENTS.md, the useful question is about which parts of the file a single run can see, and which parts only a maintainer can. The retest below is narrow on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The retest, and what it was built to kill
&lt;/h2&gt;

&lt;p&gt;Two weeks ago we published a map with three regions. A weak model on routine work: structure pays. A strong model on routine work: a ceiling. A strong model on hard work: the freeze, where one-shot files beat the forge-built ones and the skill's own boundary clauses were quoted as the reason to stop.&lt;/p&gt;

&lt;p&gt;The K3 postscript that failed to reproduce the map's third region used one judge, three to ten executions per cell, and no pre-registration. We treated that smoke test as a warning and built P2 to retest it.&lt;/p&gt;

&lt;p&gt;Three models, three arms, twenty tasks. The models were DeepSeek V4.1 Flash 1017, Kimi K3.1 Pro and DeepSeek V4 Pro. The Flash build is named only in our local arm label, ds-v41-flash-1017-unverified; the API returned deepseek-flash, so treat 1017 as declared rather than confirmed. The arms were no skill, a full skill file, and a shell of the same file. Ten tasks were fuzzy, across five families. Ten were clear, with a mechanical pass condition. Each cell ran once. Clear tasks were checked mechanically; each fuzzy execution was judged twice.&lt;/p&gt;

&lt;p&gt;The shell is the only new object here. We removed the description, the closing skill_feedback logging step, and the empty pitfall placeholder. The numbered task steps, the specific red lines, the safety constraints and the completion criteria stayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the hard tasks, we did not detect a difference
&lt;/h2&gt;

&lt;p&gt;The main metric was unjustified stop: the run quits while a safe partial result was still available. We pre-registered it, which is why the result is allowed to be boring.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Stops&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full scaffold&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;7.14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thin shell&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10.71%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our primary comparison keeps only cells where both arms returned normally and both judges agreed on this field. That left 28 pairs. Full stopped twice, shell three times. The difference is 3.57 percentage points in the shell's favor, exact McNemar p = 1. For unjustified_stop only, the preregistered full-favorable and shell-favorable sensitivity analyses gave p = 0.25 and p = 1 respectively; neither detected a significant structural difference.&lt;/p&gt;

&lt;p&gt;Read that as narrowly as it is written. We did not detect the drag. We did not show the two arms are equal, and nothing here says a strong model needs a skill file in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clear tasks are where the split shows
&lt;/h2&gt;

&lt;p&gt;A clear task has a mechanical pass condition: required fields present, banned fields absent. Literal compliance, not a semantic quality score.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;No skill&lt;/th&gt;
&lt;th&gt;Full&lt;/th&gt;
&lt;th&gt;Shell&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;5/10&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4.1 Flash 1017&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K3.1 Pro&lt;/td&gt;
&lt;td&gt;4/10&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pooled&lt;/td&gt;
&lt;td&gt;16/30&lt;/td&gt;
&lt;td&gt;24/30&lt;/td&gt;
&lt;td&gt;25/30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The observed gains are in literal task-following scores, not general capability, and they are concentrated in V4 Pro and K3.1 Pro; both skill arms yield the same numerical gains for those models in this set. The Flash build barely moves. The skill is not making the models smarter. It is telling them what this job's output has to contain, which they cannot infer from a one-line task statement.&lt;/p&gt;

&lt;p&gt;Read the order in that table as a flat list, not a ranking; these single-run literal scores do not establish a general capability ordering.&lt;/p&gt;

&lt;p&gt;A shell gets the same numerical gain. That is the part worth keeping. The design does not isolate which sections produced it, so it does not show that the deleted material never matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line that moved: contract versus ceremony
&lt;/h2&gt;

&lt;p&gt;Our old axes were model strength and task fuzziness. Those still describe something. They do not describe what a skill file is doing at the moment it helps.&lt;/p&gt;

&lt;p&gt;The axis that survived is what the file is for. A contract is what one run must satisfy: the steps, the red lines, the definition of done. Ceremony is what a maintainer does between runs: tuning the description, writing the failure ledger, accumulating pitfall notes, keeping the ritual alive. Contract changes the run. Ceremony changes the next file.&lt;/p&gt;

&lt;p&gt;A strong model already knows how to work. What it cannot know is your banned field, your definition of finished, and which shortcut is a red line in your domain. Those sentences are the cheap part of the file. The ceremony is the expensive part, and a single run cannot see it. This run cannot separate which sentences did the work.&lt;/p&gt;

&lt;p&gt;That reframes the freeze we published. The clauses that stopped the model in that run were boundary clauses written in a stop-unless-everything-matches mood. That is a contract problem. The model was obeying the file. We had written the wrong instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is not "delete your skill file"
&lt;/h2&gt;

&lt;p&gt;The shell is still a substantial document. It keeps the numbered task steps minus the closing logging step, plus the red lines, the safety constraints and the completion criteria. A shell with the safety section deleted would be a different experiment, and probably a worse one. We did not run it.&lt;/p&gt;

&lt;p&gt;All three arms carried a shared instruction telling the model to deliver any part it could complete safely and not to stall the whole task without cause. That instruction targets the main metric directly. It plausibly created a floor, and a floor is exactly what a null result looks like. The low stop rate may belong to the shared prompt rather than to any arm.&lt;/p&gt;

&lt;p&gt;The sample is small in the ways that matter. Five task families, ten fuzzy tasks, one round per cell. The 28 pairs are model-by-task cells, not 28 different questions. The business tools in the tasks are simulated. The judges disagreed on 29 field-level items across 23 cells, and we kept those disagreements rather than resolving them upward. One no-skill judgment failed to parse and was excluded under the pre-registration; it did not affect the full/shell primary comparison. The Flash build label is our own, as noted above; the API never returned a build identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we now ask before shipping a skill
&lt;/h2&gt;

&lt;p&gt;Three questions, in this order.&lt;/p&gt;

&lt;p&gt;What must one run do to be finished? Write that down, including the banned outputs and the one shortcut that would embarrass you. This is the contract, and it is the part a strong model cannot invent.&lt;/p&gt;

&lt;p&gt;Which parts does only a maintainer care about? The description wording, the ledger format, the note about a pitfall from three weeks ago. Those belong in a changelog, a lint, or a review checklist. Inside the skill file they are read once and then forgotten.&lt;/p&gt;

&lt;p&gt;Where is the check that fails the run? A contract without an observable artifact is still a comment.&lt;/p&gt;

&lt;p&gt;The size rule from our own measurements points the same way. Files grow because ceremony is easy to add and hard to delete. If the contract is four sections, the file should be close to four sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changed in our pipeline
&lt;/h2&gt;

&lt;p&gt;In our own pipeline, we now keep the maintenance layer out of the file that one run loads. The forge pipeline still writes the ledger and the pitfall notes, because a maintainer reads them. It no longer pastes them into the file that one run loads, unless a task family has shown that it needs them.&lt;/p&gt;

&lt;p&gt;That is a routing decision inside the skill, not a smaller skill for its own sake.&lt;/p&gt;

&lt;p&gt;The honest summary is narrower than the headline. On these three models, with this file and this harness, we did not detect a full-versus-shell difference in unjustified stops, and the clear-task scores were close. This does not establish general equivalence or show that every strong model needs a skill file. What it does support is narrower: a contract-shaped file is worth keeping, and the maintenance layer is the part to question.&lt;/p&gt;

&lt;p&gt;The harness, the pre-registration and the raw results are in the open repository: &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/tree/main/extras/skill-forge?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=skill-contracts-vs-ceremony" rel="noopener noreferrer"&gt;Dawncoral/wallaby-agent-rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two related pieces from the same run of work: &lt;a href="https://wallabytoken.com/blog/p/skill-engineering-three-regimes" rel="noopener noreferrer"&gt;the map this retests&lt;/a&gt; and &lt;a href="https://wallabytoken.com/blog/p/skill-file-size-6kb-limit" rel="noopener noreferrer"&gt;the file size law&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token sells API access to Kimi K3, one of the models in this comparison.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI agent memory needs a verification step</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Fri, 30 Oct 2026 13:55:59 +0000</pubDate>
      <link>https://dev.to/dawncoral/ai-agent-memory-needs-a-verification-step-c81</link>
      <guid>https://dev.to/dawncoral/ai-agent-memory-needs-a-verification-step-c81</guid>
      <description>&lt;p&gt;An AI can update &lt;code&gt;MEMORY.md&lt;/code&gt; and still leave five other files describing the old project. We added a small CLI that makes those missed updates visible. It maps nine change types to the documents that need review, checks explicit premises, tracks contradictions, and only then marks a change verified.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;extras/memory-governance/&lt;/code&gt; pack in &lt;code&gt;wallaby-agent-rules&lt;/code&gt; does this without a model, a database, or a service. One Python file. JSON for state. A fresh install takes about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first run
&lt;/h2&gt;

&lt;p&gt;From the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 extras/memory-governance/memory_governance.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; /path/to/your-project init
&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your-project
python3 scripts/memory_governance.py doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fresh install ends with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTRADICTIONS open=0
RECEIPTS pending=0
DOCTOR OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; never overwrites an existing config or script. The core memory files stay independent, so you can add the pack to a project that already has &lt;code&gt;MEMORY.md&lt;/code&gt;, &lt;code&gt;NOW.md&lt;/code&gt;, and &lt;code&gt;INDEX.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rename is not done when one file changes
&lt;/h2&gt;

&lt;p&gt;Tell the CLI which old term should disappear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/memory_governance.py impact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--event&lt;/span&gt; file_move &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--terms&lt;/span&gt; &lt;span class="s2"&gt;"old-component"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--record&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our clean-project demo, it found one reference in &lt;code&gt;README.md&lt;/code&gt; and created receipt &lt;code&gt;R-000001&lt;/code&gt;. The first verification failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NOT VERIFIED R-000001: 1 unresolved and 0 unread file(s)
  README.md (1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the useful part. A log entry saying “renamed” would not have caught the second reference.&lt;/p&gt;

&lt;p&gt;After updating the reference, &lt;code&gt;verify&lt;/code&gt; runs the scan again. If an old mention is intentional history, you allow that path explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/memory_governance.py verify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--receipt&lt;/span&gt; R-000001 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow&lt;/span&gt; &lt;span class="s2"&gt;"archive/**"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The impact and verification stay as separate records in &lt;code&gt;.memory/receipts.jsonl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Premises have failure signals, not just good intentions
&lt;/h2&gt;

&lt;p&gt;Some rules depend on a relationship staying true. A required file must exist. A JSON field must keep a specific value. A forbidden artifact must stay absent.&lt;/p&gt;

&lt;p&gt;The pack turns those into four deterministic checks: &lt;code&gt;file_missing&lt;/code&gt;, &lt;code&gt;file_exists&lt;/code&gt;, &lt;code&gt;text_contains&lt;/code&gt;, and &lt;code&gt;json_equals&lt;/code&gt;. It does not run shell commands from the config.&lt;/p&gt;

&lt;p&gt;There are three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P0&lt;/code&gt;: stop the action, exit 2.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;P1&lt;/code&gt;: require review, exit 1.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;P2&lt;/code&gt;: show a warning without blocking routine work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;code&gt;manual&lt;/code&gt; premise cannot silently pass at P0 or P1. It needs evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contradictions keep their history
&lt;/h2&gt;

&lt;p&gt;When a new statement replaces an old one, you record both and the evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/memory_governance.py contradict add &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--old&lt;/span&gt; &lt;span class="s2"&gt;"INDEX.md is optional"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--new&lt;/span&gt; &lt;span class="s2"&gt;"INDEX.md changes with files"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--evidence&lt;/span&gt; &lt;span class="s2"&gt;"DECISION-42"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open contradictions make &lt;code&gt;doctor&lt;/code&gt; return nonzero. Resolving one appends a new history entry instead of erasing the original claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the scanner will not do
&lt;/h2&gt;

&lt;p&gt;It does not edit your documents. It reports paths and match counts, not file contents. It skips &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.credentials&lt;/code&gt;, &lt;code&gt;.memory&lt;/code&gt;, dependency directories, and build output.&lt;/p&gt;

&lt;p&gt;Unreadable or oversized files block verification. A file the scanner could not read must not become evidence that no old references exist.&lt;/p&gt;

&lt;p&gt;The pack has 14 regression tests covering installation, all nine event types, premise exit codes, contradiction history, explicit scan limits, and false-green prevention. Those tests show that the checks behave as specified. They do not prove a memory-accuracy improvement across other projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repository has the full README, the CLI, example JSON files, and the tests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Dawncoral/wallaby-agent-rules?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-agent-memory-governance" rel="noopener noreferrer"&gt;wallaby-agent-rules on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;doctor&lt;/code&gt;. Then give it one real rename from your project and see whether the first verification passes.&lt;/p&gt;




&lt;p&gt;Wallaby Token builds open-weight inference infrastructure. We use file-based memory and governance on our own projects; the CLI is the part that keeps neighboring documents from being left behind.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>memory</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building a Letta Code custom provider: static first, live second</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Wed, 28 Oct 2026 16:13:07 +0000</pubDate>
      <link>https://dev.to/dawncoral/building-a-letta-code-custom-provider-static-first-live-second-5al</link>
      <guid>https://dev.to/dawncoral/building-a-letta-code-custom-provider-static-first-live-second-5al</guid>
      <description>&lt;p&gt;Fresh sessions in Letta Code failed on their first message with &lt;code&gt;Unknown model "wallaby/kimi-k3"&lt;/code&gt;. Opening &lt;code&gt;/model&lt;/code&gt; once fixed it. That symptom sent us down the wrong path for two releases, and the real cause had been public in the issue tracker for ten days.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Letta Code is the CLI for stateful agents from the Letta project (formerly MemGPT). A provider mod teaches its local runtime about a new model endpoint.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the build log behind &lt;code&gt;wallaby-letta-provider&lt;/code&gt; 0.1.2, a small npm package that puts our gateway inside Letta Code's &lt;code&gt;/connect&lt;/code&gt; flow. It is also the checklist we would hand to anyone shipping a provider mod: three lessons, one wrong turn, and a live catalog that now updates without a release.&lt;/p&gt;

&lt;p&gt;What the package gives a Letta Code user today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kimi-k3&lt;/code&gt; with a 1M context window, image input, and effort levels from &lt;code&gt;off&lt;/code&gt; to &lt;code&gt;max&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A live catalog: a new model id appears in &lt;code&gt;/model&lt;/code&gt; once it is listed and priced, without another npm release&lt;/li&gt;
&lt;li&gt;Costs read from the same public price file the gateway bills against&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a mod instead of a config file
&lt;/h2&gt;

&lt;p&gt;Letta Code's local backend wants provider metadata: a display name, an API style, a base URL, an auth variable, a model list with context limits and prices. A provider mod hands that metadata to the runtime at registration time, so the provider shows up in &lt;code&gt;/connect&lt;/code&gt; and &lt;code&gt;/model&lt;/code&gt; like a built-in one.&lt;/p&gt;

&lt;p&gt;Letta's documented recipe is small. You call &lt;code&gt;letta.providers.register("wallaby", {...})&lt;/code&gt; with &lt;code&gt;api: "openai-completions"&lt;/code&gt;, a base URL, an env var name, and a list of models. Model ids must not contain a slash, which matters later. Provider mods are local-backend only; they do not add providers for agents managed through the Letta API.&lt;/p&gt;

&lt;p&gt;Our first release did the obvious thing and also implemented &lt;code&gt;listModels&lt;/code&gt;, so the catalog would track whatever the gateway serves. That decision produced the bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug we misdiagnosed
&lt;/h2&gt;

&lt;p&gt;Version 0.1.0 shipped with &lt;code&gt;listModels&lt;/code&gt;. A fresh session then failed on its first message with &lt;code&gt;Unknown model "wallaby/kimi-k3"&lt;/code&gt;, until you opened &lt;code&gt;/model&lt;/code&gt; once and picked the model. After that, the session worked.&lt;/p&gt;

&lt;p&gt;We read the symptom as a catalog timing problem: the dynamic catalog is empty at session start, so the first lookup misses. Version 0.1.1 removed &lt;code&gt;listModels&lt;/code&gt; and shipped a static catalog. The symptom disappeared.&lt;/p&gt;

&lt;p&gt;That was a workaround built on a wrong diagnosis. We filed issue #5147, and the project's integration bot answered the next day: dynamic catalogs do start empty in each session, but resolving a turn already refreshes the provider catalog and retries when the model is missing. The error we saw came from a different bug, where a saved model handle for a mod provider could pick up an extra &lt;code&gt;provider/&lt;/code&gt; prefix, producing handles like &lt;code&gt;wallaby/wallaby/kimi-k3&lt;/code&gt;. Reselecting the model rewrote the handle, which is exactly why the &lt;code&gt;/model&lt;/code&gt; re-select worked. The prefix bug was fixed in PR #5079, which closes the public root-cause report #4995, and shipped in v0.34.17.&lt;/p&gt;

&lt;p&gt;So we retested v0.34.17 with &lt;code&gt;listModels&lt;/code&gt; turned back on, using two brand-new agents and one headless prompt each. Both answered on the first turn, no &lt;code&gt;/model&lt;/code&gt; visit needed.&lt;/p&gt;

&lt;p&gt;One edge case survives. Resuming an agent whose model handle was saved before v0.34.17 still fails, because the lookup runs against the &lt;code&gt;openai&lt;/code&gt; provider even though the saved handle is correct; re-selecting the model clears it. We asked whether a migration is planned and have no answer yet. A fix does not always reach saved state.&lt;/p&gt;

&lt;p&gt;The diagnosis was theirs, the fix was theirs, and our static catalog had been hiding a bug that was already gone.&lt;/p&gt;

&lt;p&gt;The published 0.1.2 tarball's header comment still records the original catalog-timing diagnosis. The repository note is corrected; the next release will carry it.&lt;/p&gt;

&lt;p&gt;Two lessons came out of that. Search the issue tracker before shipping a workaround: #4995 had been public since October 8 with the full root cause, ten days before our 0.1.0. And keep the workaround after the fix, because a provider that fails closed is worse than one that carries a static fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 0.1.2 does instead
&lt;/h2&gt;

&lt;p&gt;The current release keeps both paths and orders them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The static catalog registers &lt;code&gt;kimi-k3&lt;/code&gt; immediately, so the first turn of a fresh session always resolves.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;listModels&lt;/code&gt; then merges whatever the gateway's &lt;code&gt;/models&lt;/code&gt; endpoint returns. Ids containing a slash are dropped, because Letta model ids cannot contain one.&lt;/li&gt;
&lt;li&gt;A model without a matching entry in the public &lt;code&gt;pricing.json&lt;/code&gt; is skipped rather than shown at $0. A wrong price is worse than a missing row.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;/models&lt;/code&gt; returns 401 or 403, the mod raises &lt;code&gt;Wallaby rejected this API key (HTTP 401)&lt;/code&gt; when the model list refreshes. A headless run that never refreshes the list can still fall back to the host's default model instead of surfacing it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three metadata fields that decide the experience
&lt;/h2&gt;

&lt;p&gt;Most of a provider's quality is metadata, not the HTTP call. Three fields mattered most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thinking levels.&lt;/strong&gt; The model schema accepts a &lt;code&gt;thinkingLevelMap&lt;/code&gt; that maps each level to a provider-specific value; it comes from pi-ai's &lt;code&gt;Model&lt;/code&gt; type rather than the provider recipe docs. We map &lt;code&gt;off&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;minimal&lt;/code&gt; to &lt;code&gt;minimal&lt;/code&gt;, &lt;code&gt;low&lt;/code&gt; to &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt; to &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt; to &lt;code&gt;high&lt;/code&gt;, and &lt;code&gt;max&lt;/code&gt; to &lt;code&gt;max&lt;/code&gt;, with &lt;code&gt;xhigh&lt;/code&gt; folded into &lt;code&gt;high&lt;/code&gt;. We verified on the endpoint that &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;minimal&lt;/code&gt;, and &lt;code&gt;max&lt;/code&gt; are all accepted and that &lt;code&gt;reasoning_tokens&lt;/code&gt; responds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image input.&lt;/strong&gt; A model declares &lt;code&gt;input: ["text", "image"]&lt;/code&gt;. We checked that the gateway accepts an &lt;code&gt;image_url&lt;/code&gt; data URL before declaring it; a 1x1 PNG came back with the pixel described correctly. Declaring image support without that check is how you get a 400 on the first paste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; The model entry carries per-million input, output, and cached-input prices. We read them from the public price file and keep the static values in sync with it. If the number in the picker disagrees with the bill, users stop trusting the picker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Living with a two-day release cadence
&lt;/h2&gt;

&lt;p&gt;letta-code shipped six tagged releases in ten npm publishes between October 18 and October 28, from v0.34.17 to v0.34.26. That cadence is the real maintenance cost of a mod, more than the code itself.&lt;/p&gt;

&lt;p&gt;Our routine is small: run the unit tests, run one headless turn against a fresh agent on the latest release, and read the provider API surface when a release lands. The mod is one TypeScript file with no dependencies, so a break surfaces in minutes. The npm package had about 490 downloads in its first nine days, which is enough to make a silent break embarrassing.&lt;/p&gt;

&lt;p&gt;If you write a mod, pin your repro to a version, keep a static path that cannot fail, and assume the API will grow. Ours did: &lt;code&gt;listModels&lt;/code&gt; and &lt;code&gt;connect&lt;/code&gt; fields arrived as documented surface, and the model schema already carried a thinking-level map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;letta &lt;span class="nb"&gt;install &lt;/span&gt;npm:wallaby-letta-provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;/reload&lt;/code&gt;, &lt;code&gt;/connect wallaby&lt;/code&gt;, &lt;code&gt;/model&lt;/code&gt;, and pick &lt;code&gt;wallaby/kimi-k3&lt;/code&gt;. Kimi K3 runs $2.70 per million input tokens, $13.50 output, and $0.27 for cached input, 10% below the official list; new accounts start with $0.50 of trial credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The next model we add needs no code change: an entry in &lt;code&gt;/models&lt;/code&gt; and a price in &lt;code&gt;pricing.json&lt;/code&gt;, and the provider picks it up. That is why the live path exists, and why the static path stays.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; Wallaby Token sells API access to Kimi K3, and &lt;code&gt;wallaby-letta-provider&lt;/code&gt; is our package. The Letta issue and the release notes cited here are public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want the install steps rather than the build log, &lt;a href="https://wallabytoken.com/blog/p/kimi-k3-in-letta" rel="noopener noreferrer"&gt;the Letta Code walkthrough&lt;/a&gt; covers them in four commands. Running a different terminal agent? &lt;a href="https://wallabytoken.com/blog/p/kimi-k3-in-pi" rel="noopener noreferrer"&gt;The pi setup&lt;/a&gt; is the same pattern with a different host. To try the endpoint behind the mod, &lt;a href="https://api.wallabytoken.com/register?utm_source=devto&amp;amp;utm_medium=cta&amp;amp;utm_campaign=letta-provider-mod" rel="noopener noreferrer"&gt;wallabytoken.com&lt;/a&gt; gives new accounts $0.50 in trial credit, no card required.&lt;/p&gt;

</description>
      <category>letta</category>
      <category>ai</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The Model Is Not the Bottleneck. The Routing Is.</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Fri, 23 Oct 2026 17:38:13 +0000</pubDate>
      <link>https://dev.to/dawncoral/the-model-is-not-the-bottleneck-the-routing-is-3082</link>
      <guid>https://dev.to/dawncoral/the-model-is-not-the-bottleneck-the-routing-is-3082</guid>
      <description>&lt;p&gt;I still route work by hand: three models, two agent sessions, and a text file of handoffs. A task arrives, I read it, decide whether it needs the fast model or the slow one, write the prompt, and pass it along. That decision is the most valuable thing I do all day, and it is the least systematized.&lt;/p&gt;

&lt;p&gt;The obvious response is to wait for a bigger model. I don't think that is the bottleneck. The bottleneck is the routing decision: which kind of thinking a task deserves, and what evidence would make me change my mind.&lt;/p&gt;

&lt;p&gt;Daniel Kahneman's fast and slow thinking is a useful metaphor here, but it is not a model-size chart. Fast thinking is not a small model. Slow thinking is not a large one. Fast is compiled: a trigger, a rule, a template, a cached playbook. Slow is deliberate: decomposition, multiple candidates, verification, a human checkpoint. A frontier model can be fast and wrong. A mid-tier model can be slow and right if the process around it is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast and slow are paths, not model sizes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Known, reversible, low stakes&lt;/td&gt;
&lt;td&gt;Reflex, rule, template, cache&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Novel, irreversible, ambiguous, high stakes&lt;/td&gt;
&lt;td&gt;Decompose, generate, verify, decide&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The fast path should not call a model
&lt;/h2&gt;

&lt;p&gt;The first surprise is that the fast path should usually not call a model at all. If a task has a known trigger and a known action, the fastest path is a function: when X, do Y, because Z. The model only enters when the trigger is ambiguous. That is why a cheap model with a big memory is still slower and less reliable than a good reflex. You are paying for inference to rediscover something you already knew.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slow is a process
&lt;/h2&gt;

&lt;p&gt;The second surprise is that slow is not "ask the biggest model." Slow is a sequence. Decompose the task. Retrieve the relevant context. Generate independent candidates. Verify them. Decide. A bigger model helps at every step, but it helps less than a tight harness. I have watched a well-scoped refactor go better on a mid-tier coding model with a checklist than on a frontier model with a vague prompt. That says more about the harness than about the frontier model.&lt;/p&gt;

&lt;p&gt;Multiple models are useful, but not as a free-form debate. Independent candidates plus a verifier beat a round table. Debate tends to converge on the most confident voice, which is not the same as the best answer. Use debate for values and ambiguity. Use verification for facts and code.&lt;/p&gt;

&lt;p&gt;Choosing a model is the easy part. The harder problem is deciding when the fast path has stopped being true.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reflex is a bet
&lt;/h2&gt;

&lt;p&gt;A reflex is a bet that the world repeats. Most of the time it does. The job is to notice the times it doesn't. Every reflex needs a life cycle, not just a definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;last_validated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;failure_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A strong reflex can be promoted to a higher layer: personal, team, department, company. Promotion should require evidence across contexts, a low failure rate, and an owner who will answer for it. Repetition alone is not evidence. A reflex that fires a thousand times and is wrong ten percent of the time is not a strong reflex; it is a superstition with a good memory.&lt;/p&gt;

&lt;p&gt;Weak reflexes need a way out. Some should decay. Some should be challenged. Some should be retired. The immune system metaphor is useful: white blood cells scan for contradictions, lymphocytes go after a specific bad reflex. Passive decay alone is not enough, because a stale reflex can sit in the fast path for months, quietly costing you the one case where it no longer applies.&lt;/p&gt;

&lt;p&gt;The same applies to the slow path. A slow path that never becomes a reflex is just a permanent tax. The point of deliberate work is to compile some of it: a decision that was hard once becomes a checklist, then a rule, then a reflex. The system gets faster without getting dumber.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human is the sensor
&lt;/h2&gt;

&lt;p&gt;Then there is the part that is harder to engineer: the human.&lt;/p&gt;

&lt;p&gt;The model has read more than I have. It has not sat in a customer call at 2 a.m., or watched a launch go sideways because one sentence on a pricing page was ambiguous. The gap is experience, not knowledge. The only way to give the system that experience is to record the decisions, not just the outcomes.&lt;/p&gt;

&lt;p&gt;A preference is cheap. A decision trace is expensive, and worth it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;choice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;rationale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;post_use_review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built an architecture assistant around this idea. It reads our system definition, our failure cases, and a set of positive and negative examples. It is good at producing options; judgment is still the human's job. What helped was not more documentation; it was the trace: what I saw, what I considered, why I chose, what happened, and what I would do differently. Positive and negative cases helped, but they were sparse. The trace was denser, and it was the only thing that let the assistant see the shape of a decision rather than the shape of an answer.&lt;/p&gt;

&lt;p&gt;This is also why "human in the loop" is not a checkbox. A thumbs-up is an opinion. A two-week outcome is evidence. If you only collect preferences, you train the system to sound like you. If you collect decisions and outcomes, you train it to think like you in the situations you have actually survived.&lt;/p&gt;

&lt;h2&gt;
  
  
  A portfolio of local optima
&lt;/h2&gt;

&lt;p&gt;The worry is local optima. It is a real worry. A system that learns from its own decisions can get very good at the cases it has seen, and very confident in the wrong places. The answer is a portfolio of local optima, plus a way to retire one when the world moves.&lt;/p&gt;

&lt;p&gt;That means a few unglamorous things: a champion and a challenger, an exploration budget, a red team, a scheduled retirement review. Not a single global optimum. A set of local optima that can be recombined. Success, in a changing environment, is the best set of answers you can still revise, not the single best answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four components
&lt;/h2&gt;

&lt;p&gt;If I were building the routing layer today, I would start with four components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A router.&lt;/strong&gt; Score each task on novelty, ambiguity, stakes, reversibility, and dependency. Route to fast, slow, or escalate. The router should be allowed to say "I don't know."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A reflex registry.&lt;/strong&gt; Every reflex has a trigger, an action, a scope, a confidence, evidence, a last-validated date, a failure count, and an owner. Promotion is a decision, not an accumulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A model portfolio.&lt;/strong&gt; Model cards, not a leaderboard: what each model is good at, what it costs, how much context it needs, how it behaves with tools. Route by fit, not by rank.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A decision trace.&lt;/strong&gt; Options, evidence, choice, rationale, outcome, post-use review. This is the data that teaches the system how you judge, not just what you say.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with one domain. Measure four numbers: reflex hit rate, escalation rate, cost per successful task, and rework rate. If the fast path never escalates, it is not fast; it is reckless. If the slow path never compiles a reflex, it is not learning; it is just expensive.&lt;/p&gt;

&lt;p&gt;The schema is the easy part. The discipline is writing the decision down before you know the outcome.&lt;/p&gt;

&lt;p&gt;The model is the CPU. The routing layer is the operating system. We have spent a lot of energy making the CPU faster. The OS is still a person with a clipboard.&lt;/p&gt;

&lt;p&gt;That is the layer I want to build next.&lt;/p&gt;




&lt;p&gt;Disclosure: I work on &lt;a href="https://wallabytoken.com" rel="noopener noreferrer"&gt;Wallaby Token&lt;/a&gt;, an inference platform for open-weight models. This essay is about the routing layer in my own workflow; it is not a product announcement.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Memory Is Not Written. It's Audited.</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:09:57 +0000</pubDate>
      <link>https://dev.to/dawncoral/memory-is-not-written-its-audited-4oh8</link>
      <guid>https://dev.to/dawncoral/memory-is-not-written-its-audited-4oh8</guid>
      <description>&lt;p&gt;Here is a failure mode nobody screenshots. Your project log says the checkout redesign shipped on Tuesday. Your current-state file still lists it as in flight, waiting on assets. Both files are "your AI's memory." One of them is lying, and nothing in the system is going to tell you which.&lt;/p&gt;

&lt;p&gt;The files are plain text: a dated log of what happened, a current-state file, and a few rule files an agent reads at startup. No database, no service. That is the point, and also the problem.&lt;/p&gt;

&lt;p&gt;We found our own version of this the unglamorous way: not by noticing, but by writing a script that notices. It reads the dated log, reads the state file, and diffs what the one claims against what the other shows. The first run flagged entries we would have sworn were accurate. That is the entire argument of this post: &lt;strong&gt;write discipline always loosens, so memory needs an audit layer, not more discipline.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: Wallaby Token operates an OpenAI-compatible inference API. The memory system described here is open source, MIT licensed, and is the exact setup our own agents run on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two lies memory files tell
&lt;/h2&gt;

&lt;p&gt;Once we started looking, the drift came in exactly two shapes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The contradiction.&lt;/strong&gt; The log records a closure (shipped, fixed, merged, published) and the state file still carries the same topic as open work. Usually nobody updated the state file, because updating it was part of the closeout, and the closeout got skipped on a Friday. The log is append-only and honest; the state file is edited and forgetful. Left alone, the gap compounds until the state file is a museum of things you finished months ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rumor.&lt;/strong&gt; The log claims done, but points at nothing: no file, no ticket, no link. A closure you cannot verify is indistinguishable from one that never happened. This one is subtler, because the entry feels complete when you write it. Six weeks later, "fixed the retry bug" with no pointer is a rumor you told yourself.&lt;/p&gt;

&lt;p&gt;Both are boring. That is what makes them dangerous: there is no dramatic failure, just a slow divergence between what your records claim and what is true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why mechanical, not motivational
&lt;/h2&gt;

&lt;p&gt;The obvious fix is a sterner resolution to keep the files in sync. We tried that. It works for about two weeks. We know, because our audit script is how we found out it had stopped working.&lt;/p&gt;

&lt;p&gt;So the rule we now run is: the human ritual writes, the machine audits. The closeout ritual (from our &lt;a href="https://wallabytoken.com/blog/p/closeout-ritual?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;last post&lt;/a&gt;) is still the entry point; it sorts each session's output into memory, log, state, or the bin. But rituals have bad weeks, so a scheduled script checks the results. And it checks the one thing the ritual cannot check about itself: whether the story the log tells and the story the state file tells are still the same story.&lt;/p&gt;

&lt;p&gt;The script is deliberately dumb. It does keyword matching, not semantic judgment, and it reports candidates rather than verdicts; a human decides which file is stale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CLOSURE_WORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(shipped|ships|done|closed|closes|fixed|fixes|merged|published|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;released|resolved|launched|completed|landed|deployed|closed out)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That regex is the whole semantic layer. No model, no embeddings, no configuration file. Dumb is the feature: a smarter audit would need tuning, and tuning would grow its own maintenance ritual. A dumb one just runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in v3.1
&lt;/h2&gt;

&lt;p&gt;The audit ships today in &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules" rel="noopener noreferrer"&gt;wallaby-agent-rules&lt;/a&gt; as &lt;code&gt;templates/reconcile.py&lt;/code&gt;: zero dependencies, same exit-code contract as the health check (0 clean, 1 findings), two scans:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Closure contradictions&lt;/strong&gt;: log entries that claim something is done while the same topic still sits in the state file's "in flight" section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence-free closures&lt;/strong&gt;: "done" entries with no path, ticket, link, or backtick reference to verify against.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;New installs get it by default: the L0 and L1 prompts in &lt;code&gt;PROMPT.md&lt;/code&gt; now build six files instead of five. Existing installs need exactly one step: copy &lt;code&gt;templates/reconcile.py&lt;/code&gt; into your project as &lt;code&gt;scripts/reconcile.py&lt;/code&gt; and run it weekly next to the health check. The L2 upgrade check detects v3.1 by the file's presence, and like &lt;code&gt;health_check.py&lt;/code&gt;, the script is scaffolding: it holds none of your content, so upgrades can offer to swap it outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limit
&lt;/h2&gt;

&lt;p&gt;A keyword diff will flag some false positives: a closure whose topic overlaps unrelated open work, a pointer written in a format the script doesn't recognize. We accept that trade knowingly. The audit's job is to make drift &lt;em&gt;visible&lt;/em&gt;, not to be right about it. A candidate you dismiss in three seconds costs three seconds. A contradiction nobody surfaces costs you the week you spent trusting a file that had quietly become fiction.&lt;/p&gt;

&lt;p&gt;The harder limit is the one from the last post, unchanged: none of this survives if you never run it. Hang the audit on the same schedule as the health check, and let the machine be the part of the system that never gets tired.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit led to
&lt;/h2&gt;

&lt;p&gt;An audit makes drift visible. It does not stop it, and after enough weekly findings you stop treating them as incidents and start reading them as a design signal. Two files can disagree because nothing forces them to agree at the moment of writing.&lt;/p&gt;

&lt;p&gt;So we moved every memory change behind one entrance. Twelve write surfaces go through a single writer with a diff preview and a test suite of 37 cases. The source of truth is an append-only event stream; the SQLite index is derived, and we rebuilt 1,263 of 1,263 records from it in one command to prove it. Corrections are appended too: when 403 timestamps turned out to be missing seconds, we added correction events instead of editing history.&lt;/p&gt;

&lt;p&gt;The validators run in shadow first. They report for two weeks before they are allowed to reject a write. A check that can block on day one is a check nobody trusts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we run, and what we publish
&lt;/h2&gt;

&lt;p&gt;The reconcile audit is the third layer of the open stack: the file conventions and health check from v2, the closeout ritual from &lt;a href="https://wallabytoken.com/blog/p/closeout-ritual?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;v3&lt;/a&gt;, and now the consistency audit, all in &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules" rel="noopener noreferrer"&gt;wallaby-agent-rules&lt;/a&gt;, MIT licensed, all running daily on our own project. The design principles are in &lt;a href="https://wallabytoken.com/blog/p/ai-remembers-you-find?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;AI remembers, you find&lt;/a&gt;; the maintenance layer is in &lt;a href="https://wallabytoken.com/blog/p/agents-md-maintenance?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;Your AGENTS.md is rotting&lt;/a&gt;. And in 1.0.0 the stack grew a write-side counterpart to this audit: &lt;a href="https://wallabytoken.com/blog/p/trust-building-mode?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;a probation period for new memories&lt;/a&gt;, where the agent proposes and you approve.&lt;/p&gt;

&lt;p&gt;Our internal version of this script checks more (size budgets, decision ledgers, registration drift), because our workspace grew those needs. What we publish is the part every project needs on day one: the diff between what you said you did and what your files say is true. We also outsourced one audit on purpose: &lt;a href="https://wallabytoken.com/blog/p/claude-audited-our-blog?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;we paid Claude to review our own blog&lt;/a&gt; — a second pair of eyes with no stake in our conclusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;New here? The fastest path is the L0 prompt in &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules/blob/main/PROMPT.md" rel="noopener noreferrer"&gt;PROMPT.md&lt;/a&gt;: paste two lines, and the audit ships with the install. Already running the system? Copy &lt;code&gt;templates/reconcile.py&lt;/code&gt; into &lt;code&gt;scripts/&lt;/code&gt; and run it once today. If it comes back clean, congratulations; your log and your state file agree. If it doesn't, you just found out what the audit is for.&lt;/p&gt;

&lt;p&gt;Related: &lt;a href="https://wallabytoken.com/blog/p/rule-file-says-later?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=memory-is-audited" rel="noopener noreferrer"&gt;Your rule file says "later." That's why it never happens.&lt;/a&gt; — three incidents where one deferral word was the whole bug.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why your AI agent's memory says it saved but didn't</title>
      <dc:creator>Dawncoral</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:32:37 +0000</pubDate>
      <link>https://dev.to/dawncoral/why-your-ai-agents-memory-says-it-saved-but-didnt-34d8</link>
      <guid>https://dev.to/dawncoral/why-your-ai-agents-memory-says-it-saved-but-didnt-34d8</guid>
      <description>&lt;p&gt;On October 13, a user of agentmemory, an open-source memory system for coding agents, filed an issue with a code block that should not exist. Two &lt;code&gt;memory_save&lt;/code&gt; calls, two returned IDs, and about six days later, no memories. The engine had hung on October 7. It held its listening sockets open, never accepted a connection, and the client kept confirming saves that never reached disk. Call it the state-assertion bug: a system's report about its own state diverges from the state.&lt;/p&gt;

&lt;p&gt;It is not a retrieval failure. We think it is the most expensive failure class in agent memory, because it corrupts the one asset you need to recover from every other failure, the record of what happened, and it does so one green check at a time.&lt;/p&gt;

&lt;p&gt;This post takes three threads of that class from agentmemory and pairs them with the four escalating fixes we ran on the same class inside our own memory discipline, over two days in October. The short version: a status claim is not evidence. Make the claim name the check that would prove it, make the check read the live system rather than the record of it, and make the wrong thing impossible before you make it visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  A receipt from a till the store never reads
&lt;/h2&gt;

&lt;p&gt;The issue is titled "hung agentmemory was not saving but giving ids," and the body is nearly boring, which is what makes it useful. The reporter ran two saves and pasted the responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"saved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"mem_mv6tghgk_02a5a2f023b8"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"saved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"mem_mv6tghzw_4aba68f9c6f7"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A later session could not find either one. The reporter's diagnosis: the engine had been hung for about six days, holding its listeners but never accepting, so the UI showed zero memories while both saves returned IDs for rows that never touched disk.&lt;/p&gt;

&lt;p&gt;The follow-up pull request explains the mechanism better than the report does. When the health check or a proxied call fails, the MCP shim writes the memory to a local fallback file, &lt;code&gt;~/.agentmemory/standalone.json&lt;/code&gt;, and replies &lt;code&gt;{"saved": id}&lt;/code&gt;. Nothing copies that file to the server. The fallback is announced only on stderr.&lt;/p&gt;

&lt;p&gt;That last sentence is the whole story. The save did happen. The receipt was accurate about the ID and silent about the address, and the address is the only part that matters, because a memory in a file the server never reads is a memory you will never recall.&lt;/p&gt;

&lt;p&gt;The open patch does not prevent the loss. It prevents the lie: fallback saves and deletes would carry &lt;code&gt;storage: "local"&lt;/code&gt; and a warning that the server did not confirm the call. A patch that makes a failure legible is worth more than a patch that makes it rare, because the rare one still happens on a day you are not looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not saving, or saving somewhere else?
&lt;/h2&gt;

&lt;p&gt;That pull request also shows the second form of the same bug: a labeling problem wearing a storage costume. The user's mental state is "it is not saving." The system's state is "it is saving somewhere the server does not read." Both sentences are true, and the interface offers one word for three different outcomes: saved, queued, and gone.&lt;/p&gt;

&lt;p&gt;This shape is everywhere: an offline write queue, a mock returning fixtures, a cache serving stale values. The write path is not wrong. The vocabulary is.&lt;/p&gt;

&lt;p&gt;The test we now apply to any status field is whether it can be false. A field that always reads &lt;code&gt;saved&lt;/code&gt; is decoration, not a status. If the server did not confirm, the response should say the server did not confirm, and it should name the store that actually holds the bytes. The patch here does exactly that, and the interesting part is how small it is. One field and one warning, in the place where the user is already looking.&lt;/p&gt;

&lt;p&gt;Contrast that with the original signal. The fallback was announced on stderr, which is where truthful information goes to be ignored. A warning that requires a human to tail a log during an agent loop is a note in a drawer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;success: true&lt;/code&gt; after the data is gone
&lt;/h2&gt;

&lt;p&gt;On October 16, a second report took the same shape through a different door. &lt;code&gt;POST /agentmemory/import&lt;/code&gt; with &lt;code&gt;strategy: "replace"&lt;/code&gt; deletes every existing scope before it checks the optional sections. Send &lt;code&gt;lessons&lt;/code&gt; as an empty object, and the response is &lt;code&gt;success: true&lt;/code&gt; with every lesson gone. Send it as a string, and the call throws after the deletes. Either way the store is empty by the time anyone finds out.&lt;/p&gt;

&lt;p&gt;The proposed fix validates before deleting. That is the strongest shape in this post, because it does not depend on anyone reading a warning at the right moment. A malformed section is rejected before anything is lost.&lt;/p&gt;

&lt;p&gt;Why do tests miss this? Because each step is correct alone. Replace does delete scopes. The validator does reject malformed sections. The handler does return success. The bug is the order, and order rarely gets its own test until someone has been burned by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this class so expensive?
&lt;/h2&gt;

&lt;p&gt;Retrieval errors announce themselves. You ask for a fact, get the wrong one, and notice. A state-report error waits. You build the next three steps on it, and when something finally breaks, you debug using a record that is already false.&lt;/p&gt;

&lt;p&gt;Then comes the calibration cost. Every time the interface says fine and it isn't, you pay twice: once for the lost write, and once for the habit of not checking. That habit is the expensive part, and nothing in a dashboard measures it.&lt;/p&gt;

&lt;p&gt;The benchmark layer does not cover this. agentmemory publishes 95.2% R@5 on LongMemEval-S from its own run, reproducible from the harness in the repo. That number is real, and it says nothing about whether a write reached the store. Retrieval quality and write truthfulness are different contracts. A system can be excellent at the first and still fail the second on a Tuesday, which is roughly what happened here.&lt;/p&gt;

&lt;p&gt;None of this is an argument against the project. The tracker is public, the fix proposals are public, and the reports are specific enough to reproduce. The interesting fact is that a team this careful about evaluation still shipped the failure. Carelessness is the wrong explanation. The cause is a status message that promises more than the system can know.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four rounds we ran on the same class
&lt;/h2&gt;

&lt;p&gt;We recognized the shape because we have been losing to it for months. Our own memory discipline makes claims about a live project: what shipped, what is still in flight, what was decided. Four times in two days in October, one of those claims was wrong, and each fix was a stronger shape than the last.&lt;/p&gt;

&lt;p&gt;The first fix added a question to our instructions. Before stating a status, ask whether this session actually observed it. Within a day we produced two wrong status sentences, one built on a snapshot that was twenty-five days old and one that called an item pending after an exception had already closed it. A question in a document is a reminder, and reminders are read by the same mind that is about to be wrong.&lt;/p&gt;

&lt;p&gt;A script replaced the question the next day. It reads five sources in a fixed priority order, verdicts first and stale plans last, and prints which source says what. It also added a write-back rule: when an item closes, the plan line gets a pointer. The script was correct. What failed was the installation. We recorded that the check ran before status sentences, and the file that would have told anyone to run it never contained that line. The gate existed in the report about the gate.&lt;/p&gt;

&lt;p&gt;Then came a machine light. A daily scan flags log entries that contain status words without an evidence anchor. That scan found real problems, and it still missed the failure that mattered, because it scanned the log while the failure happened in a sentence about to be spoken in a conversation. The sensor was mounted in the archive while the leak was in the room.&lt;/p&gt;

&lt;p&gt;The fourth fix moved the check into the generator. A live mode reads the draft's front matter, the built output, and the live page before a status claim can be written, and the drafting template has a field for the evidence that cannot be left blank. The class recurred afterward in a sentence rather than a document. We count that as evidence for the ranking below, not against the check, because a check only covers the surface it is mounted on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix shapes, ranked by when they act
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;When it acts&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Make it impossible&lt;/td&gt;
&lt;td&gt;Before the action&lt;/td&gt;
&lt;td&gt;The bad state cannot occur&lt;/td&gt;
&lt;td&gt;Validate before delete (#1576)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wire it to the moment&lt;/td&gt;
&lt;td&gt;At the action&lt;/td&gt;
&lt;td&gt;The reader sees the truth at the moment of choice&lt;/td&gt;
&lt;td&gt;Response says &lt;code&gt;storage: "local"&lt;/code&gt; (#1563)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detect after the fact&lt;/td&gt;
&lt;td&gt;After the action&lt;/td&gt;
&lt;td&gt;You can find it later, if you look&lt;/td&gt;
&lt;td&gt;Daily scan for unevidenced claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remember to remember&lt;/td&gt;
&lt;td&gt;Depends on the moment&lt;/td&gt;
&lt;td&gt;Nothing you can rely on&lt;/td&gt;
&lt;td&gt;Our self-check fixes: 0 for 4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rank the shapes by where the failure can enter, not by how much effort the fix takes. The weakest shape is also the one that feels most like progress, because writing a rule feels like doing something, and the day you write it is usually the day the last failure is still fresh.&lt;/p&gt;

&lt;p&gt;Across our four rounds, the first three rules were all true. The question was right, the script was right, the scan was right. They failed because they lived in a channel the decision maker does not visit at the moment of decision. That is the same defect as a fallback announced on stderr, and it is why we now rank fixes by when they act.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks that hold
&lt;/h2&gt;

&lt;p&gt;Our rule now has two halves. A positive claim names the check that would prove it, and that check reads the live system, not the record of the live system. A negative claim, the kind that says not done, does not exist, or cannot be done, has to answer three questions before it leaves the desk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which source I have not checked is most likely to hold the counterexample?&lt;/li&gt;
&lt;li&gt;What evidence would overturn this conclusion, and did I look for it?&lt;/li&gt;
&lt;li&gt;Which related fact did I already see, and then ignore?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These questions are mechanical on purpose. They are the cheapest substitute we have for a machine that cannot be lied to, and they work best when the answer has to name a command and its output.&lt;/p&gt;

&lt;p&gt;Four things to copy on Monday:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the write back.&lt;/strong&gt; &lt;code&gt;memory_save&lt;/code&gt; returns an ID. Recall that ID in a fresh session and assert the content, not the status code. One extra call buys you the only evidence that counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Label the fallback.&lt;/strong&gt; If any client path can store locally, its response must say so in the result, not in stderr. If the only signal is a log line, you have no signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order destructive operations.&lt;/strong&gt; Validate, then delete, and test the malformed input path, because that is the branch where the store disappears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count your fixes.&lt;/strong&gt; If the same class fails twice, change the shape. On the third failure, move the check into the tool that produces the claim, not into the notes around it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we have not solved
&lt;/h2&gt;

&lt;p&gt;Our failure registry is still event driven and written by a human, which means the judgment stays human and the coverage depends on someone noticing. The live check reads the post source, the build output, and the live URL. It does not read the rest of what we claim about our own work. The patches here are mostly reactive in the same way: the fallback still writes to a file the server never reads, and the open patch makes it say so.&lt;/p&gt;

&lt;p&gt;There is a specification question underneath, and we would like to see it taken seriously. When a memory API cannot confirm that a write reached durable storage, should the response say &lt;code&gt;saved&lt;/code&gt; at all? Accepted, queued, and confirmed are three different promises, and most memory APIs collapse them into one. The first vendor to publish that distinction in its contract will make this entire bug class cheaper to avoid, for everyone, including the systems that already handle it well.&lt;/p&gt;

&lt;p&gt;Until then, the discipline is the same one we apply to our own records: treat every status message as a claim, and check the claim against the live system before you build on it. The memory files we use are open source in &lt;a href="https://github.com/Dawncoral/wallaby-agent-rules" rel="noopener noreferrer"&gt;wallaby-agent-rules&lt;/a&gt;, and the failure registry behind this post is internal, which is the next asymmetry worth closing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rohitg00/agentmemory" rel="noopener noreferrer"&gt;agentmemory&lt;/a&gt;, the project discussed here (Apache-2.0, about 29,700 stars in October 2026)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rohitg00/agentmemory/issues/1552" rel="noopener noreferrer"&gt;Issue #1552: hung agentmemory was not saving but giving ids&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rohitg00/agentmemory/pull/1563" rel="noopener noreferrer"&gt;PR #1563: say when a write only reached the local fallback store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rohitg00/agentmemory/issues/1573" rel="noopener noreferrer"&gt;Issue #1573: replace import deletes the store before it validates&lt;/a&gt; and &lt;a href="https://github.com/rohitg00/agentmemory/pull/1576" rel="noopener noreferrer"&gt;PR #1576&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rohitg00/agentmemory/blob/main/benchmark/LONGMEMEVAL.md" rel="noopener noreferrer"&gt;agentmemory benchmarks&lt;/a&gt;, including the note on which numbers are the project's own run&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wallabytoken.com/blog/p/memory-is-audited?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-memory-write-failures" rel="noopener noreferrer"&gt;Memory Is Not Written. It's Audited.&lt;/a&gt;, the audit layer this post extends&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wallabytoken.com/blog/p/hermes-agent-memory-audit?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-memory-write-failures" rel="noopener noreferrer"&gt;We Ran Our Memory System Inside Hermes&lt;/a&gt;, the same discipline inside another engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wallabytoken.com/blog/p/muse-active-memory-governance?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-memory-write-failures" rel="noopener noreferrer"&gt;Muse Remembers. Who Governs the Memory?&lt;/a&gt;, on memory that lives inside the vendor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclosure: Wallaby Token sells inference for open-weight models, and we keep our own project records in a file-based memory discipline. We have a stake in this layer, so check the receipts and the links.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
