<?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: dormitivegit</title>
    <description>The latest articles on DEV Community by dormitivegit (@dormitivegit).</description>
    <link>https://dev.to/dormitivegit</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%2F4075575%2Fc99b2310-da10-4de6-86dd-27f9984c0a03.jpg</url>
      <title>DEV Community: dormitivegit</title>
      <link>https://dev.to/dormitivegit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dormitivegit"/>
    <language>en</language>
    <item>
      <title>Probabilistic agents need deterministic acceptance boundaries</title>
      <dc:creator>dormitivegit</dc:creator>
      <pubDate>Thu, 13 Aug 2026 05:56:17 +0000</pubDate>
      <link>https://dev.to/dormitivegit/probabilistic-agents-need-deterministic-acceptance-boundaries-ae5</link>
      <guid>https://dev.to/dormitivegit/probabilistic-agents-need-deterministic-acceptance-boundaries-ae5</guid>
      <description>&lt;p&gt;I have been building with coding agents daily for a while now. They are good. The&lt;br&gt;
problem I keep running into is not that they write bad code — it is that they&lt;br&gt;
change things faster than I can produce evidence of what changed relative to what&lt;br&gt;
I had already accepted.&lt;/p&gt;

&lt;p&gt;This is a different problem from correctness, and I think it is being&lt;br&gt;
under-discussed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tests answer a different question
&lt;/h2&gt;

&lt;p&gt;When an agent hands back a change, the reflex is to run the test suite. If it&lt;br&gt;
passes, ship it.&lt;/p&gt;

&lt;p&gt;Tests answer: &lt;em&gt;does the code do what the tests assert?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They do not answer: &lt;em&gt;what moved, relative to the state I reviewed and accepted?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those come apart in ordinary situations. An agent refactors three files while&lt;br&gt;
fixing one. A generated migration rewrites a fixture you were treating as a&lt;br&gt;
frozen reference. A tool run mutates a config file nobody was watching. Every&lt;br&gt;
test still passes, because no test was ever written about the thing that moved.&lt;br&gt;
You find out later, or you do not find out.&lt;/p&gt;

&lt;p&gt;The gap widens as agents get more capable. A weak agent touches one function. A&lt;br&gt;
strong agent touches whatever it decides is in scope — and "whatever it decides"&lt;br&gt;
is precisely the part you cannot pin down in advance, because that&lt;br&gt;
non-determinism is where the value comes from.&lt;/p&gt;
&lt;h2&gt;
  
  
  The verification layer should be orthogonal to the agent
&lt;/h2&gt;

&lt;p&gt;The tempting fix is to make the agent verify itself: ask it to summarize its own&lt;br&gt;
changes, or run a second agent as reviewer.&lt;/p&gt;

&lt;p&gt;I do not think this works as an acceptance boundary, for a structural reason:&lt;br&gt;
both the change and the verification then come from the same probabilistic&lt;br&gt;
process. When they disagree you learn something. When they agree you have learned&lt;br&gt;
almost nothing, because agreement is exactly what a shared failure mode produces.&lt;/p&gt;

&lt;p&gt;What I want is a verification layer with the opposite properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic.&lt;/strong&gt; Same bounded input, same finding, every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model-neutral.&lt;/strong&gt; Swapping the agent should not change what acceptance means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; No network call in the verification path — a verification step
that can fail for network reasons is not a boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine-consumable.&lt;/strong&gt; A stable exit code and a structured finding, not prose
a human has to interpret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that none of this makes the agent less useful. It stays as flexible and&lt;br&gt;
probabilistic as you like. The boundary is drawn at &lt;em&gt;acceptance&lt;/em&gt;, not at&lt;br&gt;
generation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Freeze, change, verify
&lt;/h2&gt;

&lt;p&gt;The concrete mechanism I settled on is boring, which I take as a good sign.&lt;/p&gt;

&lt;p&gt;Freeze a bounded set of sources into a hash-addressed manifest. Let the agent do&lt;br&gt;
whatever it does. Verify against the manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance corpus freeze ./src &lt;span class="nt"&gt;--manifest&lt;/span&gt; baseline.jsonl
&lt;span class="go"&gt;result=PASS
exit_code=0
manifest=baseline.jsonl
write_disposition=CREATED
source_record_count=1

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;... agent runs, edits a file ...
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance corpus verify baseline.jsonl
&lt;span class="go"&gt;result=HOLD
exit_code=4
counts={"changed": 1, "match": 0, "missing": 0, "self_ingested": 0, "type_changed": 0}
FINDING {"code":"CI03_SOURCE_CHANGED","severity":"ERROR","message":"filesystem
source bytes changed","path":".../src/greeting.py","location":"source_record", ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Real output, with a few header lines — module id, rule-set version, profile —&lt;br&gt;
elided for width, and the absolute path shortened.)&lt;/p&gt;

&lt;p&gt;The two things that matter here are the ones that look least interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The exit code is part of the contract.&lt;/strong&gt; &lt;code&gt;4&lt;/code&gt; means an integrity finding&lt;br&gt;
specifically, not "something went wrong." A CI job can branch on it without&lt;br&gt;
parsing anything. Once exit codes are contractual they have to be versioned and&lt;br&gt;
tested like any other public interface, which is a constraint worth accepting&lt;br&gt;
early rather than discovering later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Bounded" is doing real work.&lt;/strong&gt; The manifest covers explicitly supplied roots.&lt;br&gt;
Not the whole machine, not an implicit working directory. An unbounded integrity&lt;br&gt;
check is one that eventually gets disabled because it is too noisy, and a&lt;br&gt;
disabled check is worse than no check because you still believe it is running.&lt;/p&gt;

&lt;p&gt;A fair objection at this point: for a clean Git repository, &lt;code&gt;git diff&lt;/code&gt; covers a&lt;br&gt;
substantial part of the ordinary file-change case. It genuinely does. The cases&lt;br&gt;
where it differs are when the bounded evidence set is not identical to the&lt;br&gt;
repository — several explicit roots at once, deliberately untracked files,&lt;br&gt;
members inside ZIP archives (read and hashed individually), symlink identity&lt;br&gt;
(the link target path itself is recorded and hashed, which is a different fact&lt;br&gt;
from the target file's contents), and the machine-consumable exit semantics&lt;br&gt;
above. If your evidence set is exactly "tracked files in one repo," use&lt;br&gt;
&lt;code&gt;git diff&lt;/code&gt;. It is right there and it is excellent.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authorization must be out-of-band — a lesson I learned by getting it wrong
&lt;/h2&gt;

&lt;p&gt;This is the part I would most like to pass on, because I got it wrong in public&lt;br&gt;
and the failure mode generalizes well beyond my own project.&lt;/p&gt;

&lt;p&gt;Suppose your verification layer checks that a mutation was authorized: the record&lt;br&gt;
claims a decision authorized it, and the tool confirms the decision exists,&lt;br&gt;
covers the same object, and was made by the right authority.&lt;/p&gt;

&lt;p&gt;The question is: &lt;strong&gt;where does "the right authority" come from?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first public implementation had the authority identity hard-coded as a literal&lt;br&gt;
string in the validation logic. It worked perfectly — for exactly one person.&lt;br&gt;
Every other user constructing a fully well-formed record got a HOLD, because&lt;br&gt;
their authority identity was not the one baked into the source. Three of the&lt;br&gt;
tool's modules were structurally unusable by anyone but me.&lt;/p&gt;

&lt;p&gt;I did not notice, and my test suite could not have told me, because all my&lt;br&gt;
fixtures used the same identity as the code. It surfaced during an independent&lt;br&gt;
adversarial review of the repository, and it surfaced only because the reviewer&lt;br&gt;
built two byte-identical inputs differing in exactly one field and observed that&lt;br&gt;
one passed and one did not. Reading the source had not found it; a green suite&lt;br&gt;
had not found it. &lt;strong&gt;A single-tenant constant hiding in validation logic is&lt;br&gt;
invisible from inside your own tests, because your fixtures share the constant.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious repair is to let the input document declare its own authority. This&lt;br&gt;
is worse. If the record under verification names the authority that will be&lt;br&gt;
accepted, then a record can authorize itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authority_identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WHOEVER_I_SAY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decisions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WHOEVER_I_SAY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUTHORIZED"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator dutifully confirms the two agree, and the check has become&lt;br&gt;
decorative. This is the same shape as a certificate that vouches for its own&lt;br&gt;
issuer.&lt;/p&gt;

&lt;p&gt;The repair that actually holds is to take the expected authority &lt;strong&gt;out of band&lt;/strong&gt; —&lt;br&gt;
supplied by the caller, at the call site, never readable from the artifact being&lt;br&gt;
checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json &lt;span class="nt"&gt;--authority-id&lt;/span&gt; PROJECT_AUTHORITY   → PASS, &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json &lt;span class="nt"&gt;--authority-id&lt;/span&gt; SOMEONE_ELSE        → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json                                    → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="go"&gt;                                                                  (fail-closed)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, importantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pack declares its own authority_identity, no &lt;span class="nt"&gt;--authority-id&lt;/span&gt; given
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check self-declaring-pack.json                     → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two lines are the ones worth arguing about. Missing expected authority&lt;br&gt;
is a HOLD, not a pass-through — an authorization check with no expected authority&lt;br&gt;
has nothing to check against, and defaulting to permissive is how these things&lt;br&gt;
quietly stop working. And a self-declared authority never overrides the&lt;br&gt;
out-of-band value, even when it happens to agree with it.&lt;/p&gt;

&lt;p&gt;The transferable lesson is narrow and worth stating plainly: &lt;strong&gt;trust anchors do&lt;br&gt;
not belong inside the artifact being verified.&lt;/strong&gt; The second-order version is the&lt;br&gt;
one that nearly caught me — the naive fix for a coupling problem introduced a&lt;br&gt;
self-authorization hole, and it looked like a clean generalization while doing&lt;br&gt;
it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The layer should refuse to make the decision
&lt;/h2&gt;

&lt;p&gt;The last design constraint is the one people push back on most, so I will state&lt;br&gt;
it plainly: this kind of tool should not decide whether to accept a change.&lt;/p&gt;

&lt;p&gt;Concretely, in mine, risk classification returns a tier &lt;em&gt;and&lt;/em&gt; an explicit field&lt;br&gt;
saying the classification is not an authorization. Handoff validation reports&lt;br&gt;
structural observations &lt;em&gt;and&lt;/em&gt; explicitly reports that receiver readiness was not&lt;br&gt;
machine-determined. Those fields are not decoration; they exist so that no&lt;br&gt;
downstream automation can quietly read a PASS as a go-ahead.&lt;/p&gt;

&lt;p&gt;The reason is not modesty about what software can do. It is that the moment a&lt;br&gt;
deterministic checker is treated as an approval authority, people start shaping&lt;br&gt;
inputs to satisfy it, and you have rebuilt the thing you were trying to avoid —&lt;br&gt;
a probabilistic process optimizing against a proxy. Keeping the tool&lt;br&gt;
descriptive, and keeping acceptance with a person, is what preserves the&lt;br&gt;
boundary's meaning.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where this leaves things
&lt;/h2&gt;

&lt;p&gt;I do not think "assurance for AI-assisted engineering" is a solved problem, or&lt;br&gt;
that a manifest checker is the whole answer. What I am fairly confident about is&lt;br&gt;
the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probabilistic generation  →  deterministic verification  →  human acceptance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with each stage refusing to do the next one's job. Agents stay flexible.&lt;br&gt;
Verification stays reproducible and inspectable. Acceptance stays with someone&lt;br&gt;
accountable.&lt;/p&gt;

&lt;p&gt;It is explicitly &lt;strong&gt;not&lt;/strong&gt; a replacement for Git, for tests, for CI, or for human&lt;br&gt;
review. It sits beside all four.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/dormitivegit/fable5-assurance-toolkit" rel="noopener noreferrer"&gt;FABLE5&lt;/a&gt; as one&lt;br&gt;
implementation of this shape — a local CLI, Python 3.11+ standard library only,&lt;br&gt;
no network calls, no daemon, no model invocation, Apache-2.0. It is early: a&lt;br&gt;
0.3.0 prerelease with 276 tests and CI across Python 3.11–3.14, maintained by&lt;br&gt;
one person. There is a self-contained runnable example that walks the whole&lt;br&gt;
freeze → change → detect → re-freeze cycle in a disposable temp directory in&lt;br&gt;
about two seconds.&lt;/p&gt;

&lt;p&gt;I would rather have the architecture argued with than the tool adopted. If you&lt;br&gt;
think the acceptance boundary belongs somewhere else, or that this is a problem&lt;br&gt;
existing CI already handles, I would genuinely like to hear it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>testing</category>
      <category>aiagents</category>
    </item>
  </channel>
</rss>
