<?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: Ken W Alger</title>
    <description>The latest articles on DEV Community by Ken W Alger (@kenwalger).</description>
    <link>https://dev.to/kenwalger</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%2F15734%2F22d0195e-9fce-4d80-9ae2-3bb416bf8d6f.jpg</url>
      <title>DEV Community: Ken W Alger</title>
      <link>https://dev.to/kenwalger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenwalger"/>
    <language>en</language>
    <item>
      <title>The Contract Discovery Bottleneck</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Thu, 10 Sep 2026 16:44:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/the-contract-discovery-bottleneck-48jb</link>
      <guid>https://dev.to/kenwalger/the-contract-discovery-bottleneck-48jb</guid>
      <description>&lt;p&gt;&lt;em&gt;AI can generate the code. We can verify the behavior. But who decides&lt;br&gt;
what correct means?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://www.kenwalger.com/blog/ai/verification-bottleneck-ai-generated-software/" rel="noopener noreferrer"&gt;wrote recently&lt;/a&gt; about a coding agent that built me a password reset&lt;br&gt;
flow with a reset link that worked more than once.&lt;/p&gt;

&lt;p&gt;The bug survived because nobody had written down that a reset link&lt;br&gt;
should be single use. It was obvious right up until it wasn't.&lt;/p&gt;

&lt;p&gt;My argument was that as AI makes implementation cheaper, verification&lt;br&gt;
becomes the bottleneck. The feature request said "build password reset."&lt;br&gt;
The agent built password reset. The happy path worked. The tests passed.&lt;br&gt;
The implementation looked finished.&lt;/p&gt;

&lt;p&gt;What nobody had asked was whether the same reset link should work twice.&lt;/p&gt;

&lt;p&gt;So I added an independently written behavioral specification. The agent&lt;br&gt;
implemented against it. The verifier rejected the reusable token. The&lt;br&gt;
agent fixed the implementation. The verifier passed it.&lt;/p&gt;

&lt;p&gt;That seemed like a useful pattern:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081104.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081104.png" alt="Flowchart showing three sequential stages: a specification leads to an implementation, which leads to independent verification." width="506" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I published the article, and the comments started finding things my&lt;br&gt;
specification didn't say. That exposed a harder problem.&lt;/p&gt;

&lt;h2&gt;The specification wasn't finished either&lt;/h2&gt;

&lt;p&gt;One reader asked what would happen if two password reset requests using&lt;br&gt;
the same token arrived at the same time.&lt;/p&gt;

&lt;p&gt;I hadn't tested that. My test covered sequential reuse:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081312.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081312.png" alt="Sequence diagram showing a user using a password reset token successfully, the server marking the token consumed, and a second attempt with the same token being rejected." width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But concurrent reuse is different:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081509.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081509.png" alt="Sequence diagram showing two concurrent password reset requests. Request A validates the token and the server reports it unused. Request B validates the same token before A has consumed it, and the server again reports it unused. Both requests then consume the token and both resets succeed, producing two successful resets from a single link." width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both requests check the token while it is still unused. Both proceed.&lt;br&gt;
If validation and consumption are not a single atomic operation, "single&lt;br&gt;
use" can still produce two successful resets.&lt;/p&gt;

&lt;p&gt;The original invariant was incomplete.&lt;/p&gt;

&lt;p&gt;That doesn't make the specification useless. It makes the specification&lt;br&gt;
provisional.&lt;/p&gt;

&lt;p&gt;The interesting part is where the new knowledge goes.&lt;/p&gt;

&lt;p&gt;Once somebody discovers that "single use" also means competing attempts&lt;br&gt;
cannot both succeed, that should stop being knowledge held by the person&lt;br&gt;
who noticed it. It belongs in the durable definition of correct&lt;br&gt;
behavior.&lt;/p&gt;

&lt;p&gt;The specification changes.&lt;/p&gt;

&lt;p&gt;Which means the loop is really closer to this:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081649.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081649.png" alt="Flowchart showing a cycle. Human intent produces a provisional specification, which leads to an implementation, then to independent verification. Verification surfaces a newly discovered invariant, which feeds back into the specification, and the cycle repeats." width="676" height="971"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is messier than the first diagram.&lt;/p&gt;

&lt;p&gt;It is also much closer to engineering.&lt;/p&gt;

&lt;h2&gt;Separate tests can share the same mistake&lt;/h2&gt;

&lt;p&gt;Another reader described an integration builder where an agent wrote&lt;br&gt;
both a connector and the tests for that connector.&lt;/p&gt;

&lt;p&gt;Everything passed.&lt;/p&gt;

&lt;p&gt;Both were wrong.&lt;/p&gt;

&lt;p&gt;The connector and its tests encoded the same incorrect assumption about&lt;br&gt;
OAuth token refresh. The mistake only surfaced when a customer's token&lt;br&gt;
expired during a live session.&lt;/p&gt;

&lt;p&gt;The implementation and test suite were separate artifacts. They were not&lt;br&gt;
independent in the way that mattered.&lt;/p&gt;

&lt;p&gt;They shared an assumption.&lt;/p&gt;

&lt;p&gt;That distinction matters because "independent verification" can sound&lt;br&gt;
like an organizational property:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different file&lt;/li&gt;
&lt;li&gt;different test suite&lt;/li&gt;
&lt;li&gt;different agent&lt;/li&gt;
&lt;li&gt;different step in the pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those necessarily provides independence.&lt;/p&gt;

&lt;p&gt;If the implementation and verifier derive their definition of correct&lt;br&gt;
behavior from the same incomplete prompt, they can agree perfectly and&lt;br&gt;
still be wrong.&lt;/p&gt;

&lt;p&gt;The student is no longer literally grading the same exam.&lt;/p&gt;

&lt;p&gt;Two students have simply studied from the same incorrect answer key.&lt;/p&gt;

&lt;h2&gt;Generating more tests doesn't discover the missing rule&lt;/h2&gt;

&lt;p&gt;Another commenter asked whether property-based testing or giving an&lt;br&gt;
agent an adversarial security persona might do a better job uncovering&lt;br&gt;
these unstated constraints.&lt;/p&gt;

&lt;p&gt;I think both are interesting, but they expose the same boundary.&lt;br&gt;
Property-based testing can explore a stated invariant extremely well.&lt;/p&gt;

&lt;p&gt;If I tell a framework:&lt;/p&gt;

&lt;blockquote&gt;
  A successfully consumed reset token must never produce another
  successful reset.
&lt;/blockquote&gt;

&lt;p&gt;it can generate combinations and sequences I would never think to&lt;br&gt;
hand-author.&lt;/p&gt;

&lt;p&gt;But it cannot tell me that single use was a requirement if nobody&lt;br&gt;
expressed it.&lt;/p&gt;

&lt;p&gt;An adversarial agent has a similar problem. Asking a model to "try to&lt;br&gt;
break this" may produce better tests than asking it to "write tests for&lt;br&gt;
this feature." But if the adversary shares the same context, model&lt;br&gt;
assumptions, and incomplete understanding of the requirement, how&lt;br&gt;
independent is it really?&lt;/p&gt;

&lt;p&gt;The question starts shifting from &lt;em&gt;who writes the tests?&lt;/em&gt; to a more&lt;br&gt;
difficult one: &lt;strong&gt;where does the definition of correct behavior come&lt;br&gt;
from?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;The verifier can be wrong too&lt;/h2&gt;

&lt;p&gt;One of the most interesting examples in the discussion came from a&lt;br&gt;
verification harness rather than generated application code.&lt;/p&gt;

&lt;p&gt;A capability test timed out.&lt;/p&gt;

&lt;p&gt;The harness recorded the result as a failure.&lt;/p&gt;

&lt;p&gt;But a timeout did not establish that the capability failed. It&lt;br&gt;
established that the harness did not obtain a result within the allotted&lt;br&gt;
time.&lt;/p&gt;

&lt;p&gt;Those are different claims.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;NOT TESTED
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;are not interchangeable.&lt;/p&gt;

&lt;p&gt;The verifier had turned an observation failure into an assertion about&lt;br&gt;
capability.&lt;/p&gt;

&lt;p&gt;That's a useful warning for any architecture built around deterministic&lt;br&gt;
verification: deterministic does not mean correct.&lt;/p&gt;

&lt;p&gt;A verifier can enforce the wrong invariant with absolute consistency.&lt;/p&gt;

&lt;p&gt;So can a specification.&lt;/p&gt;

&lt;p&gt;The goal isn't to replace an unreliable agent with an infallible&lt;br&gt;
verifier. There is no infallible verifier.&lt;/p&gt;

&lt;p&gt;The goal is to make the definition of correctness explicit enough that&lt;br&gt;
it can be inspected, challenged, tested, and revised independently of&lt;br&gt;
the implementation.&lt;/p&gt;

&lt;h2&gt;So who writes the contract?&lt;/h2&gt;

&lt;p&gt;This was the question that pushed the argument furthest for me.&lt;/p&gt;

&lt;p&gt;If humans have to write complete behavioral specifications before agents&lt;br&gt;
can implement anything, haven't we simply moved the bottleneck back to&lt;br&gt;
humans?&lt;/p&gt;

&lt;p&gt;Probably.&lt;/p&gt;

&lt;p&gt;And worse, the concurrency example demonstrates that humans don't&lt;br&gt;
necessarily know the complete specification beforehand either.&lt;/p&gt;

&lt;p&gt;So "humans write the contract" isn't much of an answer.&lt;/p&gt;

&lt;p&gt;An agent could propose it.&lt;/p&gt;

&lt;p&gt;That sounds circular at first. If the agent proposes the implementation&lt;br&gt;
and proposes the contract, aren't we back to the student grading the&lt;br&gt;
exam?&lt;/p&gt;

&lt;p&gt;Only if proposing the contract and accepting the contract are the same&lt;br&gt;
operation.&lt;/p&gt;

&lt;p&gt;They don't have to be.&lt;/p&gt;

&lt;p&gt;An agent might generate a candidate operating contract:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;reset token:
  may be used once
  competing attempts cannot both succeed
  expires after N minutes
  cannot authorize a different account
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A human, another system, or some combination can then challenge that&lt;br&gt;
much smaller artifact.&lt;/p&gt;

&lt;p&gt;The question being reviewed becomes:&lt;/p&gt;

&lt;blockquote&gt;
  Is this an adequate definition of correct behavior?
&lt;/blockquote&gt;

&lt;p&gt;rather than:&lt;/p&gt;

&lt;blockquote&gt;
  Is this entire implementation correct?
&lt;/blockquote&gt;

&lt;p&gt;That doesn't solve the trust problem, but it reduces its surface area.&lt;br&gt;
Reviewing four lines is a different activity than reviewing four hundred.&lt;br&gt;
One is a conversation about intent. The other is an audit.&lt;/p&gt;

&lt;p&gt;But this runs straight back into the answer key problem.&lt;/p&gt;

&lt;p&gt;If the same model that will implement the feature also proposes the&lt;br&gt;
contract, they share assumptions. An agent that doesn't know single use&lt;br&gt;
matters won't propose single use as an invariant. It will produce a&lt;br&gt;
confident, well-formatted contract with the same hole in it, and now the&lt;br&gt;
hole has been written down and approved.&lt;/p&gt;

&lt;p&gt;So accepting a contract has to do more than approve it. It has to&lt;br&gt;
introduce something the proposing agent didn't have.&lt;/p&gt;

&lt;p&gt;That might be a person who has debugged this class of bug before. It&lt;br&gt;
might be a genuinely different model, though I'm unsure how much&lt;br&gt;
independence that buys. It might be a checklist derived from past&lt;br&gt;
incidents, which is really institutional memory in a form an agent can&lt;br&gt;
read. For a reset token, somebody's list somewhere already says: single&lt;br&gt;
use, expiry, no account substitution, no concurrent success, session&lt;br&gt;
invalidation.&lt;/p&gt;

&lt;p&gt;The value comes from the independence of the source, not from the&lt;br&gt;
ceremony of the review.&lt;/p&gt;

&lt;p&gt;That may be a more tractable thing to build tooling around than&lt;br&gt;
verification itself.&lt;/p&gt;

&lt;h2&gt;Maybe verification isn't the deepest bottleneck&lt;/h2&gt;

&lt;p&gt;This is where the comments changed my framing.&lt;/p&gt;

&lt;p&gt;I started with:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081827.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-10-081827.png" alt="Flowchart showing the implementation bottleneck leading to the verification bottleneck, with a dashed arrow to a third stage labeled contract discovery, marked as an open question." width="798" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm less sure that's where it stops.&lt;/p&gt;

&lt;p&gt;Once implementation is cheap and verification is increasingly&lt;br&gt;
automatable, the harder problem may become discovering the invariants&lt;br&gt;
worth verifying.&lt;/p&gt;

&lt;p&gt;Call it contract discovery.&lt;/p&gt;

&lt;p&gt;The requirement says:&lt;/p&gt;

&lt;blockquote&gt;
  Reset my password.
&lt;/blockquote&gt;

&lt;p&gt;Somebody has to discover:&lt;/p&gt;

&lt;blockquote&gt;
  The link works once.
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
  Two concurrent attempts cannot both succeed.
&lt;/blockquote&gt;

&lt;p&gt;Then perhaps:&lt;/p&gt;

&lt;blockquote&gt;
  The token cannot authorize a different account.
  
  A token issued before another successful reset may no longer be valid.
  
  A reset invalidates existing sessions.
&lt;/blockquote&gt;

&lt;p&gt;Some of those are product decisions. Some are security properties. Some&lt;br&gt;
are implementation-independent behavioral invariants. Some may not apply&lt;br&gt;
at all.&lt;/p&gt;

&lt;p&gt;The difficult work is deciding which ones belong to the definition of&lt;br&gt;
correct.&lt;/p&gt;

&lt;p&gt;AI can help propose them.&lt;/p&gt;

&lt;p&gt;Property-based testing can explore them.&lt;/p&gt;

&lt;p&gt;Deterministic systems can enforce them.&lt;/p&gt;

&lt;p&gt;Production incidents will unfortunately discover some of them for us.&lt;/p&gt;

&lt;p&gt;But none of those eliminates the need to decide which claims actually&lt;br&gt;
define correctness.&lt;/p&gt;

&lt;h2&gt;This gets harder when agents start acting&lt;/h2&gt;

&lt;p&gt;There is another reason I think this matters beyond generated code:&lt;br&gt;
agents don't just write things anymore. They call things.&lt;/p&gt;

&lt;p&gt;An agent calls an API. The response is &lt;code&gt;200&lt;/code&gt;. The agent moves on.&lt;/p&gt;

&lt;p&gt;But a &lt;code&gt;200&lt;/code&gt; says the request was processed. It doesn't say the&lt;br&gt;
constraint the agent's plan depended on was enforced. Maybe the call&lt;br&gt;
timed out after the write succeeded, so the retry performed the effect&lt;br&gt;
twice. Maybe the operation was legitimate the first time and should have&lt;br&gt;
been rejected the second.&lt;/p&gt;

&lt;p&gt;That second one should look familiar. It's the reset link, one layer&lt;br&gt;
out.&lt;/p&gt;

&lt;p&gt;A bad implementation leaves an artifact somebody can inspect later.&lt;/p&gt;

&lt;p&gt;A bad tool call already happened.&lt;/p&gt;

&lt;p&gt;It sent the email. Charged the card. Revoked the access. Posted the&lt;br&gt;
message.&lt;/p&gt;

&lt;p&gt;There is no diff to read.&lt;/p&gt;

&lt;p&gt;This is where the contract-discovery problem becomes more consequential.&lt;br&gt;
The system needs some definition of what the agent is permitted to cause&lt;br&gt;
and what evidence would establish that the intended effect actually&lt;br&gt;
happened.&lt;/p&gt;

&lt;p&gt;I don't think I have the architecture for that yet, but one boundary is&lt;br&gt;
becoming clearer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The specification can be agent-readable without being agent-owned.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent should be able to see the invariant. Withholding the&lt;br&gt;
requirement only makes the work guesswork.&lt;/p&gt;

&lt;p&gt;But the agent shouldn't be able to quietly redefine the invariant when&lt;br&gt;
satisfying it becomes inconvenient.&lt;/p&gt;

&lt;p&gt;Whatever accepts, stores, and evaluates the contract needs some&lt;br&gt;
independence from the reasoning that produced the implementation or&lt;br&gt;
action.&lt;/p&gt;

&lt;p&gt;Where that boundary belongs remains a harder question.&lt;/p&gt;

&lt;h2&gt;The specification is durable because it can change&lt;/h2&gt;

&lt;p&gt;Calling the specification a durable artifact can sound like calling it&lt;br&gt;
an immutable one.&lt;/p&gt;

&lt;p&gt;I don't mean that.&lt;/p&gt;

&lt;p&gt;A durable specification should change when we learn something about what&lt;br&gt;
correct behavior actually requires.&lt;/p&gt;

&lt;p&gt;What makes it durable is that the knowledge survives the implementation&lt;br&gt;
that taught us the lesson.&lt;/p&gt;

&lt;p&gt;The reset implementation may be rewritten next month.&lt;/p&gt;

&lt;p&gt;The framework may change.&lt;/p&gt;

&lt;p&gt;The agent may change.&lt;/p&gt;

&lt;p&gt;The database may change.&lt;/p&gt;

&lt;p&gt;But once we've established that two competing reset attempts cannot both&lt;br&gt;
succeed, that invariant should survive all of them.&lt;/p&gt;

&lt;p&gt;The same applies to an integration. Once a production failure teaches us&lt;br&gt;
what token refresh must guarantee, that knowledge should not remain&lt;br&gt;
attached to the incident report or the engineer who debugged it.&lt;/p&gt;

&lt;p&gt;It should become part of what "correct connector" means.&lt;/p&gt;

&lt;p&gt;The implementation may be disposable. The accumulated definition of&lt;br&gt;
correctness is not.&lt;/p&gt;

&lt;h2&gt;I still don't think this is solved&lt;/h2&gt;

&lt;p&gt;There are plenty of uncomfortable questions left.&lt;/p&gt;

&lt;p&gt;How independent does a verifier have to be?&lt;/p&gt;

&lt;p&gt;Can two agents using different prompts but the same underlying model&lt;br&gt;
provide meaningful independence?&lt;/p&gt;

&lt;p&gt;Who accepts an agent-proposed contract?&lt;/p&gt;

&lt;p&gt;How do you distinguish a genuine product invariant from an&lt;br&gt;
implementation detail that shouldn't survive the current code?&lt;/p&gt;

&lt;p&gt;What happens when two valid invariants conflict?&lt;/p&gt;

&lt;p&gt;How do contracts evolve without quietly weakening previous guarantees?&lt;/p&gt;

&lt;p&gt;And how do we verify effects in external systems where state is delayed,&lt;br&gt;
partially observable, or distributed?&lt;/p&gt;

&lt;p&gt;I don't have good answers to all of those. That's partly why I don't&lt;br&gt;
think the answer is simply "write better tests." The tests are&lt;br&gt;
downstream of the harder question.&lt;/p&gt;

&lt;h2&gt;Write down what you mean by correct&lt;/h2&gt;

&lt;p&gt;The original password-reset bug happened because a rule existed in&lt;br&gt;
someone's head and nowhere else.&lt;/p&gt;

&lt;p&gt;The comments on that experiment showed the next problem: writing down&lt;br&gt;
one rule doesn't mean you've found all the others.&lt;/p&gt;

&lt;p&gt;That's fine. The specification doesn't have to arrive complete. It has&lt;br&gt;
to provide somewhere for discovered invariants to go, and that somewhere&lt;br&gt;
has to be a place with a history: versioned, reviewable, and attached to&lt;br&gt;
the behavior rather than to the incident that revealed it.&lt;/p&gt;

&lt;p&gt;Maybe an agent proposes them. Maybe a human notices them. Maybe&lt;br&gt;
property-based testing exposes them. Maybe an independent reviewer asks&lt;br&gt;
the annoying question nobody else asked. And sometimes production will&lt;br&gt;
teach us the expensive way.&lt;/p&gt;

&lt;p&gt;The important part is that each discovery makes the durable definition&lt;br&gt;
of correct behavior better.&lt;/p&gt;

&lt;p&gt;AI is making it remarkably cheap to turn an instruction into working&lt;br&gt;
code.&lt;/p&gt;

&lt;p&gt;Verification asks whether the code did what we said. Contract discovery&lt;br&gt;
asks whether we said enough. I'm starting to think that's the harder&lt;br&gt;
problem.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why User Provisioning Matters for Enterprise AI</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Thu, 10 Sep 2026 12:44:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/why-user-provisioning-matters-for-enterprise-ai-534m</link>
      <guid>https://dev.to/kenwalger/why-user-provisioning-matters-for-enterprise-ai-534m</guid>
      <description>&lt;p&gt;&lt;em&gt;Offboarding a user is not enough when the credentials they leave behind can still spend money and invoke tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An orphaned SSO account is inert. Nobody logs in, nobody clicks anything, and eventually someone notices it during an access review.&lt;/p&gt;

&lt;p&gt;An orphaned AI credential is different.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://dev.to/kenwalger/ai-access-control-for-enterprise-ai-turning-policy-into-runtime-enforcement-5bkk"&gt;virtual key&lt;/a&gt; can still have a budget, provider and model access, and perhaps permission to invoke MCP tools against other systems. The person it belonged to may have left six months ago while the credential continues doing exactly what it was authorized to do, with nobody attached to it and nobody wondering why its usage still appears on the bill.&lt;/p&gt;

&lt;p&gt;That is why user provisioning for enterprise AI is not simply another implementation of the joiner, mover, leaver problem IAM teams have managed for decades. The identity lifecycle is familiar. The consequences of getting it wrong are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Should Enterprise AI Provision New Users?
&lt;/h2&gt;

&lt;p&gt;The joiner case is the easy one, which is precisely why it should not get much attention.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/kenwalger/rbac-for-ai-governing-the-ai-control-plane-e59"&gt;previous post&lt;/a&gt; looked at how roles and access profiles connect human authorization to runtime policy. With &lt;a href="https://docs.getbifrost.ai/enterprise/user-provisioning" rel="noopener noreferrer"&gt;Bifrost user provisioning&lt;/a&gt;, an identity-provider group can map a user to a role, the role supplies its default access profile, and that profile issues governed access when the user arrives. Providers, models, budgets, rate limits, and tool access have already been decided. The new employee inherits policy instead of inventing it.&lt;/p&gt;

&lt;p&gt;That is the ideal state: nobody files a ticket for an API key or remembers which model Marketing may use. Organizational intent was encoded before the employee arrived, so provisioning mechanically applies a decision already made.&lt;/p&gt;

&lt;p&gt;Joiners are the flattering part of lifecycle management. Movers are where exceptions accumulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Are Role Changes Dangerous for Enterprise AI Access?
&lt;/h2&gt;

&lt;p&gt;Imagine &lt;a href="https://dev.to/kenwalger/ai-governance-for-enterprise-ai-why-governance-comes-before-the-gateway-2hm1"&gt;the acquisition from the first post&lt;/a&gt; six months later. One engineer initially joins the migration team, later moves to the platform group, and eventually transfers into an internal agent project.&lt;/p&gt;

&lt;p&gt;Each move is legitimate. Each requires new access. The dangerous question is what happened to the old access.&lt;/p&gt;

&lt;p&gt;Role-based provisioning can replace assignments that originated from a role default. But a profile assigned directly to a user represents an explicit exception, so it survives the role change. The system can distinguish a role-derived assignment from a direct assignment; it cannot determine whether the reason behind that exception still exists.&lt;/p&gt;

&lt;p&gt;Combine that with the &lt;a href="https://dev.to/kenwalger/rbac-for-ai-governing-the-ai-control-plane-e59"&gt;role behavior from the previous post&lt;/a&gt;: when someone holds multiple roles, permissions resolve upward rather than to their intersection. The result is a familiar enterprise failure with an AI-specific blast radius. People gain access at every transition and shed it at none.&lt;/p&gt;

&lt;p&gt;Three teams into someone's tenure, their effective authorization may resemble the union of all three jobs they have held. That can mean access to models approved for a previous team, a larger budget, different logs, or tools their current role has no reason to invoke.&lt;/p&gt;

&lt;p&gt;Movers therefore need more than synchronization. They need reconciliation. Bifrost's &lt;a href="https://docs.getbifrost.ai/enterprise/user-provisioning" rel="noopener noreferrer"&gt;user provisioning model&lt;/a&gt; reflects role and team changes when users are reconciled against the identity provider, but lifecycle management still has to account for exceptions outside those inherited defaults. Automation is good at applying known rules and poor at deciding whether an exception created fourteen months ago still has a business justification.&lt;/p&gt;

&lt;p&gt;The question is not simply, "What access should this person's new role add?" It is also, "What access no longer has a reason to exist?" Who owns this key, why does this direct assignment still stand, and would the organization grant the same access today if asked fresh? Those are governance questions rather than provisioning mechanics, and no synchronization job will answer them.&lt;/p&gt;

&lt;p&gt;In practice, reconciliation is a scheduled comparison rather than an event handler. Once a quarter, list every access profile assignment that did not come from a role default, along with who created it, when, and against which stated justification. Anything without a justification on record is the finding. Anything with one is a question for the person who owns that team, and the question is not whether the access is being used but whether it would be granted again today.&lt;/p&gt;

&lt;p&gt;That distinction matters because usage is a misleading signal here. An unused grant is not evidence of safety; it is a capability sitting idle until an incident or an automation reaches for it. A budget that has never been spent still authorizes the spending.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Should Happen to AI Access When a User Leaves?
&lt;/h2&gt;

&lt;p&gt;Leavers expose the difference between identity management and AI lifecycle management most clearly.&lt;/p&gt;

&lt;p&gt;The obvious response to a departing employee is deletion: remove the account, revoke credentials, clean up the objects, and leave the system tidy. &lt;a href="https://dev.to/kenwalger/ai-access-control-for-enterprise-ai-turning-policy-into-runtime-enforcement-5bkk"&gt;The second post in this series&lt;/a&gt; discussed why an expired virtual key should instead fail closed without disappearing. During offboarding, that distinction becomes critical.&lt;/p&gt;

&lt;p&gt;By the time someone leaves, the system may hold an identity, roles, access-profile assignments, a virtual key, budget counters, model permissions, rate limits, tool grants, and a record of requests made under that authority. Some must stop working immediately. Some must survive.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Identity&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Access profile&lt;/th&gt;
&lt;th&gt;Virtual key&lt;/th&gt;
&lt;th&gt;MCP credentials&lt;/th&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Joins&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Created on first login&lt;/td&gt;
&lt;td&gt;From IdP group&lt;/td&gt;
&lt;td&gt;Role default applies&lt;/td&gt;
&lt;td&gt;Issued automatically&lt;/td&gt;
&lt;td&gt;Authorized per user&lt;/td&gt;
&lt;td&gt;Created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Moves&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unchanged&lt;/td&gt;
&lt;td&gt;Replaced&lt;/td&gt;
&lt;td&gt;Role defaults swap; direct assignments persist&lt;/td&gt;
&lt;td&gt;Reissued&lt;/td&gt;
&lt;td&gt;Persist separately&lt;/td&gt;
&lt;td&gt;Retained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leaves&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deactivated&lt;/td&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;Copy retained&lt;/td&gt;
&lt;td&gt;Fails closed&lt;/td&gt;
&lt;td&gt;Revoked separately&lt;/td&gt;
&lt;td&gt;Preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the architecture: &lt;strong&gt;fails closed, record preserved.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose Security investigates an event six months later. The useful questions are historical: which models could this person reach, what budget applied, did they have MCP tool access, and was that access inherited or directly assigned?&lt;/p&gt;

&lt;p&gt;Deleting lifecycle objects because the employee left makes those questions harder precisely when their answers matter most. This is a &lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html" rel="noopener noreferrer"&gt;write-side custody&lt;/a&gt; problem as much as an identity problem. The system should preserve enough state to explain what happened rather than reconstructing it later from an IdP, billing export, deleted credential, and today's configuration.&lt;/p&gt;

&lt;p&gt;Offboarding therefore has two obligations that pull against each other: end the authority, and preserve the evidence. A leaver should become incapable of causing new actions without becoming invisible to the historical record.&lt;/p&gt;

&lt;p&gt;That distinction matters most with tools, and there is a second object hiding here. Where an MCP server uses per-user OAuth, the departing employee holds credentials that are separate from their virtual key and have to be revoked separately. Bifrost exposes a sessions view for inspecting and revoking those per-user MCP credentials, which is easy to miss in an offboarding checklist designed before agentic AI entered the enterprise stack.&lt;/p&gt;

&lt;p&gt;It is worth being precise about why that is three operations rather than one, because they fail independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deactivating the identity&lt;/strong&gt; stops someone logging in. It does not reach anything already issued under that identity. An offboarding process that ends here has closed the front door and left every key that was cut from it in circulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalidating the virtual key&lt;/strong&gt; stops the requests. This is the operation most teams think of as revocation, and it is the one that actually severs runtime authority. It should fail closed while remaining inspectable, for the reasons above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revoking per-user MCP credentials&lt;/strong&gt; is the one most likely to be missed, because the credential is not entirely yours. Per-user OAuth means the employee authorized an external service directly, and the resulting grant lives at that service. Invalidating their virtual key stops requests flowing through the gateway. It does not, on its own, tell the external provider that the authorization behind those requests should end. That grant has to be revoked where it lives, which is why a sessions view exists as a separate surface rather than as a checkbox on the user record.&lt;/p&gt;

&lt;p&gt;The practical consequence is that "we deactivated their account" answers only the first of three questions, and the third one is the one nobody thinks to ask.&lt;/p&gt;

&lt;p&gt;The exposure is conditional rather than automatic, and the condition is the interesting part. By default Bifrost does not execute tool calls on its own; a model returns suggestions and the application has to call the execution endpoint explicitly. But a workload running in &lt;a href="https://docs.getbifrost.ai/mcp/overview" rel="noopener noreferrer"&gt;agent mode&lt;/a&gt; with auto-approval configured has no such pause. A forgotten SaaS login is dormant until somebody uses it. An orphaned key attached to an autonomous workload is already being used, and the human whose authority justified those tool grants left two quarters ago.&lt;/p&gt;

&lt;p&gt;An orphaned SSO account is inert.&lt;/p&gt;

&lt;p&gt;An orphaned virtual key is an actor.&lt;/p&gt;

&lt;p&gt;And there is a question sitting underneath all of this that the series has not answered. The record survives the person, but six months later, what does that record actually prove, and who should be allowed to read it? Removing access is only half of offboarding. The other half is being able to explain what happened while that access still existed.&lt;/p&gt;

&lt;p&gt;One practical distinction before designing around any of this. &lt;a href="https://getmax.im/githubdevto" rel="noopener noreferrer"&gt;Bifrost itself is open source&lt;/a&gt;, including the gateway and the core governance around virtual keys, budgets, routing, and MCP tool filtering. The user provisioning and identity lifecycle discussed here are Enterprise capabilities, and the &lt;a href="https://docs.getbifrost.ai/overview" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; is the better place to check that boundary than a blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens to AI Credentials That Never Had a User?
&lt;/h2&gt;

&lt;p&gt;There is one uncomfortable limit to this model: lifecycle management assumes a lifecycle.&lt;/p&gt;

&lt;p&gt;None of it helps with an identity the system never knew about. A contractor handed a key directly. A shared service account nobody quite owns. A credential minted for a pipeline in 2024 by someone who has since left. These may be the most consequential actors in the environment, because they run continuously, spend consistently, and often hold broad permissions on the theory that automation is inconvenient when it fails. Unlike a person, they never transfer teams and never trigger an offboarding workflow. You cannot deprovision an identity that never existed.&lt;/p&gt;

&lt;p&gt;The only workable answer is to give them the lifecycle events they will never generate on their own. Every non-human credential needs a named human owner, not a team alias, and a review date on the calendar. The owner is who gets asked when the credential appears in an incident. The review date is the substitute for the HR event that will never arrive. Expiry helps here more than it does for people: a workload credential that has to be renewed annually forces someone to state, once a year, that the thing still needs to exist. Most of the credentials that worry security teams would not survive that question.&lt;/p&gt;

&lt;p&gt;That leaves enterprise AI with a harder question than joiners, movers, and leavers: how do you govern actors that were never people in the first place? An agent does not join a team, does not sit in a reorganization, and does not have a last day. It has an owner who may or may not remember creating it, a budget nobody reviews, and a set of tool grants that made sense on the afternoon they were issued.&lt;/p&gt;

&lt;p&gt;That is where the lifecycle problem stops being about users and starts being about workloads.&lt;/p&gt;




&lt;p&gt;This article was commissioned by the &lt;a href="https://www.getmaxim.ai/bifrost" rel="sponsored nofollow noopener noreferrer"&gt;Bifrost team&lt;/a&gt;. The link to the &lt;a href="https://sovereignplatform.dev?utm_source=devto&amp;amp;utm_medium=footer&amp;amp;utm_campaign=ai-control-plane" rel="noopener noreferrer"&gt;Sovereign Systems Specification&lt;/a&gt; points to my own work. The architectural perspective and conclusions expressed here are my own.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Verification Bottleneck in AI-Generated Software</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:32:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/the-verification-bottleneck-in-ai-generated-software-3p7l</link>
      <guid>https://dev.to/kenwalger/the-verification-bottleneck-in-ai-generated-software-3p7l</guid>
      <description>&lt;p&gt;&lt;em&gt;AI can generate code faster than ever. That doesn't mean we're shipping correct software faster.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, I asked a coding agent to build a password reset flow. It produced the route, the token handling, the email integration, and the UI in about five minutes.&lt;/p&gt;

&lt;p&gt;The implementation had a bug. The reset link worked more than once. Use it to set a new password, then open the same link again, and it still worked.&lt;/p&gt;

&lt;p&gt;The feature request hadn't explicitly said that a reset link should be single use. It wasn't in the feature request, because it's the kind of thing that goes without saying right up until the moment it doesn't. Every path a person would click through by hand worked perfectly. A code review might have caught it. A demo would not have.&lt;/p&gt;

&lt;p&gt;That gap is what I want to talk about.&lt;/p&gt;

&lt;h2&gt;Code generation is no longer the expensive part&lt;/h2&gt;

&lt;p&gt;Traditional software development has an obvious constraint: humans have to write the software. We think about the requirement, design the implementation, write the code, run it, discover that it doesn't work, debug it, and repeat until we're sufficiently convinced.&lt;/p&gt;

&lt;p&gt;AI coding agents compress parts of that loop dramatically. The agent can often produce an implementation faster than I can thoroughly review what it generated, which creates an inversion. For a long time, writing code was expensive and checking it was comparatively cheap. What happens when writing becomes cheap?&lt;/p&gt;

&lt;p&gt;Verification becomes proportionally more valuable.&lt;/p&gt;

&lt;p&gt;Suppose an agent implements a feature in five minutes, but determining whether the implementation is correct requires another 45 minutes of manual testing and code review. We haven't created a five-minute development process. We've created a 50-minute development process with a very fast implementation stage.&lt;/p&gt;

&lt;p&gt;And the verification half is harder than it used to be, because you're now auditing code you didn't write.&lt;/p&gt;

&lt;p&gt;Generating more code doesn't solve that. We need a better feedback loop.&lt;/p&gt;

&lt;h2&gt;The specification is becoming the durable artifact&lt;/h2&gt;

&lt;p&gt;Here's the shift I think matters most, and it goes well beyond testing.&lt;/p&gt;

&lt;p&gt;If an agent can rewrite a component cheaply, we become less attached to any particular implementation. Implementations are becoming disposable. Six months from now the routes change, the framework changes, the DOM changes, the internal architecture changes.&lt;/p&gt;

&lt;p&gt;The user requirement doesn't:&lt;/p&gt;

&lt;blockquote&gt;
  A user who resets their password must subsequently be able to authenticate with the new password, must no longer be able to authenticate with the old one, and must not be able to reuse the reset link.
&lt;/blockquote&gt;

&lt;p&gt;In an era of inexpensive code generation, the thing worth maintaining is increasingly the specification rather than the implementation. Which means it's worth writing the specification first, deliberately, as an artifact in its own right rather than as documentation of something already built.&lt;/p&gt;

&lt;h2&gt;Start with behavior, not implementation&lt;/h2&gt;

&lt;p&gt;I tested this with a &lt;a href="https://github.com/kenwalger/spec-first-verification" rel="noopener noreferrer"&gt;small web application&lt;/a&gt;, &lt;a href="https://claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, and &lt;a href="https://testrigor.com/" rel="noopener noreferrer"&gt;testRigor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The application was deliberately ordinary: authentication, user accounts, login behavior, the kind of functionality in countless business applications. I wasn't interested in whether an agent could produce something visually impressive. I wanted to answer a different question. Can we define expected behavior first, let an agent implement it, and use independent end to end verification to determine whether the agent actually succeeded?&lt;/p&gt;

&lt;p&gt;Consider the password reset feature. The implementation involves a route, form handling, token generation, validation, password hashing, database updates, session behavior, error handling, and UI changes. An agent generates all of it quickly.&lt;/p&gt;

&lt;p&gt;None of those implementation details are what the user cares about. The user cares whether they can request a reset, receive the email, set a new password, log in with it, and whether the old password and the used link both stop working.&lt;/p&gt;

&lt;p&gt;Those are observable outcomes. That makes them a useful boundary between what we asked the agent to build and what the agent actually built.&lt;/p&gt;

&lt;h2&gt;Executable specifications&lt;/h2&gt;

&lt;p&gt;This is where testRigor became interesting to me.&lt;/p&gt;

&lt;p&gt;testRigor expresses tests as English-like behavioral instructions rather than requiring the test author to work primarily in selectors and automation code. A test describes what a user does and what they expect to see.&lt;/p&gt;

&lt;p&gt;That creates an opportunity when combined with AI-assisted development, because the behavioral test can function as an executable specification.&lt;/p&gt;

&lt;p&gt;Instead of telling an agent to "add password reset," we provide a behavioral contract. The application must email a reset link. The link must lead to a form. The new password must work afterward. The link must not work a second time.&lt;/p&gt;

&lt;p&gt;Now there are two artifacts with two responsibilities. The coding agent is responsible for figuring out how to implement the behavior. The behavioral test is responsible for determining whether the behavior exists.&lt;/p&gt;

&lt;p&gt;That separation is the point.&lt;/p&gt;

&lt;h2&gt;Don't let the student grade the exam&lt;/h2&gt;

&lt;p&gt;AI coding tools are increasingly capable of generating their own tests, and that's useful. I use it. But there's a problem when the same system interprets the requirement, creates the implementation, creates the test for that implementation, and then announces that everything passes.&lt;/p&gt;

&lt;p&gt;The system can make the same mistaken assumption in multiple places. If the agent misunderstands the requirement, it produces code consistent with that misunderstanding and tests that validate the same misunderstanding.&lt;/p&gt;

&lt;p&gt;Everything is green, and everything is also wrong.&lt;/p&gt;

&lt;p&gt;This is exactly what happened with the reset link. An agent asked to write its own tests for the feature it just built would have tested the happy path, because the happy path is what it understood the requirement to be. It would have passed.&lt;/p&gt;

&lt;p&gt;The single use check existed only because a specification written before the code asked a question the feature request never raised. That's the whole value: not that the test is in English, but that it was written by someone thinking about the requirement rather than about the implementation.&lt;/p&gt;

&lt;p&gt;Independent behavioral verification asks a different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regardless of how you implemented this, does the software exhibit the behavior we specified?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's closer to the question a user actually cares about.&lt;/p&gt;

&lt;h2&gt;Closing the loop&lt;/h2&gt;

&lt;p&gt;The part of this that interests me most isn't the testing tool. It's the feedback loop.&lt;/p&gt;

&lt;p&gt;Once an end to end test produces a deterministic pass or failure, that result becomes input to the coding agent. The agent implements the feature, the behavioral test runs, and if the test fails, the failure returns to the agent. The agent examines the implementation, makes another change, and verification runs again.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-08-140833.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-08-140833.png" alt="Flowchart showing an AI-assisted software development loop. A behavioral contract is passed to an AI coding agent, which produces an implementation. Behavioral end-to-end verification checks whether expected behavior is present. Passing behavior becomes verified behavior, while failure evidence is returned to the coding agent for another implementation attempt.&lt;br&gt;
" width="800" height="1373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case the full cycle ran three times. Red against the baseline, because the feature didn't exist yet. Red again after generation, on the reused link. Green after the failure text went back to the agent as context. About twenty minutes of wall clock end to end, most of it unattended.&lt;/p&gt;

&lt;p&gt;The failure text mattered more than I expected. It wasn't a stack trace. It was a step in the behavior, in plain English, that didn't happen. That's readable by a person and readable by an agent, which is what let the loop close without me translating between them.&lt;/p&gt;

&lt;p&gt;The agent now has something more useful than "try again." It has evidence that a specific expected behavior was not observed.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/QCAUyFKlhhQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/QCAUyFKlhhQ" rel="noopener noreferrer"&gt;Spec-First Verification for AI-Generated Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The video is rough around the edges, which is probably appropriate for an experiment. The interesting part isn't production quality. It's watching the loop operate.&lt;/p&gt;

&lt;h2&gt;Generation and authority are different jobs&lt;/h2&gt;

&lt;p&gt;This reinforced something I've been thinking about more broadly in AI system design. Probabilistic systems are extraordinarily useful for proposing things: generate this implementation, interpret this requirement, suggest a fix, explain this failure, determine which files probably need to change.&lt;/p&gt;

&lt;p&gt;But there are places where I want something else to have authority. Did the build succeed? Did the API return the expected response? Does the database contain the expected state? Can the user complete the specified workflow? Did the test pass?&lt;/p&gt;

&lt;p&gt;Those questions can be answered deterministically, which gives a useful architectural separation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI proposes. Deterministic systems verify.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't eliminate mistakes. A badly written test verifies the wrong thing. An incomplete specification leaves important behavior uncovered. A test environment can differ from production. Verification itself needs engineering.&lt;/p&gt;

&lt;p&gt;But separating generation from verification means we stop treating the model's confidence as evidence that the implementation is correct.&lt;/p&gt;

&lt;h2&gt;Behavioral tests aren't magic&lt;/h2&gt;

&lt;p&gt;Working with an English-like testing system reinforced another lesson: plain English does not mean no learning required.&lt;/p&gt;

&lt;p&gt;Tools still have semantics. You need to understand how the system identifies elements, interprets instructions, manages state, handles authentication, and responds when the application doesn't behave as expected. There were several moments where I had to learn testRigor's particular vocabulary before a seemingly obvious instruction did what I expected. My spec assumed the application would land on a login form after a successful reset. It didn't. That's a gap in my specification, not a bug in the application, and finding it took a run I hadn't budgeted for.&lt;/p&gt;

&lt;p&gt;That isn't a flaw unique to testRigor. Abstractions don't eliminate complexity. They move it.&lt;/p&gt;

&lt;p&gt;SQL didn't eliminate the need to understand databases. High level languages didn't eliminate the need to understand software. Natural language testing doesn't eliminate the need to understand testing.&lt;/p&gt;

&lt;p&gt;What changes is who can express the behavior, and how tightly that behavior is coupled to implementation details. That part is genuinely interesting.&lt;/p&gt;

&lt;h2&gt;What we should be optimizing&lt;/h2&gt;

&lt;p&gt;Much of the excitement around AI-assisted development has focused on productivity. How much faster can developers write code? How many tasks can an agent complete? How many tokens did it use?&lt;/p&gt;

&lt;p&gt;Those metrics aren't useless, but they aren't the outcome. Software exists to behave correctly enough to solve a problem.&lt;/p&gt;

&lt;p&gt;If an agent generates 10,000 lines in ten minutes and we spend the rest of the day figuring out whether any of it works, those lines aren't evidence of productivity. They're inventory awaiting inspection.&lt;/p&gt;

&lt;p&gt;The metric worth tracking is correct functionality delivered per unit of time. That includes generation, but it also includes verification. As generation approaches zero marginal effort, the second half is where the remaining cost lives.&lt;/p&gt;

&lt;p&gt;Coding agents are going to get better. They'll generate larger changes, operate longer without supervision, understand more complex repositories, and handle more of the implementation process autonomously. That makes verification more important, not less. Greater autonomy without independent evaluation doesn't produce a better development system. It produces a faster source of unverified output.&lt;/p&gt;

&lt;p&gt;The opportunity is to build verification into the architecture rather than bolting it on afterward. The specification defines the destination. The coding agent proposes a route. Verification tells us whether we actually arrived.&lt;/p&gt;

&lt;p&gt;Not generating code faster. Shipping working software faster.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Context Hydration: When Memory Becomes Voice</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Tue, 08 Sep 2026 14:41:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/context-hydration-when-memory-becomes-voice-3b77</link>
      <guid>https://dev.to/kenwalger/context-hydration-when-memory-becomes-voice-3b77</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 7 of the Building the AI Memory Stack series&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Back in Part 1, I made a promise: memory eventually becomes voice.&lt;/p&gt;

&lt;p&gt;Up to this point, we've built the architecture that makes trustworthy memory possible.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Context Window&lt;/strong&gt; executes work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/active-working-memory.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Active Working Memory&lt;/a&gt;&lt;/strong&gt; assembles the state required for that work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/durable-memory.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Durable Memory&lt;/a&gt;&lt;/strong&gt; preserves knowledge worth keeping.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt;&lt;/strong&gt; explains how decisions were made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt;&lt;/strong&gt; ensures only trustworthy information becomes institutional memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/forensic-receipt.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Forensic Receipts&lt;/a&gt;&lt;/strong&gt; make that memory provable.&lt;/p&gt;

&lt;p&gt;All of that effort leads to one inevitable question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;How does cold, trustworthy memory become useful reasoning again?&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/context-hydration.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Context Hydration&lt;/a&gt;&lt;/strong&gt;.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-092954.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-092954.png" alt="Flowchart showing the AI Memory Stack flow from Forensic Receipt down to Model Inference, highlighting the Context Hydration transition between Durable Memory and Active Working Memory." width="487" height="1435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that Context Hydration is not another layer. It is the transition that lifts stored memory back into active reasoning, the arrow rather than the box.&lt;/p&gt;

&lt;h2&gt;Memory That Never Returns Isn't Very Useful&lt;/h2&gt;

&lt;p&gt;Imagine an engineering handbook containing thousands of Architecture Decision Records. Every decision has provenance, every revision has history, and every document was validated before it entered long-term storage. It's a remarkable archive.&lt;/p&gt;

&lt;p&gt;Until an AI agent needs to answer a question.&lt;/p&gt;

&lt;p&gt;At that moment, none of that durable knowledge matters until some of it is restored into working memory. Stored knowledge is inert. Reasoning requires living context.&lt;/p&gt;

&lt;h2&gt;Hydration Is More Than Retrieval&lt;/h2&gt;

&lt;p&gt;Traditional retrieval systems ask one question:&lt;/p&gt;

&lt;blockquote&gt;
  "Which documents are relevant?"
&lt;/blockquote&gt;

&lt;p&gt;Context Hydration asks a different one:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;"Which verified knowledge deserves to consume tokens for this task?"&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those questions sound similar, but architecturally they are very different.&lt;/p&gt;

&lt;p&gt;Retrieval finds. Hydration restores.&lt;/p&gt;

&lt;h2&gt;The Hydration Boundary&lt;/h2&gt;

&lt;p&gt;The Sovereign Systems Specification calls this transition the &lt;strong&gt;Hydration Boundary&lt;/strong&gt;.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-093048-scaled.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-093048-scaled.png" alt="Flowchart mapping the five sequential steps across the Hydration Boundary, from Durable Memory to the Context Window." width="800" height="40"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before information crosses that boundary, the system asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has this information been verified?&lt;/li&gt;
&lt;li&gt;Is it still authoritative?&lt;/li&gt;
&lt;li&gt;Does this task actually require it?&lt;/li&gt;
&lt;li&gt;Is there a cheaper representation?&lt;/li&gt;
&lt;li&gt;What is the token cost of restoring it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hydration is not a bulk export. It is deliberate reconstruction.&lt;/p&gt;

&lt;h2&gt;Verification Before Expansion&lt;/h2&gt;

&lt;p&gt;One subtle architectural decision matters enormously: the system should verify memory &lt;strong&gt;before&lt;/strong&gt; expanding it into prompts. Verification is cheap; context windows are expensive. Hydrating untrusted information wastes both compute and attention.&lt;/p&gt;

&lt;p&gt;The cheapest token is the one you never have to generate.&lt;/p&gt;

&lt;h2&gt;Hydrate Only What the Task Needs&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions in agent design is that bigger context automatically produces better answers. Usually it produces more distraction.&lt;/p&gt;

&lt;p&gt;Every additional document competes for the model's attention.&lt;/p&gt;

&lt;p&gt;Every unnecessary paragraph increases the &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/context-tax.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Context Tax&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every observation collected "just in case" contributes to the &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/observer-tax.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Observer's Tax&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Good hydration isn't about restoring everything. It's about restoring enough.&lt;/p&gt;

&lt;h2&gt;Latency Is Part of the Architecture&lt;/h2&gt;

&lt;p&gt;Hydration has a cost. Verification, retrieval, expansion, and serialization all take time, and every layer adds latency.&lt;/p&gt;

&lt;p&gt;That doesn't make hydration a bad idea. It makes it an architectural tradeoff rather than an implementation detail.&lt;/p&gt;

&lt;p&gt;The question isn't "Can we hydrate this?" The better question is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;"Is this memory worth paying to restore?"&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;h2&gt;When Memory Becomes Voice&lt;/h2&gt;

&lt;p&gt;This is the promise we began with. Memory has no value sitting on disk. It becomes valuable only when trusted knowledge crosses the Hydration Boundary and becomes reasoning once again.&lt;/p&gt;

&lt;p&gt;That is the moment memory becomes voice.&lt;/p&gt;

&lt;h2&gt;Looking Ahead&lt;/h2&gt;

&lt;p&gt;We've now assembled the complete AI Memory Stack. The final two articles zoom out.&lt;/p&gt;

&lt;p&gt;The next explores the hidden economic costs of prompt-centric architectures: the &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/prose-tax.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Prose Tax&lt;/a&gt;&lt;/strong&gt;, the &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/retrieval-tax.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_7_context_hydration" rel="noopener noreferrer"&gt;Retrieval Tax&lt;/a&gt;&lt;/strong&gt;, and the broader fiscal architecture of modern AI systems.&lt;/p&gt;

&lt;p&gt;Because building trustworthy memory is only half the challenge. Operating it efficiently is the other half.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Can Rust Make Unsafe AI Agent Actions Unrepresentable?</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Sun, 06 Sep 2026 11:29:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/can-rust-make-unsafe-ai-agent-actions-unrepresentable-3ea</link>
      <guid>https://dev.to/kenwalger/can-rust-make-unsafe-ai-agent-actions-unrepresentable-3ea</guid>
      <description>&lt;p&gt;&lt;em&gt;I went looking for a better runtime check and found a different way to think about the problem.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have spent a lot of time recently thinking about what an AI agent should be allowed to do.&lt;/p&gt;

&lt;p&gt;Reading data is one thing. Writing durable state is another. Sending an email, approving a refund, changing a production configuration, or deleting a record moves farther along the same spectrum.&lt;/p&gt;

&lt;p&gt;The usual answer is to put a guardrail in front of the dangerous operation.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134241.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134241.png" alt="Flowchart of a conventional runtime guardrail. An agent proposes an action, which flows through validate action, then check policy, then an allow-or-deny decision. Allow leads to execute; deny leads to stop. The dangerous execute step sits at the end of a chain of checks that every caller must remember to run.&lt;br&gt;
" width="549" height="1216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That makes sense, and I have built examples that work exactly this way.&lt;/p&gt;

&lt;p&gt;Then I started looking at how I might implement the same kind of boundary in Rust.&lt;/p&gt;

&lt;p&gt;Rust asked a more interesting question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the dangerous function accept an unchecked action in the first place?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;Runtime Checks Are Still Checks&lt;/h2&gt;

&lt;p&gt;The complete runnable example is &lt;a href="https://github.com/kenwalger/unrepresentable" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; if you want to make the compiler angry yourself.&lt;/p&gt;

&lt;p&gt;Consider a simplified AI agent that can write information into durable memory.&lt;/p&gt;

&lt;p&gt;The agent proposes a write:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;struct ProposedWrite {
    key: String,
    value: String,
    authority: String,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Somewhere else in the application we have a function that persists it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;fn persist(write: ProposedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Obviously we should validate the write first.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;fn validate(write: &amp;amp;ProposedWrite) -&amp;gt; bool {
    // validate provenance
    // evaluate authority
    // apply policy
    true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then our application does this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if validate(&amp;amp;write) {
    persist(write);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Perfectly reasonable.&lt;/p&gt;

&lt;p&gt;It also means &lt;code&gt;persist()&lt;/code&gt; will happily accept a &lt;code&gt;ProposedWrite&lt;/code&gt; that has never been validated. We are relying on every caller to remember the protocol.&lt;/p&gt;

&lt;p&gt;That is not necessarily a problem in a small example. In a large agentic system with multiple tools, services, developers, and execution paths, it becomes a much more interesting assumption.&lt;/p&gt;

&lt;p&gt;What happens when somebody adds this six months later?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;persist(write);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The compiler sees nothing wrong. The type is correct. The application is wrong.&lt;/p&gt;

&lt;h2&gt;Make the State Part of the Type&lt;/h2&gt;

&lt;p&gt;Rust gives us another option. Instead of treating "proposed" and "approved" as metadata attached to the same object, we can make them different types.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;struct ProposedWrite {
    key: String,
    value: String,
    authority: String,
}

struct AdmittedWrite {
    key: String,
    value: String,
    authority: Authority,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now persistence accepts only the second one:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;fn persist(write: AdmittedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This seemingly small change alters the boundary. An agent can produce a &lt;code&gt;ProposedWrite&lt;/code&gt;. It cannot produce an &lt;code&gt;AdmittedWrite&lt;/code&gt; directly, provided we control how that type is constructed. Something trusted has to perform the transition.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134220.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134220.png" alt="Flowchart of the custody transition. A ProposedWrite enters an evaluate decision point. If the authority is valid, it becomes an AdmittedWrite, shown in the trusted deep-green state. If the authority is invalid, it becomes Rejected, shown in red. Evaluate is the only path from proposed to admitted." width="710" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The persistence layer no longer asks:&lt;/p&gt;

&lt;blockquote&gt;
  Has somebody remembered to validate this?
&lt;/blockquote&gt;

&lt;p&gt;Its API says:&lt;/p&gt;

&lt;blockquote&gt;
  Give me something that has already crossed the admission boundary.
&lt;/blockquote&gt;

&lt;p&gt;That is a much stronger contract.&lt;/p&gt;

&lt;p&gt;This has a name in Rust circles: the &lt;a href="https://cliffle.com/blog/rust-typestate/" rel="noopener noreferrer"&gt;typestate pattern&lt;/a&gt;, encoding a value's state in its type so that only valid transitions type-check. It is the type-level cousin of a principle Alexis King named "&lt;a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/" rel="noopener noreferrer"&gt;parse, don't validate&lt;/a&gt;". Instead of checking a value and handing the same type onward, hoping every later caller re-checks, you transform it into a new type whose very existence proves the check already happened. The check is not something you remember to run. It is something the type system will not let you skip.&lt;/p&gt;

&lt;h2&gt;The Compiler Becomes Part of the Boundary&lt;/h2&gt;

&lt;p&gt;The phrase "provided we control how that type is constructed" is doing all the work in the previous section, so let us actually deliver that control. This is where the earlier version of this article was too loose, and where Rust rewards precision.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mod custody {
    use super::ProposedWrite;
    // Authority, Rejection, and validate_authority are defined in this module.

    pub struct AdmittedWrite {
        key: String,
        value: String,
        authority: Authority,
    }

    impl AdmittedWrite {
        pub fn key(&amp;amp;self) -&amp;gt; &amp;amp;str {
            &amp;amp;self.key
        }
        pub fn value(&amp;amp;self) -&amp;gt; &amp;amp;str {
            &amp;amp;self.value
        }
        pub fn authority(&amp;amp;self) -&amp;gt; &amp;amp;Authority {
            &amp;amp;self.authority
        }
    }

    pub fn evaluate(write: ProposedWrite) -&amp;gt; Result&amp;lt;AdmittedWrite, Rejection&amp;gt; {
        let authority = validate_authority(&amp;amp;write.authority)?;

        Ok(AdmittedWrite {
            key: write.key,
            value: write.value,
            authority,
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important detail is what is not marked &lt;code&gt;pub&lt;/code&gt;. The struct is public, so other modules can name the type and accept it in their signatures. Its fields are private.&lt;/p&gt;

&lt;p&gt;That distinction is the whole boundary. In Rust, a struct literal like &lt;code&gt;custody::AdmittedWrite { key, value, authority }&lt;/code&gt; requires every field to be visible at the construction site. Because the fields are private to the &lt;code&gt;custody&lt;/code&gt; module, no code outside that module can write that literal. And there is no other public constructor. The only way to obtain an &lt;code&gt;AdmittedWrite&lt;/code&gt; from outside is to hand a &lt;code&gt;ProposedWrite&lt;/code&gt; to &lt;code&gt;evaluate&lt;/code&gt; and have it succeed.&lt;/p&gt;

&lt;p&gt;So the persistence layer, which lives outside &lt;code&gt;custody&lt;/code&gt; and reads the data through the accessor methods, cannot be handed a value that skipped evaluation. Not because a reviewer will catch it, but because the code that would skip evaluation does not compile.&lt;/p&gt;

&lt;p&gt;Here is the shortcut a tired developer might reach for six months from now:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;let proposed = ProposedWrite {
    key: "refund_policy".into(),
    value: "Refunds under $100 do not require manager approval.".into(),
    authority: "policy".into(),
};

persist(proposed);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here is what the compiler says about it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;~\Rust_AI_Actions is 📦 v0.1.0 via 🦀 v1.98.1
❯ cargo build
   Compiling unrepresentable v0.1.0 (~\Rust_AI_Actions)
error[E0308]: mismatched types
   --&amp;gt; src\main.rs:128:13
    |
128 |     persist(proposed);
    |     ------- ^^^^^^^^ expected `AdmittedWrite`, found `ProposedWrite`
    |     |
    |     arguments to this function are incorrect
    |
note: function defined here
   --&amp;gt; src\main.rs:95:4
    |
 95 | fn persist(write: AdmittedWrite) {
    |    ^^^^^^^ --------------------

For more information about this error, try `rustc --explain E0308`.
error: could not compile `unrepresentable` (bin "unrepresentable") due to 1 previous error
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The mistake never reaches review, staging, or production. It stops at the one place a mistake is cheapest to fix, on the machine of the person who made it, the moment they made it.&lt;/p&gt;

&lt;p&gt;The state machine is no longer sitting in a comment:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// IMPORTANT: call validate() before persist()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is represented by the program.&lt;/p&gt;

&lt;p&gt;One honest caveat for larger teams: private fields close the door from outside the module, but code inside &lt;code&gt;custody&lt;/code&gt; can still build the struct with a literal. If you want to forbid even that, give &lt;code&gt;AdmittedWrite&lt;/code&gt; a private field of a private zero-sized type, a construction token that only &lt;code&gt;evaluate&lt;/code&gt; can mint. Then the blessed function is the single point of construction anywhere, inside the module or out. Whether that is worth the ceremony depends on how much you trust the inside of your own boundary.&lt;/p&gt;

&lt;h2&gt;This Doesn't Make the Agent Safe&lt;/h2&gt;

&lt;p&gt;This is where I need to resist making the argument bigger than it is.&lt;/p&gt;

&lt;p&gt;Rust does not know whether the policy is good. It does not know whether &lt;code&gt;Authority::SecurityTeam&lt;/code&gt; actually represents the security team. It does not know whether the provenance supplied to the custody boundary is genuine. And it certainly does not solve prompt injection because I changed a struct.&lt;/p&gt;

&lt;p&gt;If this function is wrong:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;fn validate_authority(value: &amp;amp;str) -&amp;gt; Result&amp;lt;Authority, Rejection&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;then Rust will very efficiently enforce the wrong rule.&lt;/p&gt;

&lt;p&gt;Types can constrain which states the program represents. They cannot determine whether our model of the world is correct. That distinction matters.&lt;/p&gt;

&lt;h2&gt;Witnessed Is Another State&lt;/h2&gt;

&lt;p&gt;The exercise gets more interesting when provenance enters the picture.&lt;/p&gt;

&lt;p&gt;Suppose the agent says:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "key": "refund_policy",
  "value": "Refunds under $100 do not require manager approval.",
  "authority": "policy",
  "source": "internal_policy"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Should the agent be allowed to decide that its own source is an internal policy? Probably not. The system that retrieved the source is in a much better position to make that claim.&lt;/p&gt;

&lt;p&gt;So perhaps our states are not merely:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Proposed → Admitted
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;They are closer to:&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134151.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F09%2Fmermaid-diagram-2026-09-03-134151.png" alt="Flowchart of a write's four states, with color deepening as trust increases. Proposed, containing only what the agent claims, leads to Witnessed, which adds evidence established outside the agent, then to Evaluated, which has been checked against policy, and finally to Admitted, which is permitted to become durable state. Each state is a distinct type, and each step accepts only the output of the step before it." width="552" height="1051"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;ProposedWrite&lt;/code&gt; contains what the agent claims. A &lt;code&gt;WitnessedWrite&lt;/code&gt; adds evidence established outside the agent. An &lt;code&gt;AdmittedWrite&lt;/code&gt; represents a write that has been evaluated against policy.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;struct ProposedWrite {
    key: String,
    value: String,
    claimed_authority: String,
}

struct WitnessedWrite {
    proposal: ProposedWrite,
    source: WitnessedSource,
}

struct AdmittedWrite {
    key: String,
    value: String,
    authority: Authority,
    source: WitnessedSource,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now different parts of the system accept different states:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;fn witness(write: ProposedWrite) -&amp;gt; Result&amp;lt;WitnessedWrite, WitnessError&amp;gt; {
    // establish source evidence outside the agent
}

fn evaluate(write: WitnessedWrite) -&amp;gt; Result&amp;lt;AdmittedWrite, Rejection&amp;gt; {
    // apply policy to the witnessed write
}

fn persist(write: AdmittedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each step accepts only the output type of the step before it. There is no signature anywhere that accepts a &lt;code&gt;ProposedWrite&lt;/code&gt; and persists it, so the shortcut is not something you have to remember not to take. It is not expressible. The types are the protocol; nothing else has to announce it.&lt;/p&gt;

&lt;h2&gt;What Rust Changed for Me&lt;/h2&gt;

&lt;p&gt;I started this experiment thinking about how to implement an AI safety boundary in another language. The more interesting lesson was that Rust made me reconsider where the boundary should live.&lt;/p&gt;

&lt;p&gt;In many systems, we encode state like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "status": "approved"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then every downstream consumer has to inspect &lt;code&gt;status&lt;/code&gt; and behave correctly.&lt;/p&gt;

&lt;p&gt;Rust encourages another question:&lt;/p&gt;

&lt;blockquote&gt;
  If these states permit fundamentally different operations, why are they represented by the same type?
&lt;/blockquote&gt;

&lt;p&gt;That question matters for AI agents because agentic systems cross consequential boundaries constantly.&lt;/p&gt;

&lt;p&gt;A model proposes a tool call. A runtime authorizes it. A tool executes it. A result becomes memory. Memory later becomes context. Context influences another action.&lt;/p&gt;

&lt;p&gt;At each transition, we can either carry another flag saying what happened, or change what the next component is capable of accepting. Those are not equivalent designs.&lt;/p&gt;

&lt;h2&gt;Invalid States Versus Invalid Reality&lt;/h2&gt;

&lt;p&gt;There is an important limit here.&lt;/p&gt;

&lt;p&gt;Suppose a write was legitimately admitted yesterday because Alice had authority to approve it. Alice leaves the company today. The &lt;code&gt;AdmittedWrite&lt;/code&gt; type does not magically expire. Likewise, a policy can be superseded, a credential revoked, or evidence later discovered to be wrong.&lt;/p&gt;

&lt;p&gt;The compiler can enforce:&lt;/p&gt;

&lt;blockquote&gt;
  This value crossed the required transition.
&lt;/blockquote&gt;

&lt;p&gt;It cannot establish:&lt;/p&gt;

&lt;blockquote&gt;
  Everything that justified that transition remains true forever.
&lt;/blockquote&gt;

&lt;p&gt;That still requires runtime governance, lifecycle management, revocation, and revalidation.&lt;/p&gt;

&lt;p&gt;So I would not claim Rust makes unsafe AI actions impossible. What it can do is make certain classes of &lt;strong&gt;architecturally invalid transitions&lt;/strong&gt; harder to express accidentally.&lt;/p&gt;

&lt;p&gt;That is narrower. It is also much more believable.&lt;/p&gt;

&lt;h2&gt;Could I Do This in Another Language?&lt;/h2&gt;

&lt;p&gt;Of course.&lt;/p&gt;

&lt;p&gt;You can model state transitions in Go, Java, TypeScript, Python, C#, or plenty of other languages. You can build wrapper types, sealed classes, discriminated unions, private constructors, capability objects, and carefully designed APIs. Rust does not own the idea, and the typestate pattern predates it.&lt;/p&gt;

&lt;p&gt;What I found useful is that Rust keeps pushing the design conversation in this direction. Ownership asks who controls a value. Visibility asks who can construct it. The type system asks what operations are valid for it. &lt;code&gt;Result&lt;/code&gt; makes failure part of the function signature.&lt;/p&gt;

&lt;p&gt;None of those concepts exists specifically for AI safety. Together, though, they provide an unusually direct vocabulary for designing agent boundaries, and the defaults nudge you toward making the boundary structural instead of remembered.&lt;/p&gt;

&lt;h2&gt;The Bigger Lesson&lt;/h2&gt;

&lt;p&gt;A lot of AI safety architecture is necessarily dynamic. Policies change. Users have different authority. Tools expose different capabilities. Context changes what an action means. We are never going to compile all of that uncertainty away.&lt;/p&gt;

&lt;p&gt;But not every invariant is dynamic.&lt;/p&gt;

&lt;p&gt;If an unwitnessed write must never be persisted, perhaps &lt;code&gt;persist()&lt;/code&gt; should not accept unwitnessed writes. If an unevaluated tool call must never execute, perhaps &lt;code&gt;execute()&lt;/code&gt; should not accept unevaluated tool calls. If a rejected action must never cross a boundary, perhaps rejection should produce a state for which crossing that boundary is not an available operation.&lt;/p&gt;

&lt;p&gt;That is what Rust changed in how I think about this problem.&lt;/p&gt;

&lt;p&gt;I started by asking:&lt;/p&gt;

&lt;blockquote&gt;
  How do I check that the agent is allowed to do this?
&lt;/blockquote&gt;

&lt;p&gt;Rust made me ask:&lt;/p&gt;

&lt;blockquote&gt;
  Why does this function accept something the agent is not allowed to do?
&lt;/blockquote&gt;

&lt;p&gt;That is a much better question.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Forensic Receipts: From Trusted to Proven</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:14:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/forensic-receipts-from-trusted-to-proven-5cj0</link>
      <guid>https://dev.to/kenwalger/forensic-receipts-from-trusted-to-proven-5cj0</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 6 of the Building the AI Memory Stack series&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At the end of the last article, I left one question unanswered.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you prove this record is exactly what was written?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_6_forensic_receipts" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt; decides which writes are trustworthy enough to become memory. But a decision to trust something is not the same as being able to prove it later.&lt;/p&gt;

&lt;p&gt;Picture that deployment record one more time. Custody examined it, judged it authoritative, and let it become institutional memory. Six months on, an auditor asks a harder question: how do you know it hasn't changed since?&lt;/p&gt;

&lt;p&gt;"We only accept trustworthy writes" is a policy.&lt;/p&gt;

&lt;p&gt;It is not proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust Is a Claim. Proof Is Evidence.
&lt;/h2&gt;

&lt;p&gt;Custody and receipts answer two different questions.&lt;/p&gt;

&lt;p&gt;Write-Side Custody asks: &lt;em&gt;should this be trusted?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A Forensic Receipt asks: &lt;em&gt;can this be proven?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first is a judgment made at the moment of the write. The second is a piece of evidence that outlives the judgment, so that anyone, later, can verify the record for themselves without having to trust the system that stored it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25phy9ydl3kqefquy8l1.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%2F25phy9ydl3kqefquy8l1.png" alt="Diagram of the AI memory stack showing Forensic Receipt flowing through Write-Side Custody, Reasoning Ledger, Durable Memory, Active Working Memory, and Context Window to Model Inference. Forensic Receipt is highlighted as the focus of this article." width="552" height="1435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, the stack reaches bedrock. Every layer above now rests on a foundation that can be verified independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Forensic Receipt Is
&lt;/h2&gt;

&lt;p&gt;The Sovereign Systems Specification calls this evidence a &lt;strong&gt;&lt;a href="https://sovereignplatform.dev/terms/forensic-receipt.html?utm_source=kenwalger_blog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_6_forensic_receipts" rel="noopener noreferrer"&gt;Forensic Receipt&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is not a log entry. Log entries can be edited, reordered, or quietly rewritten. A Forensic Receipt is a cryptographic fingerprint of a record, captured and signed at the moment the record is written.&lt;/p&gt;

&lt;p&gt;Change one character of the record, and the fingerprint no longer matches. The tampering isn't hidden. It's mathematically obvious.&lt;/p&gt;

&lt;p&gt;In practice, a receipt might look like this:&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;forensic_receipt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;record&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reasoning_ledger/deploy-2026-03-14&lt;/span&gt;
  &lt;span class="na"&gt;content_hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256:3af9c1...e07b&lt;/span&gt;
  &lt;span class="na"&gt;signed_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:22:07Z&lt;/span&gt;
  &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ed25519:9d4a...c2&lt;/span&gt;
  &lt;span class="na"&gt;signed_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sovereign-node-07&lt;/span&gt;
  &lt;span class="na"&gt;prior_receipt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256:8b21...44a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two fields do most of the work.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;content hash&lt;/strong&gt; binds the receipt to the exact bytes of the record. Nothing can be altered without breaking it.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;prior receipt&lt;/strong&gt; links each record to the one before it, forming a chain. You cannot quietly remove or reorder history without every downstream receipt failing.&lt;/p&gt;

&lt;p&gt;That is chain of custody, expressed as mathematics rather than as a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Logs Aren't Enough
&lt;/h2&gt;

&lt;p&gt;Most systems already keep audit logs.&lt;/p&gt;

&lt;p&gt;The problem is that an audit log is only as trustworthy as whoever controls it. If someone can write to the log, they can usually rewrite it, and a log that can be rewritten proves nothing about the past.&lt;/p&gt;

&lt;p&gt;A Forensic Receipt inverts that. It doesn't ask you to trust the operator, the database, or the backups. Verification depends on cryptography, not on authority, so the evidence speaks for itself.&lt;/p&gt;

&lt;p&gt;That distinction is the whole point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custody earns trust. Receipts remove the need for it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Proof
&lt;/h2&gt;

&lt;p&gt;None of this is free. Every receipt is a hash computed and a signature generated at write time, which is real work, paid on the write path, exactly where this series has argued trust belongs.&lt;/p&gt;

&lt;p&gt;But the alternative is worse. A system that cannot prove its own memory is asking you to take its word for everything it claims to remember.&lt;/p&gt;

&lt;p&gt;Proof is the price of being believed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack Is Now Trustworthy and Provable
&lt;/h2&gt;

&lt;p&gt;Step back and look at what the six layers guarantee together.&lt;/p&gt;

&lt;p&gt;The Context Window executes. &lt;a href="https://sovereignplatform.dev/terms/active-working-memory.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_6_forensic_receipts" rel="noopener noreferrer"&gt;Active Working Memory&lt;/a&gt; assembles. &lt;a href="https://sovereignplatform.dev/terms/durable-memory.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_6_forensic_receipts" rel="noopener noreferrer"&gt;Durable Memory&lt;/a&gt; preserves. The &lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=post_6_forensic_receipts" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt; explains. Write-Side Custody decides what to trust. Forensic Receipts prove it.&lt;/p&gt;

&lt;p&gt;Execution, assembly, preservation, explanation, integrity, and evidence.&lt;/p&gt;

&lt;p&gt;That is a complete architecture for trustworthy institutional memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;We've built memory that is durable, explained, trustworthy, and provable.&lt;/p&gt;

&lt;p&gt;But it is still sitting in cold storage.&lt;/p&gt;

&lt;p&gt;None of it matters until it can return to active reasoning: verified, on demand, and paid for deliberately.&lt;/p&gt;

&lt;p&gt;That is where the next article takes us, and where the promise from Part 1 is finally kept. It is the moment memory becomes voice.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>llm</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your Hiring Process Needs HTTP Status Codes</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Wed, 26 Aug 2026 15:14:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/your-hiring-process-needs-http-status-codes-png</link>
      <guid>https://dev.to/kenwalger/your-hiring-process-needs-http-status-codes-png</guid>
      <description>&lt;p&gt;&lt;em&gt;Because "we'll be in touch" is not an observable state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I submitted a job application recently.&lt;/p&gt;

&lt;p&gt;It was one of those applications where you actually put in the effort. I read the job description carefully, tailored the resume, wrote a cover letter specifically for the organization, and answered the optional questions rather than pretending they were optional.&lt;/p&gt;

&lt;p&gt;Then I clicked &lt;strong&gt;Submit&lt;/strong&gt; and got... nothing.&lt;/p&gt;

&lt;p&gt;No confirmation email. No "we received your application." No indication that a candidate record had been created. Just a browser page telling me the submission had succeeded and the vague hope that somewhere, deep inside an applicant tracking system, my carefully assembled application had not been written directly to &lt;code&gt;/dev/null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a developer, I find this troubling. Not because I expect an interview, or because I think every recruiter owes me personalized feedback. I just want a status code.&lt;/p&gt;

&lt;p&gt;And I think that's a smaller ask than it sounds like, for a reason I'll come back to at the end: the states already exist. Somewhere in that ATS, my application is in one. The decision not to show me which one is a product decision, not a privacy constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Web Solved This Problem Decades Ago
&lt;/h2&gt;

&lt;p&gt;When software sends a request to another system, we generally consider it useful for the receiving system to say what happened. Did it work? Is it still processing? Was the request malformed? Is the resource gone? Did something catch fire on the server?&lt;/p&gt;

&lt;p&gt;HTTP gives us an entire vocabulary for this.&lt;/p&gt;

&lt;p&gt;Hiring systems, meanwhile, have developed their own protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /careers/applications

[no observable response]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is sometimes followed, anywhere from three minutes to six months later, by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;After careful consideration...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of inventing vague candidate statuses like "Under Consideration," perhaps hiring systems could adopt something developers already understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2xx Family: Success, Allegedly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;200 OK&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A human being has reviewed your application.&lt;/p&gt;

&lt;p&gt;Nothing else is implied.&lt;/p&gt;

&lt;p&gt;Honestly, this alone would be revolutionary.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;202 Accepted&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your application has been received and accepted for processing.&lt;/p&gt;

&lt;p&gt;This is the most important status code in the entire hiring protocol. The candidate does not need the hiring manager's notes or a walkthrough of the internal workflow. They need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;202 APPLICATION_ACCEPTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we know the thing exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;204 No Content&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your application was successfully received.&lt;/p&gt;

&lt;p&gt;We have chosen not to acknowledge this in any way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3xx Family: You Have Been Redirected
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;302 Found&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We found an internal candidate.&lt;/p&gt;

&lt;p&gt;Thanks for playing.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;304 Not Modified&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your application has been "Under Review" for 47 days.&lt;/p&gt;

&lt;p&gt;There has been no change since the last time you checked, or the 23 times before that. There will be no change the next time either. Please consider caching this response locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;410 Gone&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The position existed when you applied. It no longer exists. No additional information is available.&lt;/p&gt;

&lt;p&gt;At least &lt;code&gt;410&lt;/code&gt; would be useful. The current discovery mechanism is noticing that your bookmarked job posting has started redirecting to the careers homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4xx Family: This One's On You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;401 Unauthorized&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You must create an account before applying.&lt;/p&gt;

&lt;p&gt;Your password must contain at least 14 characters, uppercase and lowercase letters, a number, a symbol, and one ancient Sumerian glyph. It cannot match any password you have used since 1997.&lt;/p&gt;

&lt;p&gt;You will then manually enter every field from the resume you just uploaded.&lt;/p&gt;

&lt;p&gt;You will never use this account again.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;403 Forbidden&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You have the required experience and qualifications.&lt;/p&gt;

&lt;p&gt;Unfortunately, you live 37 miles outside the geographic boundary within which we have determined this Zoom meeting can occur.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;404 Not Found&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Hiring manager not found. Recruiter not found. Job posting not found.&lt;/p&gt;

&lt;p&gt;Nobody knows who owns this requisition.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;408 Request Timeout&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;After seven weeks without communication, the candidate has accepted another position.&lt;/p&gt;

&lt;p&gt;The hiring team will contact them tomorrow to schedule the second interview.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;409 Conflict&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The position requires 12 years of production experience with a technology introduced six years ago.&lt;/p&gt;

&lt;p&gt;The conflict cannot be resolved.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;417 Expectation Failed&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You expected the phrase "we'll be in touch" to imply future communication.&lt;/p&gt;

&lt;p&gt;It did not.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;422 Unprocessable Content&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your experience is relevant, but your career path does not fit cleanly into the predefined boxes in our applicant tracking system.&lt;/p&gt;

&lt;p&gt;Human intervention may be required.&lt;/p&gt;

&lt;p&gt;Human intervention is unavailable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;425 Too Early&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You applied 14 minutes after the job was posted.&lt;/p&gt;

&lt;p&gt;LinkedIn reports 612 applicants.&lt;/p&gt;

&lt;p&gt;We have questions too.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;451 Unavailable For Legal Reasons&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your qualifications are not the issue.&lt;/p&gt;

&lt;p&gt;The issue is a background check vendor, a non-compete of uncertain enforceability, or a sponsorship question nobody wants to be the one to ask about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5xx Family: It Was Never You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;500 Internal Server Error&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Something has gone wrong internally. Your recruiter left, the headcount was frozen, the hiring manager changed, Finance reconsidered the budget, the organization restructured, or the position was posted by accident.&lt;/p&gt;

&lt;p&gt;We will communicate all of these possibilities using the same message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have decided to move forward with other candidates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;502 Bad Gateway&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The recruiter says they are waiting for the hiring manager.&lt;/p&gt;

&lt;p&gt;The hiring manager says recruiting owns the next step.&lt;/p&gt;

&lt;p&gt;Your request cannot be routed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;503 Service Unavailable&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Your recruiter is no longer with the company.&lt;/p&gt;

&lt;p&gt;Their calendar link remains active.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;504 Gateway Timeout&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;"We expect to have a decision by Friday."&lt;/p&gt;

&lt;p&gt;Friday has passed. The specification does not define which Friday was intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Candidates Don't Need Distributed Tracing
&lt;/h2&gt;

&lt;p&gt;There's a serious point hiding under all of this. Hiring has an observability problem, and it's usually mistaken for a communication problem.&lt;/p&gt;

&lt;p&gt;Companies understandably cannot expose everything happening inside a recruiting process. Candidate evaluations are private. Interview feedback may be sensitive. Hiring managers need room to compare people and make decisions.&lt;/p&gt;

&lt;p&gt;That's fine. Nobody is asking for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /hiring-manager/private-thoughts/about-me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is an enormous amount of territory between exposing confidential deliberations and providing no information at all.&lt;/p&gt;

&lt;p&gt;A hiring pipeline already has states. An application was received. It entered review. Someone reviewed it. An interview was requested. The requisition was paused. The position was filled. The position was canceled. The candidate was declined.&lt;/p&gt;

&lt;p&gt;Those state transitions already exist in the system. They are already recorded, timestamped, and reportable, because that's how recruiting teams measure their own funnel. The candidate is the one participant in the process who cannot see the record they're in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give Us the Boring Version
&lt;/h2&gt;

&lt;p&gt;Imagine an applicant portal that reported nothing more than this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;202  APPLICATION_RECEIVED     Mar 04
200  REVIEWED                 Mar 19
304  NO_CHANGE                Apr 02
304  NO_CHANGE                Apr 30
410  POSITION_CLOSED          May 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not radical transparency. It's barely transparency at all. But it tells you the system is functioning.&lt;/p&gt;

&lt;p&gt;It removes the need to wonder whether an application disappeared. It reduces pointless portal refreshing. And it prevents candidates from reading silence as information, when silence might mean anything from "the hiring manager is on vacation" to "the requisition was canceled two weeks ago."&lt;/p&gt;

&lt;p&gt;Mostly, it treats applicants like participants in a process rather than packets transmitted into an undocumented endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;200 OK&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I don't expect every application to result in an interview. I've submitted enough of them to have considerable empirical evidence supporting that position.&lt;/p&gt;

&lt;p&gt;I don't expect detailed rejection feedback either. I understand why companies are reluctant to provide it. I don't even particularly mind &lt;code&gt;403 REQUIREMENTS_NOT_MET&lt;/code&gt; or &lt;code&gt;410 POSITION_FILLED&lt;/code&gt;. Those are answers.&lt;/p&gt;

&lt;p&gt;What makes modern hiring uniquely frustrating isn't rejection. It's uncertainty about whether anything is happening at all.&lt;/p&gt;

&lt;p&gt;Software engineers have spent decades building systems around a simple principle: when one system asks another to do something, the response should carry enough information to understand what happened.&lt;/p&gt;

&lt;p&gt;Candidates aren't asking for distributed tracing of your recruiting infrastructure.&lt;/p&gt;

&lt;p&gt;We'd settle for a status code.&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>humor</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your AI Agent Shouldn't Be Allowed to Write Whatever It Wants</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Tue, 25 Aug 2026 19:11:50 +0000</pubDate>
      <link>https://dev.to/kenwalger/your-ai-agent-shouldnt-be-allowed-to-write-whatever-it-wants-e33</link>
      <guid>https://dev.to/kenwalger/your-ai-agent-shouldnt-be-allowed-to-write-whatever-it-wants-e33</guid>
      <description>&lt;p&gt;&lt;em&gt;Building a Write-Side Custody gate in Go&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI memory systems spend most of their design budget on retrieval. Which vector database? How should we chunk? Which embedding model? What should &lt;code&gt;top_k&lt;/code&gt; be?&lt;/p&gt;

&lt;p&gt;Those are useful questions, but they all arrive after something more consequential has already happened: the system decided that some piece of information deserved to become memory.&lt;/p&gt;

&lt;p&gt;Consider an agent researching vendors for regulated workloads. It finds this statement:&lt;/p&gt;

&lt;blockquote&gt;
  Vendor X is approved for regulated workloads.
&lt;/blockquote&gt;

&lt;p&gt;The source is Vendor X's own marketing site.&lt;/p&gt;

&lt;p&gt;The statement might be true. It might even be current. But the source does not have the authority to establish organizational security policy. If our agent writes it directly into durable memory, better retrieval will not save us. We have only made questionable evidence easier to find.&lt;/p&gt;

&lt;p&gt;The problem is not storage. It is admission.&lt;/p&gt;

&lt;p&gt;I've been calling the architectural boundary responsible for that decision &lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=write_side_custody_go" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt;. Let's build a small one in Go.&lt;/p&gt;

&lt;h2&gt;What Write-Side Custody Does&lt;/h2&gt;

&lt;p&gt;A storage API answers a mechanical question:&lt;/p&gt;

&lt;blockquote&gt;
  Can I persist this object?
&lt;/blockquote&gt;

&lt;p&gt;Write-Side Custody asks a different set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is trying to write this?&lt;/li&gt;
&lt;li&gt;Where did the information come from?&lt;/li&gt;
&lt;li&gt;What authority is being claimed?&lt;/li&gt;
&lt;li&gt;Is that source allowed to establish that authority?&lt;/li&gt;
&lt;li&gt;Does policy permit this class of information to become durable?&lt;/li&gt;
&lt;li&gt;What evidence should survive the decision?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after those are answered should storage become involved.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-25-114307.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-25-114307.png" alt="Flowchart showing a proposed write travelling from an agent or application into a Write-Side Custody gate. The gate routes accepted writes to durable memory and rejected writes to discard, while a dotted line records the decision in a Reasoning Ledger." width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the &lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=write_side_custody_go" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt; does not make the decision. Custody enforces. The ledger witnesses. That separation matters a great deal once these systems have to be examined later.&lt;/p&gt;

&lt;h2&gt;Start With the Proposed Write&lt;/h2&gt;

&lt;p&gt;Go gives us a useful property for this experiment: we can make the things crossing our boundary explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ProposedWrite&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Source&lt;/span&gt;           &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ProducedBy&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our research agent might produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ProposedWrite&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="s"&gt;"Vendor X is approved for regulated workloads."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="s"&gt;"https://vendorx.example.com/why-vendorx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ProducedBy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"research-agent-run-4471"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"security-policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in this structure says the statement is false, and that's intentional. Write-Side Custody is not a universal truth detector. It determines whether a proposed write satisfies the rules governing this particular memory system.&lt;/p&gt;

&lt;p&gt;For this system, a vendor marketing page cannot establish internal security policy. So we need policy.&lt;/p&gt;

&lt;h2&gt;Make Authority Explicit&lt;/h2&gt;

&lt;p&gt;First, two types. Authorities and source classes are different kinds of thing, and there is no situation in which we want to accidentally use one where the other belongs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;SourceType&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can define which source classes may establish which authorities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AuthoritySources&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="n"&gt;SourceType&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AuthoritySources&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="n"&gt;SourceType&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"security-policy"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"internal-security-policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"security-authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"user-preference"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"application-state"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production this comes from a policy service or configuration layer rather than a Go literal. The important part is that the relationship exists independently of whatever the agent claims. The agent does not get to decide that a marketing page constitutes security authority simply because it found one saying something useful.&lt;/p&gt;

&lt;h2&gt;Give the Gate a Verdict&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Allow&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ALLOW"&lt;/span&gt;
    &lt;span class="n"&gt;Deny&lt;/span&gt;  &lt;span class="n"&gt;Verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DENY"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CustodyDecision&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Verdict&lt;/span&gt;   &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;Reason&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the gate itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;EvaluateWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="n"&gt;ProposedWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sourceType&lt;/span&gt; &lt;span class="n"&gt;SourceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CustodyDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;allowedSources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthoritySources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CustodyDecision&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"unknown claimed authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;allowedSources&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sourceType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CustodyDecision&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;Allow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"source may establish claimed authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CustodyDecision&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"source cannot establish claimed authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two decisions in there are worth surfacing.&lt;/p&gt;

&lt;p&gt;The first is that conversion on the map lookup. &lt;code&gt;ProposedWrite&lt;/code&gt; holds plain strings because that's what arrives over the wire, deserialized from JSON we did not write. &lt;code&gt;Authority(write.ClaimedAuthority)&lt;/code&gt; is the moment an untrusted string becomes a term in our governance vocabulary, and it happens inside the gate rather than at the edge of the process. That's the right place for it. Custody is precisely the layer where foreign input earns domain meaning.&lt;/p&gt;

&lt;p&gt;The second is that &lt;code&gt;sourceType&lt;/code&gt; is a separate parameter. It is not a field on &lt;code&gt;ProposedWrite&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is deliberate. Source classification is a judgment about the write, not a property the writer gets to assert about itself. If &lt;code&gt;sourceType&lt;/code&gt; lived on the struct, our agent could label its own marketing page &lt;code&gt;internal-security-policy&lt;/code&gt; and the gate would cheerfully agree. The classifier belongs to the custody layer, or to a runtime component that can independently observe where the content came from.&lt;/p&gt;

&lt;p&gt;Small signature choice. Most of the security property.&lt;/p&gt;

&lt;p&gt;Our vendor claim now reaches the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;EvaluateWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"vendor-marketing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;DENY&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;establish&lt;/span&gt; &lt;span class="n"&gt;claimed&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The statement never becomes durable memory. We did not store questionable evidence and hope retrieval would eventually sort things out. We governed the write while the evidence and its provenance were still in hand.&lt;/p&gt;

&lt;p&gt;The full gate, the policy, and a table-driven test suite covering the cases above are in &lt;a href="https://github.com/kenwalger/memory-stack-patterns/tree/post-01-go-custody/go" rel="noopener noreferrer"&gt;memory-stack-patterns&lt;/a&gt;. Standard library only, so &lt;code&gt;go test ./...&lt;/code&gt; and &lt;code&gt;go run ./cmd/demo&lt;/code&gt; work on a clean checkout with nothing to install.&lt;/p&gt;

&lt;h2&gt;Don't Throw Away the Rejection&lt;/h2&gt;

&lt;p&gt;Rejecting a write does not make the decision useless.&lt;/p&gt;

&lt;p&gt;Imagine someone asks six months later:&lt;/p&gt;

&lt;blockquote&gt;
  Why doesn't the system remember that Vendor X was approved?
&lt;/blockquote&gt;

&lt;p&gt;"I don't know" is not a satisfying answer, and in a regulated environment it isn't an acceptable one either.&lt;/p&gt;

&lt;p&gt;The custody decision is observable system behavior, which makes it a candidate for a Reasoning Ledger record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LedgerEntry&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;               &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Timestamp&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;
    &lt;span class="n"&gt;Actor&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt;           &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Verdict&lt;/span&gt;          &lt;span class="n"&gt;Verdict&lt;/span&gt;
    &lt;span class="n"&gt;Reason&lt;/span&gt;           &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Source&lt;/span&gt;           &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our gate emits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LedgerEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;               &lt;span class="n"&gt;newID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProducedBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="s"&gt;"durable-memory-write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClaimedAuthority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;newID&lt;/code&gt; is a few lines over &lt;code&gt;crypto/rand&lt;/code&gt;, which keeps the whole example dependency-free.)&lt;/p&gt;

&lt;p&gt;This entry is deliberately simplified. A ledger you would actually rely on needs a canonical serialization, a hash chain linking each entry to its predecessor, and some defense against tail truncation, because an append-only log that anyone can quietly shorten is not append-only.&lt;/p&gt;

&lt;p&gt;Even the timestamp is less innocent than it looks. &lt;code&gt;time.Now()&lt;/code&gt; gives you whatever precision the host clock offers, and JSON drops trailing zeros, so two entries can serialize at different widths. Hash a chain over a non-deterministic encoding and you have hashed nothing. The repo linked above truncates to a fixed precision and formats with a fixed-width layout for exactly that reason.&lt;/p&gt;

&lt;p&gt;The Python implementation in the &lt;a href="https://github.com/kenwalger/sovereign-sdk?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=write_side_custody_go" rel="noopener noreferrer"&gt;Sovereign Systems SDK&lt;/a&gt; does all three, which is part of why the Go exercise interests me. The hard parts are already solved somewhere. The open question is what happens to the boundary when it moves.&lt;/p&gt;

&lt;p&gt;What matters here is the shape of what survives. The rejected statement still doesn't enter memory. What persists is evidence that a write was proposed, evaluated, and rejected under a named rule. That is a different kind of information than the claim itself, and it's the kind that answers questions later.&lt;/p&gt;

&lt;h2&gt;The Agent Doesn't Get to Grade Its Own Homework&lt;/h2&gt;

&lt;p&gt;There's a further boundary hiding in the payload. Suppose our agent sends:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "content": "Vendor X is approved for regulated workloads.",
  "source": "https://vendorx.example.com/why-vendorx",
  "claimed_authority": "security-policy",
  "retrieval_method": "fresh",
  "policy_verified": true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Should we believe the last two fields?&lt;/p&gt;

&lt;p&gt;There is an epistemic difference between:&lt;/p&gt;

&lt;blockquote&gt;
  The agent says it performed a fresh retrieval.
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
  The runtime that performed the HTTP request witnessed a fresh retrieval.
&lt;/blockquote&gt;

&lt;p&gt;The same distinction applies to tool execution, timestamps, approval events, and policy versions. A stronger custody boundary therefore doesn't only ask whether a record may be written. It asks:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;Is this writer authorized to assert this particular kind of claim?&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent legitimately owns claims about itself: its decision, the alternatives it considered, its confidence, the unknowns it identified. The runtime should mint the facts it can independently witness. Custody should not promote the former into the latter merely because both arrived in valid JSON.&lt;/p&gt;

&lt;p&gt;This is the same principle as the &lt;code&gt;sourceType&lt;/code&gt; parameter, applied one level up.&lt;/p&gt;

&lt;h2&gt;Why Do This at Write Time?&lt;/h2&gt;

&lt;p&gt;You could defer all of this to retrieval. Store everything, attach metadata, and let the reader decide what governs.&lt;/p&gt;

&lt;p&gt;But then every questionable write becomes something every future reader has to reason around. It consumes storage. It becomes eligible for retrieval. It competes for context. It can be summarized, embedded, and propagated into records that no longer carry its provenance. And once provenance is gone, a future system may not have enough information to work out why the record was questionable in the first place.&lt;/p&gt;

&lt;p&gt;A bad write today becomes bad context tomorrow.&lt;/p&gt;

&lt;p&gt;Write-Side Custody puts the decision at the moment the system has the best possible view of what it is admitting.&lt;/p&gt;

&lt;h2&gt;Why Go?&lt;/h2&gt;

&lt;p&gt;None of this architecture requires Go, which is partly why I wanted to build it in Go.&lt;/p&gt;

&lt;p&gt;A custody gate is a boundary service, and Go fits that role: explicit data structures, unremarkable HTTP services, small deployable binaries, and a type system strong enough to make the important distinctions visible without taking over the implementation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Authority&lt;/code&gt; and &lt;code&gt;SourceType&lt;/code&gt; declarations we needed earlier are the clearest example. They cost one line each, and in exchange the compiler now refuses to let a source class be used where an authority belongs. That distinction would otherwise have lived in a variable name and a hope.&lt;/p&gt;

&lt;p&gt;The same move applies elsewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Verdict&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ActorType&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At which point the function signatures start expressing the vocabulary of the governance system rather than just its plumbing. &lt;code&gt;EvaluateWrite&lt;/code&gt; doesn't take three strings. It takes a proposed write, a source classification, and a policy, and no caller can shuffle them by accident.&lt;/p&gt;

&lt;p&gt;Go didn't create the architecture. It made the contracts hard to leave implicit.&lt;/p&gt;

&lt;h2&gt;Memory Begins Before Storage&lt;/h2&gt;

&lt;p&gt;Vector databases are very good at answering questions about similarity. They cannot tell us whether something deserved to become memory.&lt;/p&gt;

&lt;p&gt;That's an architecture decision, and by the time retrieval surfaces the problem, the questionable record may already have shaped dozens of others.&lt;/p&gt;

&lt;p&gt;Give the proposed write provenance. Give the boundary policy. Give the decision evidence. Then let storage do what storage is good at.&lt;/p&gt;

&lt;p&gt;Store what survived.&lt;/p&gt;





&lt;p&gt;&lt;em&gt;One thing I keep turning over: this boundary shouldn't depend on Go. If Write-Side Custody only makes sense inside one language, it isn't much of an architectural boundary. I'm curious what it would look like elsewhere. Would Rust's type system make an invalid custody decision impossible to construct rather than merely inconvenient? Would Pydantic and FastAPI make the policy check feel so natural you'd stop noticing you were doing governance at all? If you've built something like this in your stack, I'd like to hear how the boundary changed shape.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I maintain the Sovereign Systems specification and SDK, which is where the vocabulary in this post comes from. The Go code here is a reference implementation written to test whether the idea travels, not a product.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Write-Side Custody: Trust Begins Before Memory</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/write-side-custody-trust-begins-before-memory-1cam</link>
      <guid>https://dev.to/kenwalger/write-side-custody-trust-begins-before-memory-1cam</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 5 of the Building the AI Memory Stack series&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The previous articles introduced the &lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=write_side_custody" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt; and then worked through what a single ledger record should contain. Together they answered a hard question: why did this decision happen, and how do we design a record that can be trusted to say so honestly.&lt;/p&gt;

&lt;p&gt;That raises the question underneath all of it. A record is only as good as what was allowed to become one in the first place.&lt;/p&gt;

&lt;p&gt;Imagine reviewing an AI-assisted deployment six months later, the same approval the Reasoning Ledger recorded earlier in this series. The ledger says it was approved. The evidence looks complete, the timestamps make sense, everything appears correct. Then you notice that one of the "authoritative" policy records the decision relied on was written by a tool run that no one had authorized, from a source that should never have been treated as governing.&lt;/p&gt;

&lt;p&gt;Nothing was tampered with after the fact. The record is faithful. The problem is earlier than that: a write that should never have been accepted became institutional memory, and every decision downstream inherited it.&lt;/p&gt;

&lt;p&gt;At that point the problem is no longer memory. It is admission.&lt;/p&gt;

&lt;h2&gt;Memory Alone Cannot Create Trust&lt;/h2&gt;

&lt;p&gt;Throughout this series we have gradually separated the responsibilities of an AI system. The Context Window enables execution. &lt;a href="https://sovereignplatform.dev/terms/active-working-memory.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=write_side_custody" rel="noopener noreferrer"&gt;Active Working Memory&lt;/a&gt; assembles context. &lt;a href="https://sovereignplatform.dev/terms/durable-memory.html?utm_source=kenwalger_blog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=write_side_custody" rel="noopener noreferrer"&gt;Durable Memory&lt;/a&gt; preserves knowledge. The Reasoning Ledger explains and records decisions.&lt;/p&gt;

&lt;p&gt;None of those layers decides whether a given piece of information deserved to enter the system at all. Knowing what happened is valuable. Ensuring that only trustworthy information became part of what the system knows is what makes the rest of it worth trusting.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-21-202411.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-21-202411.png" alt="Diagram of the AI memory stack showing where Write-Side Custody sits. A proposed write enters Write-Side Custody. Accepted writes flow down into Durable Memory, then Active Working Memory, then the Context Window, then Model Inference. A separate dashed branch runs from Write-Side Custody to the Reasoning Ledger, labeled &amp;quot;decision witnessed,&amp;quot; showing that the ledger observes the custody decision rather than sitting in the write path." width="800" height="1127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;The Landfill Problem&lt;/h2&gt;

&lt;p&gt;Organizations rarely fail because they cannot remember enough. They struggle because they cannot distinguish trusted institutional knowledge from accumulated noise.&lt;/p&gt;

&lt;p&gt;This is the failure mode that should motivate everything else in this article. A durable memory that accepts every write eventually becomes a landfill: technically complete, searchable, and useless, because the trustworthy records and the junk are indistinguishable once they are all sitting in the same store with the same retrieval priority. Every low-quality write does not just take up space. It becomes a candidate for future retrieval, future reasoning, and future decisions. A bad write today is bad context tomorrow.&lt;/p&gt;

&lt;p&gt;The instinct is to fix this on the read side, with better ranking and filtering. That helps at the margin and loses the war, because by the time a bad record is competing for retrieval, it already looks like everything else. The cheaper and more durable fix is not to accept it in the first place. That decision has to happen at the boundary, before the write becomes memory.&lt;/p&gt;

&lt;h2&gt;Trust Begins Before Storage&lt;/h2&gt;

&lt;p&gt;A common architectural assumption is that integrity can be added later. Encrypt the database, protect the backups, restrict access. Those are all worthwhile, and they are also too late. By the time information reaches storage, the most important decision has already been made: whether this write should have been accepted at all.&lt;/p&gt;

&lt;p&gt;The Sovereign Systems Specification calls the responsibility for that decision &lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=building_the_ai_memory_stack&amp;amp;utm_content=write_side_custody" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt;. It sits before information becomes durable memory, and it asks a different question than a storage layer does. A storage layer asks whether this can be persisted. Custody asks whether it is legitimate.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-21-202658.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-21-202658.png" alt="Diagram showing how Write-Side Custody evaluates incoming information. Observations, tool results, and policies flow into a Write-Side Custody decision point. Verified writes proceed to Durable Memory, rejected writes are discarded, and in all cases the custody decision is recorded to the Reasoning Ledger." width="799" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of "can this be stored," custody asks whether the information is authoritative, whether the source is trusted, whether the supporting evidence was preserved, whether the write violates policy, and whether this should become institutional knowledge at all. The answers determine whether the write is accepted, rejected, or accepted with qualification.&lt;/p&gt;

&lt;h2&gt;What a Custody Decision Looks Like&lt;/h2&gt;

&lt;p&gt;Principles are easy to nod along to and hard to picture, so here is a concrete one.&lt;/p&gt;

&lt;p&gt;An agent finishes a tool run and tries to write a new record into durable memory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;proposed_write:
  content: "Vendor X is approved for regulated workloads."
  claimed_authority: security-policy
  source: web-fetch:vendor-x-marketing-page
  produced_by: research-agent-run-4471
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At the boundary, custody evaluates it rather than storing it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;custody_decision:
  accepted: false
  reason: authority-mismatch
  detail: &amp;gt;
    Record claims security-policy authority, but the source is an
    external marketing page, not the security team's policy service.
    A regulated-workload approval cannot be granted by the artifact
    it would benefit.
  action: discard
  recorded_to_ledger: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The write never reaches durable memory. The content might even be true, and it still fails, because custody is not judging whether the claim is plausible. It is judging whether this source is allowed to establish that claim as governing knowledge. An approval that arrives from the thing being approved does not get to become institutional memory, no matter how confident the text sounds.&lt;/p&gt;

&lt;p&gt;Notice the last field. The rejection did not vanish. It became an event the Reasoning Ledger preserves, which is the point of the next section.&lt;/p&gt;

&lt;h2&gt;Custody Enforces. The Ledger Witnesses.&lt;/h2&gt;

&lt;p&gt;There is an apparent contradiction here worth resolving directly, because a reader following the series will feel it.&lt;/p&gt;

&lt;p&gt;The Reasoning Ledger, by design, does not enforce. It witnesses. Its records stay examinable precisely because the ledger cannot block anything. Write-Side Custody, by contrast, exists to enforce. It rejects writes. So which is it, do these layers block things or not?&lt;/p&gt;

&lt;p&gt;Both, and that is the whole architecture. Custody is the enforcement boundary. The ledger is the witness that records what the boundary decided. When custody rejects the vendor-approval write, custody makes the decision and stops the write, and the ledger records that a write was proposed, evaluated, and rejected, along with the reason. Neither layer does the other's job. The enforcer never becomes the historian, and the historian never gets a veto. That separation is exactly why each one can be trusted at what it does: an enforcer that also wrote the only account of its own decisions would be unauditable, and a witness that could block actions would stop being a neutral record.&lt;/p&gt;

&lt;p&gt;This is the same "separate custody, one interface" principle from earlier in the series, seen from the write side. Different responsibilities, deliberately kept in different layers, cooperating at a boundary.&lt;/p&gt;

&lt;h2&gt;Every Write Is a Governance Event&lt;/h2&gt;

&lt;p&gt;Traditional applications treat writes as database operations. Agentic systems should treat them as governance events, because in an agentic system a write is not just data at rest. It is a future participant in reasoning.&lt;/p&gt;

&lt;p&gt;Every accepted write influences future retrieval, shapes future reasoning, and becomes a candidate for future decisions. That is a much larger claim than "this row was inserted." It means the moment of admission is the cheapest and most powerful place to protect everything downstream. A policy check at the boundary is worth more than a great deal of read-side cleverness later, because it prevents the bad record from ever competing for attention in the first place.&lt;/p&gt;

&lt;p&gt;Custody is what keeps durable memory intentional rather than merely persistent. It is the mechanism that slows the entropy from trusted knowledge toward landfill, one admission decision at a time.&lt;/p&gt;

&lt;h2&gt;Looking Ahead&lt;/h2&gt;

&lt;p&gt;Write-Side Custody decides what is allowed to become memory. It does not, on its own, prove that an accepted record is still byte-for-byte what was written.&lt;/p&gt;

&lt;p&gt;Suppose someone asks a harder question six months later: not "should this have been accepted," which custody answers, but "can you prove this exact record has not been altered since." That is a different guarantee, and it needs more than an admission policy. It needs evidence that survives independent of the store that holds it.&lt;/p&gt;

&lt;p&gt;That is where &lt;a&gt;Forensic Receipts&lt;/a&gt; enter the architecture, and where the next article takes us.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>llm</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Designing a Reasoning Ledger Record</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:23:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/designing-a-reasoning-ledger-record-22eo</link>
      <guid>https://dev.to/kenwalger/designing-a-reasoning-ledger-record-22eo</guid>
      <description>&lt;p&gt;&lt;em&gt;A companion to Part 4 of the Building the AI Memory Stack series. Part 4.5 of the series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 4 argued that agentic systems need a &lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt;: a layer that preserves why a decision happened, not just what was decided. The comment thread that followed turned into something more specific and more useful, a working design conversation about what a single ledger record should actually contain. This piece consolidates that. Several of the strongest ideas below arrived from other people, and I have tried to credit them where they land.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The easy version of this article is a schema. Here are the fields, copy them, done.&lt;/p&gt;

&lt;p&gt;I want to resist that, because the field list is the least durable thing I could hand you. Implementations differ, field names drift, and a record shape copied without its reasoning becomes cargo-cult structure that nobody maintains. The useful thing is the set of design tensions that decide what belongs in the record and what does not. Get those right and you can derive the fields yourself. Get them wrong and no schema will save you.&lt;/p&gt;

&lt;p&gt;So this is principles first, record second. At the end there is a worked record and a field reference, tagged for what is core and what is genuinely optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Starting Point
&lt;/h2&gt;

&lt;p&gt;Here is the baseline record from Part 4. It is a reasonable start and, as the thread quickly established, incomplete in instructive ways.&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;reasoning_ledger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deployment"&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:22:00Z&lt;/span&gt;
  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ADR-014&lt;/span&gt;
      &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;architecture-review&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-policy&lt;/span&gt;
      &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-team&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GitHub&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CI pipeline&lt;/span&gt;
  &lt;span class="na"&gt;approvals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;release manager&lt;/span&gt;
  &lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every principle below is, in effect, a thing this record does not yet say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 1: The Ledger Witnesses, It Does Not Enforce
&lt;/h2&gt;

&lt;p&gt;The first tension is architectural, and it is the one I would defend hardest. A reasoning ledger must not be able to block, veto, or gate the action it records. Its job is to preserve what happened and what evidence surrounded it. The moment the ledger can prevent an action, it stops being an independent witness and becomes part of the mechanism it is supposed to describe, and its own records stop being examinable as neutral fact.&lt;/p&gt;

&lt;p&gt;This came up when &lt;a href="https://dev.to/pm25coder"&gt;pm25coder&lt;/a&gt; noted, correctly, that a ledger that only narrates can quietly become fiction, and that trust comes from being able to gate rather than merely describe. I agree with the diagnosis and draw the boundary one step earlier: enforcement is real and necessary, but it belongs at the policy and tool boundary, not inside the witness. The ledger preserves that the boundary was evaluated and what it returned. The boundary decides whether the action proceeds.&lt;/p&gt;

&lt;p&gt;The practical consequence for the record: a ledger entry can contain a &lt;code&gt;policy_evaluated&lt;/code&gt; result showing that a check ran and what it concluded, but it never contains the enforcement decision as its own authority. It reports; it does not rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core.&lt;/strong&gt; This is not a field, it is a constraint on the whole design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 2: Supersession Is a New Event, Never a Rewrite
&lt;/h2&gt;

&lt;p&gt;A superseded decision should become a new record that points back at the old one. It should never overwrite the original. "We decided A, and later decided B instead" is two events with a relationship between them, not one field that changed value.&lt;/p&gt;

&lt;p&gt;This matters because "wrong now" does not mean "was never decided then." If you rewrite the March record when you change course in August, you have destroyed the ability to answer whether the March decision was reasonable given what was known in March. The noisier history is the correct trade. Compaction can always produce a clean current-state projection later, but once you have rewritten the historical evidence, you cannot reconstruct it.&lt;/p&gt;

&lt;p&gt;This is the same append-only discipline that makes &lt;a href="https://sovereignplatform.dev/terms/forensic-receipt.html" rel="noopener noreferrer"&gt;Forensic Receipts&lt;/a&gt; useful: preserve what was decided under which evidence and authority, then record the superseding decision as its own event with its own receipt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 3: Record How the Authority Was Obtained, Not Just Which One
&lt;/h2&gt;

&lt;p&gt;The baseline record says &lt;code&gt;version: 7&lt;/code&gt;. That tells a future reader what supposedly governed. It does not tell them how the system established that version 7 was authoritative at decision time, and those are very different trust claims.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/kenielzep97"&gt;Self-Correcting Systems&lt;/a&gt; and &lt;a href="https://dev.to/pm25coder"&gt;pm25coder&lt;/a&gt; arrived at this from opposite directions and met in the middle: a policy version fetched fresh from its authority at 09:22, a version read from a five-minute cache, and a version inherited from session state can produce identical &lt;code&gt;version: 7&lt;/code&gt; fields while supporting completely different claims about what the system could reasonably have known. The fix is to treat the authority fetch itself as a recorded event. The record should say which source was consulted, when, what came back, and whether cached state was involved.&lt;/p&gt;

&lt;p&gt;This also exposes the sharpest failure mode in the thread, the one an otherwise perfect ledger cannot catch on its own. If the external authority moved to version 8 an hour before your decision and nothing in your system observed that change, the record faithfully captures version 7 and stays perfectly self-consistent. It is a flawless account of a decision that was already wrong when it was made. The record cannot flag this, because there is no edge to preserve; nothing inside the system ever saw the change. Recording how the version was obtained at least lets a later examiner distinguish "we checked and got stale data" from "we never checked."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core&lt;/strong&gt; for the fact of how evidence was obtained. The revalidation mechanism that catches silent version drift lives outside the record, and Principle 7 covers it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 4: Relationships Need Two Clocks
&lt;/h2&gt;

&lt;p&gt;If you ever want to reconstruct what the system could have known at a past moment, every relationship in the ledger needs two timestamps, not one. This is standard bitemporal modeling, and &lt;a href="https://dev.to/gde03"&gt;Giulio D'Erme&lt;/a&gt; named exactly why it is not optional here.&lt;/p&gt;

&lt;p&gt;Valid time is when a fact was true in the world. Transaction time is when your system asserted or learned the relationship. If a supersession edge carries only a single date, replaying last March will show March's decision annotated with August's supersessions, and the decision-maker will look like they ignored a policy that did not yet exist. You will have judged a past decision using knowledge that arrived in the future, which is the precise thing a reasoning ledger exists to prevent.&lt;/p&gt;

&lt;p&gt;So a supersession or correction relationship carries both &lt;code&gt;valid_time&lt;/code&gt; (when the new state became true) and &lt;code&gt;asserted_at&lt;/code&gt; (when the system recorded the edge). Reconstruction filters on &lt;code&gt;asserted_at&lt;/code&gt; to see only what was knowable then.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core&lt;/strong&gt; for any ledger whose purpose includes reconstructing historical decision context. If you genuinely only ever query current state, you can defer this, but that is a smaller ambition than most of these systems have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 5: Preserve What Lost, Not Just What Won
&lt;/h2&gt;

&lt;p&gt;A ledger that records only the evidence supporting the final decision is a post-hoc justification engine wearing an audit trail. You can reconstruct why the decision looked reasonable, and you have quietly lost what competed with it, what failed a threshold, and what stayed unresolved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/gnomeman4201"&gt;GnomeMan4201&lt;/a&gt; made this case from the investigation side, and it reframed the record for me. An immutable ledger can preserve history perfectly and still preserve a biased history if the losing evidence never gets written. The distinction between "we chose A because of X" and "we chose A because of X, rejected B because of Y, and could not resolve Z" is enormous when someone later asks whether the decision was defensible given what was actually known.&lt;/p&gt;

&lt;p&gt;The fields this implies: &lt;code&gt;alternatives_considered&lt;/code&gt; with a &lt;code&gt;rejection_reason&lt;/code&gt; for each, &lt;code&gt;disconfirmed_by&lt;/code&gt; for evidence that actively cut against the chosen path, and &lt;code&gt;unknowns&lt;/code&gt; or &lt;code&gt;scope_limitations&lt;/code&gt; for what the system could not resolve at decision time.&lt;/p&gt;

&lt;p&gt;A scoping note, in answer to &lt;a href="https://dev.to/kartik-nvjk"&gt;Kartik N V J K&lt;/a&gt;, who asked whether to capture rejected branches: capture the alternatives that were explicit parts of the decision process, not an exhaustive reconstruction of every path the model internally considered. If the agent evaluated three tools and rejected two on policy grounds, those rejections are observable decision evidence and belong in the record. The model's private deliberation does not. Observable reasoning is architecture; private reasoning belongs to the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional, escalating to Core with stakes.&lt;/strong&gt; For a low-consequence decision, surviving evidence may be enough. For anything a human will later audit, defend, or be held accountable for, treat these as required. The higher the stakes, the more the losing evidence matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 6: The Trigger Is a First-Class Field
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/pm25coder"&gt;pm25coder&lt;/a&gt; offered the most immediately practical field in the thread, from running a live decision ledger: the thing people actually read first, months later, is not the outcome. It is what provoked the decision. A timestamped complaint, an incident, a threshold breach, a human request. When every record carries its trigger, "why did we change this" becomes a search rather than an archaeology project, and the audit trail starts writing itself.&lt;/p&gt;

&lt;p&gt;It is easy to bury the trigger inside an evidence list. Do not. Promote it to its own field, because it is the field that makes the record findable by the question a future reader will actually bring to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core.&lt;/strong&gt; Small field, disproportionate value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 7: Some Things Belong Outside the Record
&lt;/h2&gt;

&lt;p&gt;Two mechanisms the thread kept reaching for are real and necessary, and they do not go in the ledger entry. Naming them keeps the record honest about what it is.&lt;/p&gt;

&lt;p&gt;The first is revalidation. A ledger cannot observe a change in the outside world that never entered the system, so something outside the ledger has to periodically re-fetch referenced authorities and emit a fresh observation. &lt;a href="https://dev.to/pm25coder"&gt;pm25coder&lt;/a&gt; described this as a periodic "still current" or "stale" marker, which is a clean way to put it. The important framing: the revalidation job runs outside the ledger, and its result becomes a new event the ledger preserves. The ledger never claims continuous authority between checks, only that authority was observed at particular moments.&lt;/p&gt;

&lt;p&gt;The second is retrieval. &lt;a href="https://dev.to/gde03"&gt;Giulio D'Erme&lt;/a&gt; and &lt;a href="https://dev.to/mickyarun"&gt;arun rajkumar&lt;/a&gt; converged on the point that a ledger gets read at exactly one moment, when someone is about to change the thing the reasoning was about, and that nobody goes looking for a constraint they have never hit. A well-structured record that is never surfaced is not much better than no record. The fix is to make the decision history an obligation on retrieval rather than an obligation on the reader: when a query surfaces the artifact a decision governed, the decision rides along, asked for or not.&lt;/p&gt;

&lt;p&gt;That is what I have started calling separate custody, one interface. The ledger stays independently governed, so it cannot be edited in the same operation that changes what it witnesses. But the retrieval layer reunites the artifact and its decision history when the relationship becomes relevant, so no one has to know the ledger exists to benefit from it. Both properties matter, and they pull in opposite directions, which is exactly why they belong to different layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core as principles, external as mechanisms.&lt;/strong&gt; Neither is a field in the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Worked Record
&lt;/h2&gt;

&lt;p&gt;Applying the core principles to the baseline, a fuller record looks closer to this. The optional fields from Principle 5 are included and marked, since this is the kind of consequential decision where they earn their place.&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;reasoning_ledger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;decision_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dep-2026-03-14-0922&lt;/span&gt;
  &lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deployment"&lt;/span&gt;
  &lt;span class="na"&gt;decided_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:22:00Z&lt;/span&gt;

  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                 &lt;span class="c1"&gt;# Principle 6&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INC-2291&lt;/span&gt;
    &lt;span class="na"&gt;observed_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T08:55:00Z&lt;/span&gt;

  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ADR-014&lt;/span&gt;
      &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;architecture-review&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;obtained&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                            &lt;span class="c1"&gt;# Principle 3&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adr-service&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;re-derived&lt;/span&gt;
        &lt;span class="na"&gt;retrieved_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:21:40Z&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-policy&lt;/span&gt;
      &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-team&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
      &lt;span class="na"&gt;obtained&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;policy-cache&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cached&lt;/span&gt;
        &lt;span class="na"&gt;retrieved_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:21:41Z&lt;/span&gt;
        &lt;span class="na"&gt;cache_age_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;240&lt;/span&gt;

  &lt;span class="na"&gt;policy_evaluated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                        &lt;span class="c1"&gt;# Principle 1 (reports, does not rule)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dirty-tree-guard&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;

  &lt;span class="na"&gt;alternatives_considered&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# Principle 5 (optional, stakes-dependent)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;option&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Defer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;next&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;window"&lt;/span&gt;
      &lt;span class="na"&gt;rejection_reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Incident&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exceeded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;defer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;threshold"&lt;/span&gt;
  &lt;span class="na"&gt;disconfirmed_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;unknowns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Downstream&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cache&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;warm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verified"&lt;/span&gt;

  &lt;span class="na"&gt;relationships&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                           &lt;span class="c1"&gt;# Principle 4 (two clocks)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supersedes&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dep-2026-02-02-1130&lt;/span&gt;
      &lt;span class="na"&gt;valid_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:22:00Z&lt;/span&gt;
      &lt;span class="na"&gt;asserted_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-03-14T09:22:00Z&lt;/span&gt;

  &lt;span class="na"&gt;approvals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;release-manager&lt;/span&gt;
  &lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;approved&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Field Reference
&lt;/h2&gt;

&lt;p&gt;For quick use, here is the same thing as a reference, tagged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core fields.&lt;/strong&gt; &lt;code&gt;decision_id&lt;/code&gt;, &lt;code&gt;decision&lt;/code&gt;, &lt;code&gt;decided_at&lt;/code&gt;, &lt;code&gt;trigger&lt;/code&gt;, &lt;code&gt;evidence&lt;/code&gt; (with per-item &lt;code&gt;authority&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, and an &lt;code&gt;obtained&lt;/code&gt; block recording source, method, and retrieval time), &lt;code&gt;outcome&lt;/code&gt;, and, for any relationship, both &lt;code&gt;valid_time&lt;/code&gt; and &lt;code&gt;asserted_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional fields, escalating to core with stakes.&lt;/strong&gt; &lt;code&gt;alternatives_considered&lt;/code&gt; with &lt;code&gt;rejection_reason&lt;/code&gt;, &lt;code&gt;disconfirmed_by&lt;/code&gt;, &lt;code&gt;unknowns&lt;/code&gt;, &lt;code&gt;scope_limitations&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional, context-dependent.&lt;/strong&gt; &lt;code&gt;confidence&lt;/code&gt; assessments, &lt;code&gt;tools&lt;/code&gt; used, and &lt;code&gt;policy_evaluated&lt;/code&gt; results where a boundary check ran. Useful, but not every decision needs them, and an empty one is worse than an absent one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not fields at all.&lt;/strong&gt; Enforcement decisions, revalidation jobs, and integrity guarantees. These are mechanisms that surround the ledger, not contents of the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Limit
&lt;/h2&gt;

&lt;p&gt;It is worth ending where the design genuinely runs out, because pretending otherwise is how ledgers get oversold.&lt;/p&gt;

&lt;p&gt;A perfect record can tell you exactly what the system knew and did. It cannot retroactively give the system knowledge it never acquired. If the world changed and no observation of that change ever crossed your boundary, the ledger will contain a flawless, self-consistent account of a decision that was already wrong. Revalidation narrows that gap. It does not close it. Auditability is a property of what was observed, not a guarantee that everything relevant was.&lt;/p&gt;

&lt;p&gt;That is not a reason to skip the record. It is a reason to be precise about what the record proves. It witnesses observation, not omniscience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;This piece is about what a record should contain and the principles that decide it. It has deliberately said almost nothing about whether the record can be trusted not to have been altered after the fact. That is a separate problem with its own answer, &lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt;, and it is where Part 5 goes next. Designing the record and guaranteeing its integrity are different jobs, and keeping them apart is itself one of the design principles.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;With thanks to the commenters whose contributions shaped this: &lt;a href="https://dev.to/gnomeman4201"&gt;GnomeMan4201&lt;/a&gt; on disconfirming evidence, &lt;a href="https://dev.to/pm25coder"&gt;pm25coder&lt;/a&gt; on the trigger field and authority-fetch-as-event, &lt;a href="https://dev.to/gde03"&gt;Giulio D'Erme&lt;/a&gt; on two clocks and retrieval as an obligation, &lt;a href="https://dev.to/kenielzep97"&gt;Self-Correcting Systems&lt;/a&gt; on provenance of the version, &lt;a href="https://dev.to/mickyarun"&gt;arun rajkumar&lt;/a&gt; on where the record lives, &lt;a href="https://dev.to/hannune"&gt;Tae Kim&lt;/a&gt; on evidence chains under audit, and &lt;a href="https://dev.to/kartik-nvjk"&gt;Kartik N V J K&lt;/a&gt; on rejected branches. The record is better for the argument.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Your Memory API Is Lying to Your Agent</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Thu, 20 Aug 2026 22:33:17 +0000</pubDate>
      <link>https://dev.to/kenwalger/your-memory-api-is-lying-to-your-agent-252h</link>
      <guid>https://dev.to/kenwalger/your-memory-api-is-lying-to-your-agent-252h</guid>
      <description>&lt;p&gt;&lt;em&gt;The memory store may know the truth. The interface may be throwing it away.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This piece grew out of a conversation on &lt;a href="https://dev.to/izgorodin"&gt;Edward Izgorodin&lt;/a&gt;'s post &lt;a href="https://dev.to/izgorodin/your-agent-doesnt-need-more-memory-it-needs-to-know-what-its-allowed-to-believe-22j7"&gt;Agent Memory: Everything It Remembers Has the Same Authority, and That Is the Bug&lt;/a&gt;. Several of the sharpest points below have names attached, and I have tried to attach them.&lt;/em&gt;&lt;/p&gt;





&lt;p&gt;Imagine an AI agent asks its memory system a straightforward question:&lt;/p&gt;

&lt;blockquote&gt;
  What database does the production application use?
&lt;/blockquote&gt;

&lt;p&gt;The memory API returns:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[
  {"content": "The production database is PostgreSQL.", "score": 0.94},
  {"content": "The production database is MongoDB.", "score": 0.91}
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Retrieval worked. It found two highly relevant memories, scored them, ranked them, and returned them. The agent picks PostgreSQL.&lt;/p&gt;

&lt;p&gt;The production application migrated to MongoDB four months ago.&lt;/p&gt;

&lt;p&gt;Nothing failed in retrieval. The PostgreSQL record may genuinely be more semantically similar to the query. But semantic relevance was never the question the agent needed answered. The store knew more than it returned: PostgreSQL governed from January 2025 until April 2026, when MongoDB superseded it under a newer architecture decision. Somewhere between storage and the agent, that relationship disappeared.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150313.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150313.png" alt="Diagram showing a PostgreSQL record valid from January 2025 to April 2026 under authority ADR-017, superseded by a MongoDB record valid from April 2026 to present under ADR-042. The supersession relationship is what a ranked list discards." width="798" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The API returned the records and threw away the relationship between them. That is a very different kind of memory failure, and it is the one this piece is about.&lt;/p&gt;

&lt;h2&gt;The Storage Problem Is Mostly Solved&lt;/h2&gt;

&lt;p&gt;Before going further, it is worth being honest about what is actually new here, because part of this problem was solved before agents existed.&lt;/p&gt;

&lt;p&gt;Separating when a fact was true from when the system learned it is bitemporal modeling, standardized in SQL:2011 as application-time and system-versioned tables. Edward raised this in the thread, and he is right that the database world has handled "this was true then, this is true now" for over a decade. A well-built store can close a fact's validity window instead of overwriting it, and the past stays explicable.&lt;/p&gt;

&lt;p&gt;So the interesting problem is not storage. If your store still deletes on update, fix that first, and the literature is waiting for you. The problem this piece is about starts one layer up: even when the store preserves all of it, the retrieval interface usually hands the agent a flat ranked list and throws the structure away. The store solved the problem. The API un-solves it on the way out.&lt;/p&gt;

&lt;h2&gt;A Ranked List Has Nowhere to Put an Edge&lt;/h2&gt;

&lt;p&gt;That phrase is Edward's, from the thread, and it may be the sentence that breaks the whole abstraction. Once you sit with it, the rest follows.&lt;/p&gt;

&lt;p&gt;Most AI memory interfaces inherited a familiar retrieval shape: give the system a query, get back a ranked list of relevant things. There may be metadata attached, a timestamp, a document id, a source, a confidence value. The fundamental abstraction stays the same. Memory is a bag of items, and retrieval returns the best-matching items.&lt;/p&gt;

&lt;p&gt;That works well when the problem is finding things. Agentic systems increasingly need memory to do something harder: represent what the system currently knows, what it previously knew, where that knowledge came from, whether it still governs, and how apparently contradictory records relate. A ranked list is a poor representation of that world, because the relationships between records are part of the knowledge, and a list has nowhere to put them.&lt;/p&gt;

&lt;p&gt;Consider two records: customer refunds require manager approval, and customer refunds under $100 do not. Maybe the second is a correction, because the first was entered wrong. Maybe it superseded the first, because policy changed. Maybe both are true in different jurisdictions and the first simply no longer governs this transaction. Those are not variations of one operation. They make different claims about history.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150448.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150448.png" alt="Diagram showing one record, A, related to a later record or authority in three distinct ways: superseded by B because the world changed, corrected by B because the record was wrong, and invalidated by an authority because A may still be true but no longer governs." width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the storage layer, all three can look like an update. At the audit and retrieval layers, they are fundamentally different events.&lt;/p&gt;

&lt;h2&gt;"No Longer True" Is Not "Never True," and Neither Is "No Longer Governs"&lt;/h2&gt;

&lt;p&gt;CRUD trained us to think in one verb, &lt;code&gt;UPDATE&lt;/code&gt;, but durable memory needs at least three, and the third is the one that gets missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supersession&lt;/strong&gt; says the world changed. Policy A was true, Policy B is true now, and A is not wrong, it is closed. &lt;strong&gt;Correction&lt;/strong&gt; says our record was wrong, including during the window an agent may have relied on it, so A was never true. &lt;strong&gt;Invalidation&lt;/strong&gt; is the one worth slowing down for, because it is not a truth claim at all. It is an authority claim. A record can be perfectly true and no longer govern.&lt;/p&gt;

&lt;p&gt;That distinction is the load-bearing one. A store that collapses these into a single value change can still answer "what is true now" cleanly, and will quietly fail the moment anyone asks "why did the agent approve that transaction on March 17." The answer to that question may depend on a record that is closed, or corrected, or stripped of authority, and that store no longer knows which.&lt;/p&gt;

&lt;h2&gt;Availability Is Not Usage, Even for a Schema&lt;/h2&gt;

&lt;p&gt;Here is the part that should make anyone building this check their own system before writing another feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/gde03"&gt;Giulio D'Erme&lt;/a&gt; read the original thread, then went and counted his own corpus: zero of 152 memos in his memory store, and zero of 59 documents in his docs, declared a validity window or a supersession edge. The engine could read those keys. Nothing that wrote memories ever wrote them. As he put it, &lt;cite&gt;availability is not usage, and it applies to schema as much as to tools&lt;/cite&gt;.&lt;/p&gt;

&lt;p&gt;This is the failure mode hiding behind every rich schema. You can ship the read path, document the fields, and watch a live API serve a dead feature, because the thing that writes memories, a prompt or a template or another agent, was never taught the keys. A supersession column that nothing populates is not preservation. It is a column.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/hannune"&gt;Tae Kim&lt;/a&gt; described the same shape from production trade data: the same company surfacing as different nodes depending on whether you asked before or after an acquisition, with the store silently picking one. Stamping the connections with time ranges and returning both versions helped. The part that bit later was that the agent's choice between them still vanished without a trace, which is the next problem.&lt;/p&gt;

&lt;h2&gt;Relevance Is Not Authority&lt;/h2&gt;

&lt;p&gt;The PostgreSQL example exposes the assumption underneath ranked retrieval. A similarity score answers, roughly, "how relevant is this record to the query." It does not answer "which record currently governs." Those correlate, but they are not the same. PostgreSQL might score &lt;code&gt;0.94&lt;/code&gt; because it contains the exact terminology in the query, while MongoDB scores &lt;code&gt;0.91&lt;/code&gt; because the migration decision is phrased differently. Retrieval did its job. The agent still gets the wrong answer, because &lt;code&gt;0.94 &amp;gt; 0.91&lt;/code&gt; quietly became conflict resolution, and semantic similarity never established anything about authority.&lt;/p&gt;

&lt;p&gt;This is why I have come to think of &lt;a href="https://sovereignplatform.dev/terms/memory-as-infrastructure.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=lying-api" rel="noopener noreferrer"&gt;Memory as Infrastructure&lt;/a&gt; rather than memory as a database feature. Once memory participates in consequential decisions, retrieval quality is only one property of the subsystem. Provenance, authority, lifecycle, temporal validity, and correction semantics matter too. The closest memory is not necessarily the memory that governs.&lt;/p&gt;

&lt;h2&gt;Contradiction Is Information&lt;/h2&gt;

&lt;p&gt;Memory systems often treat conflicting records as a retrieval-quality problem: delete the older one, rank the newer one higher, filter one out with metadata. Sometimes that is right. Sometimes the contradiction is the most important thing memory knows.&lt;/p&gt;

&lt;p&gt;Consider a record from Procurement saying Supplier X is approved for regulated workloads, and one from Security saying Supplier X is prohibited. Both may be inside their validity windows. No supersession may exist. The correct response is not to silently decide which wins. It is to report that the records conflict, where each came from, which authority issued each, and that resolution is required.&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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150612.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150612.png" alt="Diagram showing two records about Supplier X, one from Procurement marking it approved for regulated workloads and one from Security marking it prohibited, both flowing into a single unresolved conflict node rather than one silently winning." width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the store knows the conflict exists but the API returns two ordinary ranked hits, the disagreement disappears at exactly the moment it mattered most.&lt;/p&gt;

&lt;h2&gt;The Response Type Is Part of the Architecture&lt;/h2&gt;

&lt;p&gt;This is why the fix is harder than adding a metadata column. If memory contains relationships, the response type has to be able to carry relationships. A richer interface might conceptually return something like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "records": [
    {"id": "A", "content": "Production uses PostgreSQL."},
    {"id": "B", "content": "Production uses MongoDB."}
  ],
  "relationships": [
    {
      "type": "supersession",
      "from": "A",
      "to": "B",
      "effective_at": "2026-04-15T00:00:00Z"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The precise schema is not the point, and I am not proposing that JSON as a standard. The conceptual change is that the response is no longer a list of memories. It is a representation of a knowledge state, one that can carry contradiction, supersession, correction, invalidation, provenance, and authority as first-class content. Once those relationships affect agent behavior, they cannot stay trapped in the storage layer.&lt;/p&gt;

&lt;p&gt;Two honest problems come with that, and both surfaced in the thread and then got worse the more Edward and I pushed on them.&lt;/p&gt;

&lt;p&gt;The first is budget, and it turns out to be deeper than allocation. A ranked list is impoverished, but it is cheap, and &lt;code&gt;top_k&lt;/code&gt; is a clean way to decide what to drop. The moment a response carries facts, relationships, authority, provenance, and prior decisions together, the problem stops being ranking and becomes allocating a finite context budget across different kinds of knowledge. A lower-ranked authority edge may matter more than the next highly relevant fact, and dropping a supersession relationship can change the meaning of the records that survive.&lt;/p&gt;

&lt;p&gt;The tempting fix is to select the edges after ranking, as a post-filter on whatever &lt;code&gt;top_k&lt;/code&gt; returned. Edward's counter is the part that reshaped my thinking: to know whether a supersession edge is worth carrying, you already have to be holding the record it supersedes. Edge hydration therefore cannot be a post-filter. It has to influence which candidates are considered in the first place, which means the allocation happens before ranking rather than after it. That is a far deeper change to a retrieval stack than adding a field to a response, and it is the point at which "improve the store" stops being the fix.&lt;/p&gt;

&lt;p&gt;The second is addressing, and it needs to be more precise than "give the conflict an identity." My first instinct was to key the disagreement on the pair, A conflicts with B. Edward's refinement is better: pairs are unstable, because the moment a third record arrives, "A conflicts with B" is no longer the same object, and yesterday's decision now points at a conflict that no longer exists in that shape. Key on the subject the records argue about instead, the question, not the pair, and the decision stays addressable however many records pile up under it over time.&lt;/p&gt;

&lt;h2&gt;What Did the Agent Do Last Time?&lt;/h2&gt;

&lt;p&gt;That second problem points at a relationship that matters once agents repeatedly hit the same knowledge. Suppose yesterday's agent encountered records A and B in conflict, determined that B governed because Security had authority over regulated workloads, and acted on B. Today another agent hits the same conflict. If the system stored only A and B, today's agent resolves it from scratch. If yesterday's decision lives only in an audit log somewhere else, it exists but is unavailable at the moment it could prevent a repeat.&lt;/p&gt;

&lt;p&gt;This is where a &lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=lying-api" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt; becomes operationally interesting, and where I want to hold a line rather than blur one. I still think durable memory and the decision record deserve different custody. Knowledge can be superseded; a decision record cannot, because it has to keep saying what was believed at the time even after the belief is retracted. That separation belongs at the storage layer.&lt;/p&gt;

&lt;p&gt;It should not survive into retrieval. &lt;a href="https://dev.to/jugeni"&gt;Mike Czerwinski&lt;/a&gt; put the risk plainly in the thread: if the agent's choice between conflicting records is not logged, silent resolution just relocates from the store to the inference step, the same bug at a harder-to-find address, because now the store looks honest. Tae Kim started writing those choices back as events only because a client asked about a strange output and there was nothing to point at. Audit pressure, not architecture taste, is usually what makes the field real.&lt;/p&gt;

&lt;p&gt;So the shape I would argue for is not &lt;code&gt;memory + ledger&lt;/code&gt; presented as two things. It is separate systems of record behind one interface that can return facts, relationships, authority, and relevant prior decisions together. Separate custody, one interface, is the shortest way I have found to say it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150723.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-20-150723.png" alt="Diagram showing five separate subsystems, durable memory, reasoning ledger, provenance, authority and policy, and temporal state, all feeding a single memory and context interface that then serves the agent, illustrating that separate storage boundaries can sit behind one unified retrieval interface." width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Different subsystems may have very different storage requirements, retention policies, and security boundaries. The mistake is assuming those implementation boundaries must decide what the agent is allowed to know at retrieval time. Storage boundaries do not have to be retrieval boundaries.&lt;/p&gt;

&lt;h2&gt;The API Is Making Claims&lt;/h2&gt;

&lt;p&gt;Every interface decides what survives abstraction. A memory API that returns only content and similarity scores is implicitly telling the agent that records are independent items and ranking is the only meaningful relationship among them. That was a reasonable claim when memory meant fetching passages to stuff into a prompt. It becomes a dangerous one when memory carries policy, organizational decisions, historical state, authority, and evidence for autonomous agents.&lt;/p&gt;

&lt;p&gt;Here is what can vanish when a rich memory system is flattened into a ranked list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
  &lt;th&gt;Store knows&lt;/th&gt;
  &lt;th&gt;API returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td&gt;B superseded A&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;A was corrected by B&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;A remains true but no longer governs&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;A contradicts B&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;A and B share the same provenance&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;B governed the previous decision&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;A's authority expired&lt;/td&gt;
  &lt;td&gt;A and B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the API's perspective, nothing is wrong. From the agent's perspective, almost everything important is gone.&lt;/p&gt;

&lt;p&gt;We have spent enormous effort improving retrieval: better embeddings, hybrid search, rerankers, metadata filters, graph retrieval, larger context windows. All of it helps systems find relevant information. Finding the right records and understanding what they mean in relation to one another are different problems, and agentic systems are pushing memory hard toward the second. If the store preserves that structure but the interface discards it, improving the store will not help. The API has become the lossy boundary.&lt;/p&gt;

&lt;p&gt;A memory API that knows A was superseded by B but hands the agent &lt;code&gt;[A: 0.94, B: 0.91]&lt;/code&gt; has not merely dropped some metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has changed the meaning of the memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The thread that produced this piece has already moved the problem past where I started it. "A ranked list has nowhere to put an edge" was the right first cut, and it is a statement about the response shape. The sharper version, the one I am chasing now, is that some edges need durable identities, and something has to decide which edges are worth hydrating, before ranking rather than after. That is no longer a claim about the shape of the response. It is a claim about the shape of retrieval itself. Which is a longer conversation, and, I suspect, the next one.&lt;/p&gt;





&lt;p&gt;&lt;em&gt;With thanks to &lt;a href="https://dev.to/izgorodin"&gt;Edward Izgorodin&lt;/a&gt;, whose post started this and whose "nowhere to put an edge" framing anchors it, and to &lt;a href="https://dev.to/gde03"&gt;Giulio D'Erme&lt;/a&gt;, &lt;a href="https://dev.to/hannune"&gt;Tae Kim&lt;/a&gt;, and &lt;a href="https://dev.to/jugeni"&gt;Mike Czerwinski&lt;/a&gt;, whose thread contributions are cited above. Different directions, same wall.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Reasoning Ledger: Remembering Decisions, Not Just Data</title>
      <dc:creator>Ken W Alger</dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:03:00 +0000</pubDate>
      <link>https://dev.to/kenwalger/the-reasoning-ledger-remembering-decisions-not-just-data-56gm</link>
      <guid>https://dev.to/kenwalger/the-reasoning-ledger-remembering-decisions-not-just-data-56gm</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 4 of the Building the AI Memory Stack series&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After finishing the previous article, I looked at the repository a little differently. The specifications were still there. The Architecture Decision Records were still there. The glossary entries were still there. The project's durable memory had done exactly what it was supposed to do: preserve the knowledge that deserved to survive.&lt;/p&gt;

&lt;p&gt;But something was missing. I could see &lt;strong&gt;what&lt;/strong&gt; existed, but I couldn't always see &lt;strong&gt;why&lt;/strong&gt; it existed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory tells you what. Reasoning tells you why.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction turns out to matter.&lt;/p&gt;

&lt;h2&gt;Durable Memory Isn't the Whole Story&lt;/h2&gt;

&lt;p&gt;In the previous article, I argued that Durable Memory decides what knowledge deserves to outlive the task that created it.&lt;/p&gt;

&lt;p&gt;That remains true. But imagine opening an Architecture Decision Record six months later and asking:&lt;/p&gt;

&lt;blockquote&gt;
  Why was this decision made?
&lt;/blockquote&gt;

&lt;p&gt;The document gives you the conclusion, but it may not give you the path that produced it. Perhaps the decision came from competing specifications, several tool invocations, human review, rejected alternatives, or a policy constraint that no longer exists.&lt;/p&gt;

&lt;p&gt;The final artifact survives. The reasoning process often does not.&lt;/p&gt;

&lt;h2&gt;Another Layer in the Stack&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-17-073958.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%2Fwww.kenwalger.com%2Fblog%2Fwp-content%2Fuploads%2F2026%2F08%2Fmermaid-diagram-2026-08-17-073958.png" alt="Diagram of the AI Memory Stack highlighting the Reasoning Ledger as the layer that preserves why decisions happened. Information flows from the Reasoning Ledger to Durable Memory, Active Working Memory, the Context Window, and finally Model Inference." width="552" height="1019"&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;Layer&lt;/th&gt;
  &lt;th&gt;Primary Question&lt;/th&gt;
  &lt;th&gt;Preserves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;a href="https://sovereignplatform.dev/terms/reasoning-ledger.html" rel="noopener noreferrer"&gt;Reasoning Ledger&lt;/a&gt;&lt;/td&gt;
  &lt;td&gt;Why did this happen?&lt;/td&gt;
  &lt;td&gt;Decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;a href="https://sovereignplatform.dev/terms/durable-memory.html" rel="noopener noreferrer"&gt;Durable Memory&lt;/a&gt;&lt;/td&gt;
  &lt;td&gt;What should survive?&lt;/td&gt;
  &lt;td&gt;Knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;a href="https://sovereignplatform.dev/terms/active-working-memory.html" rel="noopener noreferrer"&gt;Active Working Memory&lt;/a&gt;&lt;/td&gt;
  &lt;td&gt;What matters now?&lt;/td&gt;
  &lt;td&gt;Working set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;Context Window&lt;/td&gt;
  &lt;td&gt;What can the model see?&lt;/td&gt;
  &lt;td&gt;Current tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;Software Already Solved Part of This&lt;/h2&gt;

&lt;p&gt;Git repositories preserve more than source code. They preserve commit history, pull requests, code reviews, issues, and discussion. Together they explain how software evolved.&lt;/p&gt;

&lt;p&gt;Imagine if Git only stored the latest version of every file. The software would still exist, but understanding it would become dramatically harder.&lt;/p&gt;

&lt;p&gt;Git doesn't exist because developers forget what their code looks like. It exists because developers eventually ask:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;strong&gt;Why did we change this?&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;p&gt;Agentic systems deserve the same architectural capability.&lt;/p&gt;

&lt;h2&gt;The Missing Layer&lt;/h2&gt;

&lt;p&gt;Most AI systems optimize retrieval, but far fewer preserve the observable decision process surrounding an inference. If someone asks months later:&lt;/p&gt;

&lt;blockquote&gt;
  Why did the system recommend this?
&lt;/blockquote&gt;

&lt;p&gt;can we answer?&lt;/p&gt;

&lt;p&gt;If the only answer is "because the model said so," then the system hasn't preserved enough information to be trustworthy. We've preserved knowledge but lost understanding.&lt;/p&gt;

&lt;h2&gt;The Reasoning Ledger&lt;/h2&gt;

&lt;p&gt;The Sovereign Systems Specification calls this architectural layer the Reasoning Ledger.&lt;/p&gt;

&lt;p&gt;It deliberately avoids recording private chain-of-thought.&lt;/p&gt;

&lt;p&gt;It records the observable architecture surrounding a decision.&lt;/p&gt;

&lt;p&gt;A ledger may capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evidence consulted&lt;/li&gt;
&lt;li&gt;Tool invocations&lt;/li&gt;
&lt;li&gt;Policy evaluations&lt;/li&gt;
&lt;li&gt;Human approvals&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;li&gt;Confidence assessments&lt;/li&gt;
&lt;li&gt;References to durable artifacts&lt;/li&gt;
&lt;li&gt;Links to &lt;a href="https://sovereignplatform.dev/terms/forensic-receipt.html" rel="noopener noreferrer"&gt;Forensic Receipts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, a single record might look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;reasoning_ledger:
  decision: "Approve deployment"
  timestamp: 2026-03-14T09:22:00Z
  evidence:
    - artifact: ADR-014
      authority: architecture-review
      version: 3
    - artifact: production-health-metrics
      observed_at: 2026-03-14T09:20:00Z
    - artifact: security-policy
      authority: security-team
      version: 7
  tools:
    - GitHub
    - CI pipeline
  approvals:
    - release manager
  outcome: approved
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that the ledger does not merely record that a security policy was consulted. It can preserve which policy, which version, and which authority governed the decision at that moment. That distinction matters because evidence can remain perfectly retrievable long after the world that made it authoritative has changed.&lt;/p&gt;

&lt;p&gt;The Reasoning Ledger is therefore a historical record, not a promise of continuing authority. It tells us what governed the decision then. Determining whether the same evidence still governs a future decision belongs elsewhere in the architecture.&lt;/p&gt;

&lt;p&gt;The goal is not to reconstruct what happened inside the model. It is to preserve the externally observable evidence, authorities, policies, tools, approvals, and outcomes that allow someone to examine the decision later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observable reasoning is architecture. Private reasoning belongs to the model.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;Memory Preserves Knowledge. Reasoning Preserves Decisions.&lt;/h2&gt;

&lt;p&gt;Memory is fundamentally a write problem, while reasoning is fundamentally an accountability problem. Memory preserves knowledge. Reasoning preserves decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both are required for trustworthy AI systems.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;Looking Ahead&lt;/h2&gt;

&lt;p&gt;A Reasoning Ledger explains the observable path that produced a decision.&lt;/p&gt;

&lt;p&gt;But how do we know those records themselves have not been altered?&lt;/p&gt;

&lt;p&gt;That is where &lt;a href="https://sovereignplatform.dev/terms/write-side-custody.html" rel="noopener noreferrer"&gt;Write-Side Custody&lt;/a&gt; begins, and where Part 5 will take us.&lt;/p&gt;

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