<?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: Haley</title>
    <description>The latest articles on DEV Community by Haley (@haaaaaley).</description>
    <link>https://dev.to/haaaaaley</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%2F4022687%2F703ed06f-0209-4f1f-9ffb-c60d641ad45b.jpg</url>
      <title>DEV Community: Haley</title>
      <link>https://dev.to/haaaaaley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haaaaaley"/>
    <language>en</language>
    <item>
      <title>Show Consent Scope Before an AI Moderator Follows Up</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:26:16 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-consent-scope-before-an-ai-moderator-follows-up-27ai</link>
      <guid>https://dev.to/haaaaaley/show-consent-scope-before-an-ai-moderator-follows-up-27ai</guid>
      <description>&lt;p&gt;I almost clicked approve on the interview packet. The script looked tidy. The calendar hold was already sent. Then I asked a quieter question. What had the participant actually agreed to discuss?&lt;/p&gt;

&lt;p&gt;The research lead owned that click. A free-model moderator would run the session. One extra follow-up could leave the consent fence. The only clean reverse sat before the first unscripted probe, not after the transcript landed in a shared drive.&lt;/p&gt;

&lt;p&gt;That is the decision I now freeze. Not the insight readout. Not the highlight reel. The moment a model wants one more question.&lt;/p&gt;

&lt;p&gt;Why this moment, and not later? Because later is theater. You cannot un-ask a probe. You can only hide it, which is worse. Hidden probes still shape the quotes you trust.&lt;/p&gt;

&lt;p&gt;Think of consent as a fence around a yard. The scripted questions live inside. Follow-ups are the dog that sprints the perimeter. If you never mark the fence, the dog decides the yard.&lt;/p&gt;

&lt;p&gt;This walkthrough is a from-zero packet. I treat it as a proposed research ops tutorial, not a completed study. I do not present sample chats as findings. I do not invent participant quotes. The artifact is the consent card, the probe ceiling, and a local checker you can run before anyone joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Name the owner and the reverse
&lt;/h2&gt;

&lt;p&gt;I start a folder before I touch a model. The first file is not a prompt. It is a decision stub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; study/&lt;span class="o"&gt;{&lt;/span&gt;consent,probes,runs,handback&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/decision.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
decision: approve_ai_moderated_interview
owner: research_lead
consequence: off_protocol_probe_enters_evidence
reverse_point: before_first_unscripted_follow_up
status: blocked_until_scope_verified
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring on purpose. Does the owner field name a human? Does reverse_point sit before speech, not after coding? If either answer is no, I stop. A model cannot be the owner. A transcript cannot be the reverse.&lt;/p&gt;

&lt;p&gt;Who gets hurt if we skip this? The participant, first. Then the study. Then the product claim that will quote a question nobody agreed to hear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Write the consent scope as fields, not vibes
&lt;/h2&gt;

&lt;p&gt;I refuse paragraphs of “we may ask related topics.” Related to what? Related is how a model wanders. I write fields a checker can fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/scope.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
study_id: checkout-recovery-2026-09
consent_topics:
  - recovering a failed payment
  - what the error message meant
  - whether they tried again the same day
out_of_scope:
  - household income
  - other household members
  - medical or disability detail
  - employer names
max_followups_per_topic: 1
recording: audio_only
identifiable_quotes: prohibited_until_redaction_review
participant_can_stop: say_stop_or_leave_call
hand_back_to: human_moderator
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a field count, not a vibe check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import yaml
from pathlib import Path
scope = yaml.safe_load(Path("study/consent/scope.yaml").read_text())
required = ["consent_topics","out_of_scope","max_followups_per_topic","hand_back_to"]
missing = [k for k in required if not scope.get(k)]
assert not missing, missing
assert scope["max_followups_per_topic"] == 1
assert scope["hand_back_to"] != "model"
print("scope_ok")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that print never appears, there is no session. Would a longer policy PDF help here? Usually it only adds noise. The missing evidence is the topic list the participant saw, not a legal novel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Separate evidence from design hypotheses
&lt;/h2&gt;

&lt;p&gt;I keep two files on purpose. Mixing them is how a probe becomes a leading question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/consent/evidence.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
evidence:
  - field: last_shown_error_copy
    status: unknown_until_session
  - field: participant_restated_consent_topics
    status: unknown_until_session
hypotheses:
  - users blame themselves for gateway timeouts
  - retry copy feels like a threat
rule: hypotheses_may_not_become_probes
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hypothesis file is a warning label. It is not a question bank. If a designer wants to “just check” self-blame, that check waits for a human moderator after hand-back. Why? Because a free model will turn a hypothesis into a why-did-you question. That why is not on the fence.&lt;/p&gt;

&lt;p&gt;Verification asks one rude question. Can I grep a hypothesis noun in the probe file? If yes, I delete the probe. I do not soften it. Softening is how leading questions survive review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Cap the probes before the model sees the packet
&lt;/h2&gt;

&lt;p&gt;I write every allowed follow-up in advance. Improvisation is the product risk, not the feature. The model may reorder silence. It may not invent a third question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/probes/allowed.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
scripted:
  - What did you think the payment error was asking you to do?
  - Did you try again that same day?
followups_allowed:
  - Can you say that last part in your own words?
forbidden_stems:
  - why did you
  - how much
  - does anyone else
  - are you disabled
  - where do you work
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the original checker. It is small. That is the point. A research gate should fail closed without a dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/probes/check_probes.py &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
import re, sys, yaml

scope = yaml.safe_load(Path("study/consent/scope.yaml").read_text())
probes = yaml.safe_load(Path("study/probes/allowed.yaml").read_text())
hyp = yaml.safe_load(Path("study/consent/evidence.yaml").read_text())

errors = []
if len(probes.get("followups_allowed", [])) &amp;gt; scope["max_followups_per_topic"]:
    errors.append("followup_ceiling_exceeded")

blob = " ".join(probes.get("scripted", []) + probes.get("followups_allowed", [])).lower()
for stem in probes.get("forbidden_stems", []):
    if stem.lower() in blob:
        errors.append(f"forbidden_stem:{stem}")

for item in hyp.get("hypotheses", []):
    key = re.sub(r"[^a-z]+", " ", item.lower())
    for token in key.split():
        if len(token) &amp;gt; 5 and token in blob:
            errors.append(f"hypothesis_leaked:{token}")
            break

if errors:
    print("FAIL")
    print("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;".join(errors))
    sys.exit(1)
print("probes_ok")
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 study/probes/check_probes.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this exits nonzero, the packet is not shy. It is invalid. Do we need model-quality scores before this gate? No. Scores would only add noise. The missing evidence is still the fence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Dry-run on a free lane, never on a person
&lt;/h2&gt;

&lt;p&gt;I needed a staging lane that was not a live participant. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I used MonkeyCode's free model access and free server option as that staging lane for the moderator packet. I still ran the local checker first. A free lane does not bless a leaky probe.&lt;/p&gt;

&lt;p&gt;The dry-run rule is simple. The model sees scope.yaml and allowed.yaml only. It never sees hypotheses. It never sees real names. The output is a rehearsal transcript, labeled rehearsal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/runs/dry_run_prompt.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
You are a research moderator in rehearsal, not a live session.
Ask only scripted questions from allowed.yaml.
You may use at most one follow-up from followups_allowed.
If the user mentions an out_of_scope topic, say:
"I will hand this back to a human moderator."
Then stop.
Do not invent questions.
Do not treat this chat as evidence.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification after the dry run is a grep, not a feeling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
text = Path("study/runs/rehearsal.txt").read_text().lower() if Path("study/runs/rehearsal.txt").exists() else ""
flags = []
for stem in ["why did you", "how much", "where do you work"]:
    if stem in text:
        flags.append(stem)
print("dry_run_flags:", flags or "none")
assert not flags, flags
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No rehearsal file yet? Then there is nothing to approve. An empty dry run is a stop, not a pass. Would a longer chat log make me safer? Only if I am hunting stems. Extra witty turns are noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Accessibility review of the consent moment
&lt;/h2&gt;

&lt;p&gt;The fence fails if the participant cannot perceive it. I review the consent screen as a pattern, not as a component library. Can a screen-reader user hear the topic list as a list of topics, not a blob? Can they get the stop phrase before the call starts? Is the language short enough to reread without a timer?&lt;/p&gt;

&lt;p&gt;I keep a review card next to the YAML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONSENT PATTERN REVIEW
Decision owner: research_lead
What the participant must hear: the three consent_topics
Stop control: say stop or leave the call, equally valid
Time pressure: none; start is participant-started
Reading load: topics as separate items, not one paragraph
Audio alternative: same topics spoken, then confirmed
Failure: if they cannot restate one topic, do not start the model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a restatement test. In rehearsal I ask a colleague to say the topics back. If they invent a fourth topic, the copy failed. If they miss an out-of-scope warning, the copy failed. I do not “clarify live” with the model. Clarifying live is a new probe.&lt;/p&gt;

&lt;p&gt;What if the consent copy is beautiful and still silent on recording? Then it is not consent. Beauty is noise. The missing evidence is whether recording was named.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Hand-back and recovery before any quote is coded
&lt;/h2&gt;

&lt;p&gt;When a probe hits the fence, the session is not “mostly fine.” The turn is contaminated. I write the hand-back packet before the call, because mid-call invention is how teams keep going.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; study/handback/card.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
trigger:
  - out_of_scope_topic_mentioned
  - model_asks_unlisted_question
  - participant_says_stop
  - participant_cannot_restate_consent
action:
  - stop_model_speech
  - mark_turn_contaminated
  - do_not_code_quote
  - human_moderator_joins_or_ends
record_kept:
  - the unasked follow-up that was refused
  - the reason for refusal
reverse: discard_contaminated_turn_not_the_whole_day_by_default
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I keep refused follow-ups in the record. Why keep a question nobody asked? Because the discarded probe shows the fence worked. If we only store answers, the log pretends the model never reached. That pretence is how later readers think the protocol was looser than it was.&lt;/p&gt;

&lt;p&gt;Verification is a file, not a standup story.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; study/handback/card.yaml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;handback_ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What should stop approval, and what is noise
&lt;/h2&gt;

&lt;p&gt;Missing evidence that must stop me: no named human owner, no topic list the participant will hear, no probe ceiling, no forbidden stems, no restatement check, no hand-back owner, no dry-run grep. Extra information that only adds noise: model brand chat, elo scores, a second color pass on the consent button, a longer empathy paragraph in the script, a promise that the model is “careful.”&lt;/p&gt;

&lt;p&gt;Careful is not a control. A ceiling is a control.&lt;/p&gt;

&lt;p&gt;This method will not fit every team. Do not use it for clinical, legal, or investigative interviews that need a licensed human from the first word. Do not use it if you cannot name a research lead who will discard a turn. Do not use a free-model rehearsal as if it were a participant. Do not ship product copy from a dry run. The free lane is a fence rehearsal. It is not a person.&lt;/p&gt;

&lt;p&gt;I still catch myself wanting one clever follow-up. That urge is the tell. The model is outgrowing the test when the test is “did the interview happen.” The test I want is narrower. Did every spoken probe stay inside the consent the participant could restate?&lt;/p&gt;

&lt;p&gt;If you also stage packets before a live hour, a free server lane can hold the rehearsal files. The consent card remains the product. The model remains the dog at the fence.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Coverage Gaps Before Approving an Agent Study Script</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sat, 19 Sep 2026 08:48:52 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-coverage-gaps-before-approving-an-agent-study-script-2fol</link>
      <guid>https://dev.to/haaaaaley/show-coverage-gaps-before-approving-an-agent-study-script-2fol</guid>
      <description>&lt;p&gt;The Slack ping landed at 4:47 p.m. A final study script sat waiting in Slack. A free model had drafted every task overnight.&lt;/p&gt;

&lt;p&gt;The first session was already booked for Monday. I almost typed a cheerful looks good. Did anyone freeze the live agent surface first?&lt;/p&gt;

&lt;p&gt;I own this research as the product designer. A false usability claim would follow a lazy yes. The only clean reverse point sits before we recruit.&lt;/p&gt;

&lt;p&gt;The agent gained a calendar-write tool yesterday afternoon. The script still only tested search and summary. We would watch people succeed at last week's product.&lt;/p&gt;

&lt;p&gt;Then we would ship a write path nobody saw fail. That is not a study at all. That is a costume on a changed system.&lt;/p&gt;

&lt;p&gt;This walkthrough is a proposed method only. I am not reporting a lab sample here. Treat every command as a template for your inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision, named plainly
&lt;/h2&gt;

&lt;p&gt;Someone must approve that script today. That someone is you or me. The missing evidence is coverage, not charm.&lt;/p&gt;

&lt;p&gt;Which live tools have zero tasks attached? Which failure states have zero recovery beats? Which consent beats never appear in the packet?&lt;/p&gt;

&lt;p&gt;If those gaps stay hidden, approval is theater. I keep one question on a sticky note. Which missing evidence should stop this study?&lt;/p&gt;

&lt;p&gt;Extra color on tone of voice is noise. An untested write tool is not noise. Show the gaps before you ask for approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0. Set up a local coverage desk
&lt;/h2&gt;

&lt;p&gt;I start on a clean directory, nothing fancy. You need Python, a shell, and PyYAML. This is research ops, not a frontend build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; agent-study-coverage/&lt;span class="o"&gt;{&lt;/span&gt;inventory,script,matrix,cards&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agent-study-coverage
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import yaml; print('yaml-ok')"&lt;/span&gt;
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; inventory/frozen-at.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring on purpose here. You must see &lt;code&gt;yaml-ok&lt;/code&gt; print to stdout. You must see a UTC timestamp inside &lt;code&gt;inventory/frozen-at.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If either check fails, stop this tutorial. You cannot freeze a surface without a desk. Do not jump into the model-written script yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1. Freeze the live tool inventory
&lt;/h2&gt;

&lt;p&gt;Do not start inside the pretty script. Start with production truth, written down. Who actually knows which tools are live today?&lt;/p&gt;

&lt;p&gt;Now write the live tools by hand. Do not let a model invent them. A chatty paragraph will hide a missing tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; inventory/live-tools.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
surface: calendar-agent
source: production-config-copy
tools:
  - id: search_events
    side_effect: none
    reversible: true
  - id: summarize_day
    side_effect: none
    reversible: true
  - id: create_event
    side_effect: writes_calendar
    reversible: false_until_undo
  - id: send_invite
    side_effect: emails_other_people
    reversible: false
consent_beats:
  - calendar_write
  - outbound_email
failure_states:
  - tool_timeout
  - partial_write
  - invite_already_sent
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run a tiny check so the freeze is real.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import yaml
d = yaml.safe_load(open("inventory/live-tools.yaml"))
assert d["tools"], "no tools frozen"
assert d["consent_beats"], "no consent beats frozen"
print("frozen tools:", len(d["tools"]))
print("consent beats:", len(d["consent_beats"]))
print("failure states:", len(d["failure_states"]))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that assert fires, you stop immediately. You have no study until tools are frozen. Why yaml instead of a slide deck?&lt;/p&gt;

&lt;p&gt;Because ids do not flatter anyone in the room. A missing &lt;code&gt;send_invite&lt;/code&gt; row stays painfully visible. A paragraph about the new calendar stuff is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2. Extract tasks from the drafted script
&lt;/h2&gt;

&lt;p&gt;Drop the model-written script into &lt;code&gt;script/draft.md&lt;/code&gt;. Keep the original wording untouched for now, always. Do not tidy the prose before this extraction.&lt;/p&gt;

&lt;p&gt;I use a small extractor that stays deliberately dumb. Would you let a student grade their own exam? The drafted script does not get to score itself.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;When I need a sandbox for this extractor, I use MonkeyCode. Free model access and a free server option are enough. I still paste the live inventory in by hand.&lt;/p&gt;

&lt;p&gt;The model never owns the freeze file. That boundary is the whole method. Now run the extractor against a stub draft.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# extract_tasks.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script/draft.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&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;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(task|prompt|ask the participant)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script/tasks.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extracted &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; task lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; script/draft.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Task: Search next week's events and read them back.
Prompt: Ask the participant to summarize Tuesday.
Warm-up: explore freely around the home screen.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 extract_tasks.py
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; script/tasks.json
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json; print(len(json.load(open('script/tasks.json'))))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a headcount here, not a feeling. You need a task for each high-side-effect tool. Zero matching lines means you do not recruit.&lt;/p&gt;

&lt;p&gt;Look at that stub and ask the coverage question. Does &lt;code&gt;create_event&lt;/code&gt; appear even once? Does &lt;code&gt;send_invite&lt;/code&gt; appear even once?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3. Build the coverage matrix
&lt;/h2&gt;

&lt;p&gt;This matrix is the artifact, not a vibe. A written matrix makes each coverage gap undeniable. People argue with stories; they rarely argue with empty cells.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# coverage.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inventory/live-tools.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script/tasks.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;gaps&lt;/span&gt; &lt;span class="o"&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;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blob&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;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_invite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;side_effect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;side_effect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reversible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reversible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;covered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matrix/coverage.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GAPS:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaps&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;gaps&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gaps&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 coverage.py&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;matrix/coverage.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 1 means you stop approval. An exit 0 only means task language exists. It does not mean those tasks are any good.&lt;/p&gt;

&lt;p&gt;I paste the csv into the review thread every time. The empty cells do the uncomfortable talking now. Do we still want Monday's sessions after this?&lt;/p&gt;

&lt;p&gt;Here is the user flow I actually walk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Moderator packet arrives
        |
        v
Designer opens frozen inventory
        |
        v
Extractor lists task lines
        |
        v
Matrix marks covered vs gap
        |
   gap? --yes--&amp;gt; STOP, rewrite tasks, do not recruit
        |
       no
        |
        v
Consent beats present in the packet?
        |
   no ---&amp;gt; STOP
        |
       yes
        |
        v
A11y session rehearsal
        |
   fail -&amp;gt; STOP
        |
       pass
        |
        v
Approval card with reversibility window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the stop boxes along that path. Approval is a gated walk, not a mood. Where would you reverse if the mail already went out?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4. Write stop conditions into the card
&lt;/h2&gt;

&lt;p&gt;A gap without a stop phrase still recruits. Someone will say we will watch for it. That sentence should never book a person, ever.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cards/approval.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
decision: approve_agent_study_script
owner: research_designer
reversible_until: recruit_mail_unsent
evidence:
  inventory_frozen: true
  coverage_csv: matrix/coverage.csv
  untested_tools: []
  untested_consent: []
  untested_failures: []
stop_if:
  - any_write_tool_has_zero_tasks
  - outbound_email_lacks_consent_beat
  - failure_state_has_no_recovery_line
  - keyboard_only_path_unrehearsed
hypotheses_not_evidence:
  - the model writes realistic tasks
  - five users will mention the new tool anyway
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fill &lt;code&gt;untested_tools&lt;/code&gt; from the csv by hand first. Then let a script refuse a green card. A green card with gaps is a ritual bug.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import csv, yaml, sys
gaps = [r["tool"] for r in csv.DictReader(open("matrix/coverage.csv")) if r["covered"] == "False"]
card = yaml.safe_load(open("cards/approval.yaml"))
card["evidence"]["untested_tools"] = gaps
open("cards/approval.yaml", "w").write(yaml.safe_dump(card, sort_keys=False))
print("untested_tools:", gaps)
if gaps:
    sys.exit("STOP: coverage gaps remain")
print("card green for tools only")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process must stay red on the stub draft. &lt;code&gt;create_event&lt;/code&gt; and &lt;code&gt;send_invite&lt;/code&gt; should appear as gaps. If the card prints green, your checker is lying.&lt;/p&gt;

&lt;p&gt;I separate evidence from hypotheses on purpose here. Users will stumble onto the write tool remains a hope. Hope is not a task line in the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5. Rehearse the session as an interface
&lt;/h2&gt;

&lt;p&gt;The participant is not a camera for our demo. The session itself is an interface with controls. What happens when those controls fail a keyboard?&lt;/p&gt;

&lt;p&gt;I rehearse four beats before I approve recruitment. Can a keyboard-only participant confirm &lt;code&gt;create_event&lt;/code&gt; today? Can a screen reader user hear draft versus sent?&lt;/p&gt;

&lt;p&gt;Can the moderator pause the agent without a mouse-only toast? Is there spoken recovery after a partial write? Write the rehearsal down, not a slogan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cards/a11y-rehearsal.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# Session accessibility rehearsal
Keyboard-only confirm of create_event: PASS_OR_FAIL
Name and state of send_invite, draft vs sent: PASS_OR_FAIL
Spoken stop phrase that halts the agent: PASS_OR_FAIL
Recovery copy after partial_write: PASS_OR_FAIL
Stop rule: any FAIL blocks recruitment.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"PASS_OR_FAIL"&lt;/span&gt; cards/a11y-rehearsal.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STOP: rehearsal still blank"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"rehearsal recorded"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That grep must fail after you rehearse. A blank rehearsal card is not inclusive design. Blank rows are postponement wearing an accessibility heading.&lt;/p&gt;

&lt;p&gt;If the agent speaks over the screen reader, that is coverage too. The study would measure panic, not the write tool. I will not learn that on participant one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6. Hand back when a session still hits a gap
&lt;/h2&gt;

&lt;p&gt;Even a green matrix can lie to you later. A participant will wander into yesterday's new tool. Does your moderator have a hand-back line ready?&lt;/p&gt;

&lt;p&gt;I table-read this out loud with them. If they paraphrase it into just keep going, we failed. The record has to keep the discarded path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF the participant reaches an untested tool
THEN moderator says:
  I am pausing the agent. That path is out of scope today.
THEN operator disables the tool for the rest of the session
THEN the record stores tool_id, timestamp, and side-effect state
THEN recovery:
  undo if reversible
  notify the other person if an invite already left
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a spoken rehearsal, not a wiki page. Ask the moderator to say the pause line twice. Ask what they do if &lt;code&gt;send_invite&lt;/code&gt; already fired.&lt;/p&gt;

&lt;p&gt;Keep discarded paths in the concrete session record. A paused tool is evidence for the readout. It is not a mess to hide from stakeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not, and who should skip it
&lt;/h2&gt;

&lt;p&gt;This is not a model bake-off of any kind. I am not ranking free models in this walkthrough. I am gating a human decision with missing evidence.&lt;/p&gt;

&lt;p&gt;Do not use this workflow without a person who can freeze inventory. Do not use it for marketing testimonials dressed as research. Do not use it when the agent can spend money you cannot reverse.&lt;/p&gt;

&lt;p&gt;Do not treat it as legal review of consent copy. The free sandbox does not freeze production for you. It does not attend the session or own the stop.&lt;/p&gt;

&lt;p&gt;If your agent has no tool boundary, pick another method. This matrix needs ids, side effects, and reversibility. Without those fields, you are scoring prose again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sticky note, again
&lt;/h2&gt;

&lt;p&gt;Which missing evidence should stop approval on your team? For me, an untested write tool stops the study. A missing consent beat for outbound email also stops it.&lt;/p&gt;

&lt;p&gt;A keyboard-only fail on confirm stops recruitment too. Which extra information would only add noise here? Another adjective in the intro script, for starters.&lt;/p&gt;

&lt;p&gt;Another joke in the warm-up adds nothing useful. Another claim that the model knows our product adds nothing. Show the coverage gaps first, then ask me to approve.&lt;/p&gt;

&lt;p&gt;If you already keep a free-model sandbox, run this matrix there. Bring the csv to the review, not the vibe.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Show the Stop Phrase Before a Shared Draft Writes Your Script</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Fri, 18 Sep 2026 06:52:00 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-the-stop-phrase-before-a-shared-draft-writes-your-script-l1m</link>
      <guid>https://dev.to/haaaaaley/show-the-stop-phrase-before-a-shared-draft-writes-your-script-l1m</guid>
      <description>&lt;p&gt;I almost shipped a moderator script I could not defend.&lt;/p&gt;

&lt;p&gt;A teammate pasted it into our research channel. "The model already knows how we interview," they said. Does it? Who owns that claim tomorrow morning?&lt;/p&gt;

&lt;p&gt;I owned the decision that day. The consequence was a live session with five participants. The reverse path was a rewrite after harm. That is a miserable reverse path.&lt;/p&gt;

&lt;p&gt;Nobody had written a stop phrase. The draft may have run on a shared server. Our prototype tasks were still under NDA. I came close to saying yes. This tutorial is the gate I now use.&lt;/p&gt;

&lt;p&gt;Think of a kitchen ticket. The cook does not fire the dish without the ticket. A model is not a cook with judgment. It is a stove. You still write the ticket first.&lt;/p&gt;

&lt;p&gt;Recent talk treats fluent output as engineering. Fluency is not a protocol. A protocol can fail closed. This one does. I label everything below as a proposed kit. It is not a completed study. It is not a vendor benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage one: freeze the decision before heat
&lt;/h2&gt;

&lt;p&gt;Stage one starts on an empty card. Do not open a model tab yet. Write the decision, the owner, and the harm. Write the reverse path in the same file.&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="c1"&gt;# proposed file: research/decision_card.yaml&lt;/span&gt;
&lt;span class="na"&gt;decision_card&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;moderator-script-2026-09-18&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product-design&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;May&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shared&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;draft&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;our&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;moderator&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;script?"&lt;/span&gt;
  &lt;span class="na"&gt;consequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Participants&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;may&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hear&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;untested&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;skip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;promises."&lt;/span&gt;
  &lt;span class="na"&gt;reversibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stop&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;keep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;record."&lt;/span&gt;
  &lt;span class="na"&gt;missing_evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;stop_phrase_visible_before_generate&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;server_class_shown_to_reviewer&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nda_tasks_excluded_from_prompt&lt;/span&gt;
  &lt;span class="na"&gt;noise_fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;empathy_adjectives_in_the_script&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;token_dashboards_without_a_task&lt;/span&gt;
  &lt;span class="na"&gt;stop_phrase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refuse&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;promises&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;skip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;we&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;never&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tested."&lt;/span&gt;
  &lt;span class="na"&gt;hand_back&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return&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;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;moderator.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Do&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;silent-retry."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify stage one out loud. Read the file to a partner. If the stop phrase is missing, you stop. If the owner is "the team," you stop. A crowd cannot refuse in the moment.&lt;/p&gt;

&lt;p&gt;I keep a tiny verifier beside the card. It fails closed. Treat it as local protocol, not product magic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed local verifier, unexecuted until you run it
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research/decision_card.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision_card&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consequence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reversibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop_phrase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hand_back&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;strip&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;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP. Card incomplete:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing_evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP. You never named required evidence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stage one verified. Generation is still forbidden.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that check now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 research/verify_card.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the forbidden line. That is success for stage one. No script. No vibes. A closed gate. Do you still want a sample paragraph? Then you are bargaining with empty evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage two: put the alarm handle on the review surface
&lt;/h2&gt;

&lt;p&gt;Stage two writes the stop phrase into review copy. Not into a wiki. Onto the control the reviewer will see. I treat this like a fire alarm. A beautiful alarm with no handle is decoration.&lt;/p&gt;

&lt;p&gt;Proposed fields, labeled as draft copy only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SERVER CLASS: shared free server, research draft only
TASK: write a moderator script for review, not for the session
STOP PHRASE: Refuse if the script promises a skip we never tested.
EVIDENCE BOX: observations only
HYPOTHESIS BOX: design guesses only
[ Refuse and hand back ]    [ Generate research draft ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify stage two with scenarios, not a cropped screenshot. Scenario A. The reviewer uses a screen reader. Does refuse have a name? Is it before generate in focus order? Scenario B. The reviewer cannot rely on color. Can they still read the server class as text? Scenario C. The draft is slow. Does a live region say the job is still on a shared server?&lt;/p&gt;

&lt;p&gt;If any scenario fails, you still do not generate. I write the checks as a second file, still a proposal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposed file: research/a11y_review.txt
refuse_name: Refuse and hand back
generate_name: Generate research draft
focus_order: refuse_before_generate
server_class: text_not_color_only
status_live_region: polite
timeout_is_not_consent: true
nda_tasks_in_prompt: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would a token graph help here? It usually adds noise. Stop approval if the refuse control is unnamed. Stop approval if generate comes first. That is the evidence that matters on this surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage three: only now may a shared draft run
&lt;/h2&gt;

&lt;p&gt;Stage three is the first moment a model may run. I needed a fence for research drafts. Not production. Not participant recordings. Not the live research repo.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode is an open source project with free model access and a free server option. I use that pair as a sandboxed draft host after the card exists. I do not treat the output as user research. I do not send NDA tasks there. I do not invent a model name I cannot verify.&lt;/p&gt;

&lt;p&gt;A proposed command shape looks like this. Swap in your own authenticated entrypoint. Keep the card. Drop the private tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposed research-draft call, placeholders only&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; research/sessions
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/stop_phrase|decision|hand_back/'&lt;/span&gt; research/decision_card.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; research/request.txt
&lt;span class="c"&gt;# send the card, not prototype NDA tasks&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\nWrite a moderator script. Obey stop_phrase. Do not claim a skip was tested.\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; research/request.txt
&lt;span class="c"&gt;# host: free server used only after stage two passes&lt;/span&gt;
&lt;span class="c"&gt;# save model output to research/sessions/draft.md, never to the live script&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify stage three with boring file tests. The request must include the stop phrase. The request must exclude private task names. The response must land in a draft file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"stop_phrase"&lt;/span&gt; research/request.txt &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STOP. Stop phrase never sent."&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s2"&gt;"secret-task|nda|participant-email"&lt;/span&gt; research/request.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STOP. Private material leaked into the prompt."&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; research/sessions/draft.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"draft isolated"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STOP. No draft file."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If either check fails, discard the output. Do not "just glance." Glancing is how a fake skip lands in a live session. Would you read a contract after signing it? Then do not read a draft after pasting it into the runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage four: split evidence from hope
&lt;/h2&gt;

&lt;p&gt;Stage four splits evidence from hypothesis. This is the part teams skip. They paste confident lines, then retrofit a rationale. Remember a lab notebook. The left page is what happened. The right page is what you hope it means.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# research/sessions/session-2026-09-18.md&lt;/span&gt;

&lt;span class="gu"&gt;## Evidence (do not rewrite)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Server class shown: shared free server
&lt;span class="p"&gt;-&lt;/span&gt; Stop phrase shown before generate: yes
&lt;span class="p"&gt;-&lt;/span&gt; Refuse control focused first: yes
&lt;span class="p"&gt;-&lt;/span&gt; Draft promised "you can skip any task": yes
&lt;span class="p"&gt;-&lt;/span&gt; Skip path rehearsed with a pilot participant: no

&lt;span class="gu"&gt;## Hypotheses (may be wrong)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; The tone feels calmer than our last script
&lt;span class="p"&gt;-&lt;/span&gt; A shorter warm-up might reduce nerves

&lt;span class="gu"&gt;## Decision&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Status: refused
&lt;span class="p"&gt;-&lt;/span&gt; Reason: skip was implied, not tested
&lt;span class="p"&gt;-&lt;/span&gt; Hand-back: human rewrite, no silent retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify stage four with a brutal grep. Feeling language without a participant is a hypothesis. Hypotheses do not ship.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s2"&gt;"users felt|participants will|you can skip"&lt;/span&gt; research/sessions/draft.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s2"&gt;"pilot participant|rehearsed"&lt;/span&gt; research/sessions/session-2026-09-18.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STOP. Feeling language without a participant is a hypothesis."&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the model outgrow your test? Then your test was never a test. It was a compliment. Compliments do not belong in the evidence box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage five: walk the flow you will actually refuse
&lt;/h2&gt;

&lt;p&gt;Stage five is the user flow I will not approve without. Walk it with a partner. You are not measuring eloquence. You are measuring whether a human can still refuse.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Decision card exists] --&amp;gt; B{Stop phrase visible?}
  B --&amp;gt;|No| H[Hand back. Do not generate]
  B --&amp;gt;|Yes| C{Refuse named and first in focus?}
  C --&amp;gt;|No| H
  C --&amp;gt;|Yes| D{NDA tasks excluded from prompt?}
  D --&amp;gt;|No| H
  D --&amp;gt;|Yes| E[Generate on isolated free server]
  E --&amp;gt; F[Split evidence and hypothesis]
  F --&amp;gt; G{Skip or recovery implied but untested?}
  G --&amp;gt;|Yes| H
  G --&amp;gt;|No| I[Human may edit the script]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Success measure: refuse works without a timeout. Success measure: the session record still holds the stop phrase. Stop measure: any implied skip without a rehearsal. Stop measure: missing server class on the review card. If your partner cannot refuse on the first pass, the flow failed. Do not "fix it in copy" later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage six: keep the refused line in the record
&lt;/h2&gt;

&lt;p&gt;Stage six records the hand-back. Keep the discarded sentence. Do not let the next generate pretend it never happened. Silent retries train the team to forget. I will not approve that amnesia.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposed file: research/sessions/hand_back.yaml
hand_back_record:
  refused_span: "You can skip any task."
  why: "No skip path rehearsed with a pilot."
  next_human_task: "Write a script that only offers tested exits."
  retry_allowed: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with a read-back. If the refused span vanished, the record failed. Ask one more question before you close the folder. Which missing evidence should stop approval? Which extra field would only add noise?&lt;/p&gt;

&lt;p&gt;For me, three gaps still stop the run. An unnamed refuse control. A skip promise with no rehearsal. A draft that cannot show its server class. Extra token charts are noise. Empathy adjectives are noise. A second generate "just to see" is noise.&lt;/p&gt;

&lt;p&gt;Why does a free shared server belong in this kit at all? Because research drafts need a fence. A free server is a fence only if you treat it as one. It is the spare table in the hallway. Fine for cardboard models. Wrong for the family silver. Do not put real recordings on it. Do not put unpublished NDA tasks on it. Do not call the draft a usability finding.&lt;/p&gt;

&lt;p&gt;Who should not use this approach? Teams that need a private, contracted isolation boundary. Teams shipping unsupervised agents to production users. Researchers who would skip a pilot after a fluent paragraph. Anyone hoping a free host becomes an SLA.&lt;/p&gt;

&lt;p&gt;Limitations sit in the open. This protocol does not score model quality. It does not prove consent law. It does not replace accessibility testing with real people. Free model access can change without notice. I will not claim quotas, latency, or permanence. I will not pretend a tutorial is a study.&lt;/p&gt;

&lt;p&gt;The human still owns the script. The model can rent the pencil. It does not get the signature. If you already keep research drafts off production hosts, MonkeyCode's free model access and free server option can park that generate step after the card exists. Keep the stop phrase on screen first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Observation Notes Before Approving Agent Usability Claims</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Thu, 17 Sep 2026 04:57:43 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-observation-notes-before-approving-agent-usability-claims-4i67</link>
      <guid>https://dev.to/haaaaaley/show-observation-notes-before-approving-agent-usability-claims-4i67</guid>
      <description>&lt;p&gt;Last Tuesday a design lead asked me to ship. An overnight agent had rewritten the first-run flow. The model called every step clear for everyone.&lt;/p&gt;

&lt;p&gt;Clear for which living person, exactly? That approval is the entire product decision. The owner is design, not the model.&lt;/p&gt;

&lt;p&gt;The consequence is a blocked or shamed first-time user. The last reversible point is the production flag. After that flag, you are apologizing in public.&lt;/p&gt;

&lt;p&gt;I keep one analogy on the wall nearby. A free model is a confident understudy. An understudy can rehearse a difficult scene well.&lt;/p&gt;

&lt;p&gt;An understudy is not the paying audience though. Why do teams forget that so fast? Critique became cheap, and slogans arrived attached.&lt;/p&gt;

&lt;p&gt;Vibe-coded flows show up with usability medals already pinned. Someone still has to refuse the cheap medal. Should that someone be a prompt?&lt;/p&gt;

&lt;p&gt;I split evidence from hypothesis on paper. Users will understand step two remains a guess. Three people described step two is evidence.&lt;/p&gt;

&lt;p&gt;Mixing those two is how fiction ships. I will not bless fiction because it sounds kind. Kind copy can still trap a body.&lt;/p&gt;

&lt;p&gt;This is a proposed protocol, not a finished study. I walk the review from zero on purpose. If a check fails, approval stops cold.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. When a team already has MonkeyCode free model access and a free server option, this protocol parks rehearsal prompts there only. It never parks recordings, consent forms, or live account data there.&lt;/p&gt;

&lt;p&gt;That isolated box is a green room only. That isolated box is not a research lab. Cheap rehearsal is useful in a pinch. Cheap evidence remains a lie in these reviews.&lt;/p&gt;

&lt;p&gt;Each stage has a command you can run. Each stage has a pass or a hard stop. If you skip a stop, you are not reviewing. You are decorating a guess with empty ceremony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: isolate the claim
&lt;/h2&gt;

&lt;p&gt;Stage 0 creates a disposable review desk first. I want a folder that cannot deploy anything. A free server is fine for this scratch.&lt;/p&gt;

&lt;p&gt;A laptop folder is also fine for this. The point is isolation from production flags. This isolation is the first accessibility feature we owe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; review/first-run-claim/notes
&lt;span class="nb"&gt;cd &lt;/span&gt;review/first-run-claim
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; claim.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
claim_id: first-run-2026-09-17
decision_owner: design-lead
reversible_until: production-flag
claim_text: "First-run is clear for everyone."
evidence_status: missing
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; claim.yml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE0_PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did STAGE0_PASS print on your terminal? If it did not print, you stop. You do not brief a model without a still claim.&lt;/p&gt;

&lt;p&gt;The file is the decision sitting in daylight. Can you point at it with a finger? If you cannot, you do not have a review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: name a body, not a vibe
&lt;/h2&gt;

&lt;p&gt;Stage 1 rewrites the claim without marketing words. I strike intuitive, seamless, and everyone on sight. I replace them with one observable action.&lt;/p&gt;

&lt;p&gt;Can a new user complete invite to first save without help? That question has a body inside it. Bodies are what this review still owes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hypothesis.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Hypothesis, not evidence:
A new user can complete invite to first save.
They can recover if the invite link expires.
They can do this with a screen reader.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'intuitive|seamless|everyone|clear'&lt;/span&gt; hypothesis.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE1_STOP &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE1_PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If STAGE1_STOP appears, I rewrite the file again. A hypothesis must name a task, a recovery, and a body. Medals do not belong in this file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: rehearse failure, never mint quotes
&lt;/h2&gt;

&lt;p&gt;Stage 2 is the only place a free model belongs. I ask it to attack the hypothesis in the green room. I do not ask it to bless the flow.&lt;/p&gt;

&lt;p&gt;Would you let an intern sign off accessibility work? Then do not let a model do it. Rehearsal helps. Rehearsal is not observation.&lt;/p&gt;

&lt;p&gt;I paste the hypothesis into the free model there. My prompt is labeled as rehearsal on purpose. If the prompt flatters the flow, I have already failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is a rehearsal, not a study.
List ways this first-run can fail.
Do not claim users were observed.
Do not invent quotes or disability experience.
Return failure modes only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I save the output as &lt;code&gt;rehearsal-failures.md&lt;/code&gt;. Then I check it like a suspicious receipt. Fake witnesses are a stop, not a style issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'user said|participants?|we observed|told me'&lt;/span&gt; rehearsal-failures.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE2_STOP &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE2_PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that grep finds fake observation language, I stop cold. The model tried to wear a participant badge. That badge is the missing evidence.&lt;/p&gt;

&lt;p&gt;I also scan for invented assistive-tech autobiography. A model does not get to narrate a screen reader user's feelings. I put those sentences straight into the bin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: collect notes or collect nothing
&lt;/h2&gt;

&lt;p&gt;Stage 3 is human observation, or it is nothing. I do not need a huge theater of a study. I need notes with a date, a person, and a task.&lt;/p&gt;

&lt;p&gt;Two or three scenario sessions beat one glamorous summary. I use this scenario card every time. This card is a protocol, not a trophy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Scenario card: invite to first save&lt;/span&gt;
Stop if the person cannot consent in their own words.
Task: accept invite, land, create first save, leave.
Recovery: expire the invite, then resume.
Access: keyboard only, then one screen reader pass.
Notes field: what they did, not what I hoped.
Discarded quotes: keep them in this same file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After each session I append notes by hand. I never let the model rewrite those notes. Why would I hand the evidence pen back to the intern?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; notes/session-01.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; notes/session-02.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;STAGE3_PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No notes files means no approval, full stop. One session is a story you liked. Two sessions can still be luck, and that is fine.&lt;/p&gt;

&lt;p&gt;I still will not approve on zero notes. Would you ship a bridge on a sketched load? Then do not ship a first-run on vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: sign a card that can refuse
&lt;/h2&gt;

&lt;p&gt;Stage 4 is the decision card the human signs. I want fields that force a pause in the hand. I want discarded quotes kept in the same record.&lt;/p&gt;

&lt;p&gt;Dropped frustration is often the real finding. If the card has no place for refusal, it is a poster. Bright posters do not govern agents in shipping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Decision card&lt;/span&gt;
Claim: new user completes invite to first save.
Owner: design-lead
Evidence: session-01, session-02 notes
Coverage: keyboard, one screen reader, expired invite
Hypothesis leftover: none claimed as fact
Discarded quotes: kept in notes/discarded.md
Reversibility: production flag still off
Stop: missing notes OR missing AT pass OR missing recovery
Approve: yes / no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I validate the card with a small script. This is research ops, not frontend implementation. Someone else can build the interface later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# verify_decision_card.py
# Proposal: local check, not a production gate.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Owner:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Evidence:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Coverage:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Discarded quotes:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reversibility:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stop:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session-*.md&lt;/span&gt;&lt;span class="sh"&gt;"&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;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP missing fields: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP missing observation notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production flag still off&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP no reversibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STAGE4_PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it before anyone pastes a ship message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 verify_decision_card.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it exits nonzero, the claim stays a hypothesis. That is the entire point of the gate. Software should refuse a hollow yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow I actually review
&lt;/h2&gt;

&lt;p&gt;Here is the user flow I hold against the card. Notice the hand-back before the flag. The model never crosses that line.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[Agent drafts first-run] --&amp;gt; B[Human writes hypothesis]
  B --&amp;gt; C[Free model rehearsal only]
  C --&amp;gt; D{Fake observation language?}
  D --&amp;gt;|Yes| E[Stop and discard]
  D --&amp;gt;|No| F[Human sessions]
  F --&amp;gt; G{Notes plus AT plus recovery?}
  G --&amp;gt;|No| E
  G --&amp;gt;|Yes| H[Decision card]
  H --&amp;gt; I{Production flag still off?}
  I --&amp;gt;|Yes| J[Human approve or refuse]
  I --&amp;gt;|No| E&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Accessibility is not a smell a model can claim. I require a keyboard pass in the coverage line. I also require one screen reader pass before yes.&lt;/p&gt;

&lt;p&gt;I require consent copy the person can refuse out loud. If the agent added a we-watched-you sentence, I pull it. Surveillance copy is not research warmth.&lt;/p&gt;

&lt;p&gt;Focus order, name, role, and value still matter in the live path. I do not treat those as a later polish ticket. If the rehearsal found a nameless button, Stage 3 must watch someone meet it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stops versus noise
&lt;/h2&gt;

&lt;p&gt;What missing evidence should stop approval today? Missing session notes should stop it now. Missing recovery from an expired invite should stop it.&lt;/p&gt;

&lt;p&gt;Missing assistive-tech coverage should stop it too. Missing consent language the person can decline should stop it. Those are stops, not leftover vibes.&lt;/p&gt;

&lt;p&gt;What extra information is only noise here? Model confidence scores are noise. Extra generated personas are noise. A third rewrite of the same slogan is noise.&lt;/p&gt;

&lt;p&gt;Another intern blessing the intern is noise. I throw those extras out of the card. Silence is cheaper than false coverage on a first-run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits, recovery, and who should leave
&lt;/h2&gt;

&lt;p&gt;This approach has limits I will not hide. A free model will still hallucinate disability. A free server is the wrong vault for recordings.&lt;/p&gt;

&lt;p&gt;Two sessions will not map a whole market. The script only checks fields, not truth. Fields can be filled with fiction if a reviewer lies.&lt;/p&gt;

&lt;p&gt;The protocol assumes a human who still cares. If nobody cares, the yaml is costume. Costume fails in the same old ways.&lt;/p&gt;

&lt;p&gt;Who should not use this workflow at all? Teams replacing participants with prompts should not. Teams putting real user media on a shared free server should not.&lt;/p&gt;

&lt;p&gt;Teams that need clinical or legal research methods should not. Teams hoping a tutorial will excuse skipped consent should not. If flow failure can harm someone, rehearsal is not enough.&lt;/p&gt;

&lt;p&gt;Hire a proper study in those cases. I also will not use this to argue models code better than people. That debate misses the interface in front of a person.&lt;/p&gt;

&lt;p&gt;Someone still decides what the product may do. That someone needs notes, not medals. Notes are slower. Notes are the job.&lt;/p&gt;

&lt;p&gt;If a claim already shipped without notes, recover in public. Turn the flag off before you write sorry copy. Keep the discarded quotes in the record.&lt;/p&gt;

&lt;p&gt;Write the gap on the decision card in daylight. Then rerun Stage 3 with real people. Do not ask the model to apologize in microcopy.&lt;/p&gt;

&lt;p&gt;Ask a human to repair the path instead. Would I let a free model sit in the green room? Yes, as rehearsal only.&lt;/p&gt;

&lt;p&gt;Would I let it sign the house tonight? No. That is the whole tutorial, and it should stay boring.&lt;/p&gt;

&lt;p&gt;If you already have a free-model sandbox, run this protocol there before anyone calls the output research.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Recovery Evidence Before Approving Free-Model Empty States</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Wed, 16 Sep 2026 03:14:32 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-recovery-evidence-before-approving-free-model-empty-states-14e5</link>
      <guid>https://dev.to/haaaaaley/show-recovery-evidence-before-approving-free-model-empty-states-14e5</guid>
      <description>&lt;p&gt;I will walk a Friday review as a rehearsal. Treat the scene as a protocol, not a trophy. A billing empty state waits for a human yes.&lt;/p&gt;

&lt;p&gt;The line looks calm on the board. "Payment failed. We will retry for you." Who is we in that quiet sentence? What recovery window does that retry occupy?&lt;/p&gt;

&lt;p&gt;The designer on call owns the yes. Finance still owns the angry inbox later. Reversibility sits on a later receipt, not here.&lt;/p&gt;

&lt;p&gt;So I ask two questions before any model speaks. Which missing evidence should stop approval? Which extra flourish is only noise?&lt;/p&gt;

&lt;p&gt;An empty state is a fire exit sign. You do not let a poet rewrite the exit. Not without the floor plan in hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Freeze the decision in a file
&lt;/h2&gt;

&lt;p&gt;Do not start by prompting a fluent model. Fluency still is not coverage. Have you felt that rush this week?&lt;/p&gt;

&lt;p&gt;I write the act, the owner, and the stop. If the stop cannot be named, the copy cannot ship. A decision without a brake is only vibe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; review/billing-empty-state
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/decision.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# Consequential decision (rehearsal)
Act: approve empty-state copy after a failed payment
Owner: designer on call
Consequence: the user infers that money is already moving
Reversibility: the next receipt, not this view
Stop if: retry actor, window, or cancel path is unnamed
Noise: tone words that do not change the recovery path
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I verify the stop exists in the file. A missing stop is a missing brake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"Stop if"&lt;/span&gt; review/billing-empty-state/decision.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE1_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that command fails, I do not invite a model. Why ask a system to finish a sentence I cannot reverse?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Split evidence from hypotheses
&lt;/h2&gt;

&lt;p&gt;Evidence is what the product already knows. Hypotheses are stories we hope users believe. Mix them, and the model writes fiction with a straight face.&lt;/p&gt;

&lt;p&gt;I keep four evidence fields and nothing else. They are payment state, retry actor, retry window, and cancel. Everything else is a hypothesis wearing costume.&lt;/p&gt;

&lt;p&gt;"Users want reassurance" is still a hypothesis. That line feels kind in a review room. Kindness without a mechanism is still a guess. Should a guess sit in the evidence block?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/coverage.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
evidence:
  payment_state: failed
  retry_actor: unknown
  retry_window: unknown
  cancel_control: unknown
  error_announced_before_promise: unknown
hypotheses:
  - users want a calmer sentence
  - retry in the background feels helpful
rules:
  approve_only_if:
    - retry_actor is named
    - retry_window is named
    - cancel_control is named
    - error_announced_before_promise is true
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the unknowns. Unknowns are the point of this card. They are not shame, and they are not polish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; unknown review/billing-empty-state/coverage.yml&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 4 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE2_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that count is not four, I misfiled a wish as a fact. Would you let a wish sit beside payment_state?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Generate rivals in a sandbox, not a winner
&lt;/h2&gt;

&lt;p&gt;I still want alternate sentences on the table. I do not want a crowned sentence. This is where a free-model sandbox earns a seat.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode's free model access to draft rival copy only. I use the free server option to pin one shared review board. I do not treat either as proof.&lt;/p&gt;

&lt;p&gt;The sandbox is a studio, not a court. A free model can propose a sentence. A free server can remember the card. Neither can bless a billing line.&lt;/p&gt;

&lt;p&gt;A proposed prompt stays boring on purpose. Boring prompts leak fewer hypotheses. Fancy prompts smuggle comfort into the evidence pile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/prompt.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Write three empty-state variants for a failed payment.
Each variant must name retry actor, retry window, and cancel path.
Do not invent a success. Do not hide the failure.
Return YAML only with id, copy, named_actor, named_window, named_cancel.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run that against the sandbox the team already uses. Your command will differ by tool. The check after it should not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed check. Label: unexecuted example.&lt;/span&gt;
&lt;span class="c"&gt;# Replace RUN_SANDBOX with your CLI against the free-model endpoint.&lt;/span&gt;
&lt;span class="c"&gt;# RUN_SANDBOX --file review/billing-empty-state/prompt.txt &amp;gt; review/billing-empty-state/rivals.yml&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
required = ["named_actor", "named_window", "named_cancel"]
print("STAGE3_OK if rivals.yml fills:", ", ".join(required))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a variant cannot name the actor, it is not a candidate. It is noise wearing empathy. Would you ship empathy that hides the stop?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Score coverage, not charm
&lt;/h2&gt;

&lt;p&gt;Charm is cheap on empty states. Coverage is a stubborn table. I read each rival against the four evidence fields.&lt;/p&gt;

&lt;p&gt;I refuse to average a vibe across the room. Have you watched people vote for the friendliest lie? I have, in rehearsals like this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposed scorer. Label: unexecuted example.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review/billing-empty-state/coverage.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve_only_if&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_actor is named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_window is named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel_control is named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_announced_before_promise is true&lt;/span&gt;&lt;span class="sh"&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;assert&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STAGE4_OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify by reading the printout aloud with the owner. If you cannot say the rule in one breath, the rule is theater. Theater does not reverse a charge.&lt;/p&gt;

&lt;p&gt;I keep discarded rivals in the same folder on purpose. A deleted almost-yes will haunt the next review. Scar tissue belongs in the record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/discarded.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# Discarded rivals
- "We will retry for you." Reason: actor unnamed. Empathy without a mechanism.
- "Hang tight." Reason: hides failure. Adds noise, not coverage.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; review/billing-empty-state/discarded.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE4_LOG_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stage 5: Rehearse two research scenarios
&lt;/h2&gt;

&lt;p&gt;Copy review is not user research. A scenario protocol can still stop a bad yes. I run two scenes only, then I stop adding questions.&lt;/p&gt;

&lt;p&gt;Retry later succeeds. Retry is blocked. Success measure: the person names who retries and how to stop. Stop measure: if they cannot name the actor, the copy fails.&lt;/p&gt;

&lt;p&gt;No extra adjectives get to vote in the room. Would a third mood question change the mechanism? That extra question is noise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/scenarios.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# Scenario A — retry later succeeds
Setup: card declined, retry queued for 02:00 local, cancel is visible.
Ask: Who retries? When? How do you stop it?
Pass: actor, window, and stop are spoken back.
Fail: any of the three is guessed.

# Scenario B — retry is blocked
Setup: bank rejected the merchant, no retry will run.
Ask: What happens to the money? What can you do now?
Pass: no invented retry. Failure remains audible.
Fail: participant waits for a background save that will never come.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the fail lines exist before you recruit anyone. A protocol without fail lines is a compliment session. Compliments do not protect a payment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'^Fail:'&lt;/span&gt; review/billing-empty-state/scenarios.md&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE5_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want that count at two. Two scenes. Two ways to lose. That is enough evidence for a copy yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Accessibility is a recovery check
&lt;/h2&gt;

&lt;p&gt;Empty states fail in the ear first. Screen readers often hear the hopeful promise before the error. That order is a lie with good kerning.&lt;/p&gt;

&lt;p&gt;I walk the view as a stop path, not a mood board. Error text first. Then recovery facts. Then cancel. Then any model-written comfort.&lt;/p&gt;

&lt;p&gt;Comfort never leads the announcement. Comfort can even be missing. Missing comfort is not a failure of the pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; review/billing-empty-state/a11y.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
# A11y rehearsal (proposed)
Order: error, recovery facts, cancel, optional comfort.
Live region: retry actor and window travel with the error.
Tab: cancel is reachable before the comfort line.
Stop if: promise is read before the failure.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with a screen reader or an accessibility tree dump. I do not treat a visual screenshot as evidence. Eyes lie about order, especially on calm cards.&lt;/p&gt;

&lt;p&gt;If the promise leads, I reject the copy on the spot. Would you accept an apology that hides the fire alarm?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"Stop if"&lt;/span&gt; review/billing-empty-state/a11y.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE6_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stage 7: Hand back with the discarded record
&lt;/h2&gt;

&lt;p&gt;Approval is not a vibe in this flow. Approval is a hand-back to a named human. The model returns a card. A human records what was refused.&lt;/p&gt;

&lt;p&gt;The product keeps the refuse list beside the shipped line. Future reviews need that scar tissue. Otherwise the same friendly lie returns on Monday.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Failed payment view] --&amp;gt; B{Coverage card complete?}
  B --&amp;gt;|No| C[Refuse copy]
  C --&amp;gt; D[Hand back to evidence owner]
  B --&amp;gt;|Yes| E[Sandbox rivals from a free model]
  E --&amp;gt; F{Actor window and cancel named?}
  F --&amp;gt;|No| C
  F --&amp;gt;|Yes| G[Two research scenarios]
  G --&amp;gt; H{Participant can stop the retry?}
  H --&amp;gt;|No| C
  H --&amp;gt;|Yes| I[A11y order check]
  I --&amp;gt; J{Error announced first?}
  J --&amp;gt;|No| C
  J --&amp;gt;|Yes| K[Human yes plus discarded log]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Verify the log still holds the almost-shipped line. Future you will thank present you. Memory is a product surface, not a chat aftertaste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; review/billing-empty-state/discarded.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; review/billing-empty-state/coverage.yml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"STAGE7_OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this rehearsal is not
&lt;/h2&gt;

&lt;p&gt;This is not a claim that models write safer billing copy. This is not a benchmark against any paid model. This is not a substitute for counsel on payment language.&lt;/p&gt;

&lt;p&gt;Do not use this flow when no human owns the yes. Do not use it for medical, legal, or credit decisions. Do not use a free sandbox as production truth.&lt;/p&gt;

&lt;p&gt;A free server can host the board for the team. It cannot host accountability for the charge. Generation is cheap. Coverage is the job.&lt;/p&gt;

&lt;p&gt;I still like generating rivals on a free model. I refuse to confuse generation with research. Evidence covers the act. Hypotheses stay in their pen.&lt;/p&gt;

&lt;p&gt;Which missing field would stop your next empty state? Which sentence is only noise?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Reversibility Proof Before Pasting Agent Undo Copy</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sun, 13 Sep 2026 19:09:22 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-reversibility-proof-before-pasting-agent-undo-copy-156o</link>
      <guid>https://dev.to/haaaaaley/show-reversibility-proof-before-pasting-agent-undo-copy-156o</guid>
      <description>&lt;p&gt;The agent already sent the client email. I am staring at a recovery screen. A free model drafted the undo label. It sounds calm and finished. It wrote, You can undo this send. Can I actually reverse it?&lt;/p&gt;

&lt;p&gt;The send API has no reverse path. I own this approval. The client owns the consequence. The last reversible moment is this card. Missing evidence should stop me here. Extra flavor text should not.&lt;/p&gt;

&lt;p&gt;This is a research tutorial, not a launch story. I label every example as a protocol. I will not treat model copy as a finding. I will not invent an undo that product never shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze the decision, not the vibe
&lt;/h2&gt;

&lt;p&gt;Name the decision before any model talks. Who may paste recovery copy into the system? What harm follows a false undo promise? Where does reversibility die?&lt;/p&gt;

&lt;p&gt;I write those three answers on paper first. Decision owner is the designer on the review. Consequence is a billed or public action. Reversibility dies after the paste.&lt;/p&gt;

&lt;p&gt;Does confidence in the draft change any of that? No. Tone is not evidence. A fluent sentence is still a hypothesis.&lt;/p&gt;

&lt;p&gt;Here is the user flow I keep on the wall.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;failed action
    -&amp;gt; agent offers recovery
        -&amp;gt; review card (human)
            -&amp;gt; STOP if undo is unproven
            -&amp;gt; HAND BACK if user must choose
            -&amp;gt; paste copy only after proof
                -&amp;gt; focus returns to the failed control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that middle card is missing, I do not proceed. A pretty label is not a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Build the evidence card
&lt;/h2&gt;

&lt;p&gt;I refuse a blank prompt at this stage. The card has fields, not vibes. Each field is either evidence or noise.&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;"decision_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recovery-undo-label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failed_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"action_already_committed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"undo_api_exists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"undo_window_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"focus_return_target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"model_origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"model_assumptions"&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;"copy_claims_undo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_vs_hypothesis"&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;"evidence"&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;"hypotheses"&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Verification for this stage is boring on purpose. Every required field is present. Empty strings stay empty. Null stays null. I do not let a model fill &lt;code&gt;undo_api_exists&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which missing evidence should stop approval? A blank &lt;code&gt;undo_api_exists&lt;/code&gt; stops me. A missing &lt;code&gt;focus_return_target&lt;/code&gt; stops me. A missing &lt;code&gt;failed_action&lt;/code&gt; stops me.&lt;/p&gt;

&lt;p&gt;Which extra information is only noise? Synonym lists. Confidence percentages. Brand adjectives. A paragraph about empathy. None of that reverses the email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Record the failed action in human language
&lt;/h2&gt;

&lt;p&gt;I write the failed action as the user saw it. Not as the trace saw it. "Send proposal to Northwind" is the record. &lt;code&gt;POST /messages&lt;/code&gt; is support, not the record.&lt;/p&gt;

&lt;p&gt;Then I ask one mean question. Has this action already left our boundary? If yes, undo copy is probably a lie. If no, I still need a window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;failed_action: Send proposal to Northwind
action_already_committed: true
undo_api_exists: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: I can read those three lines aloud. A teammate who never saw the ticket still understands the harm. If they cannot, the record is too technical. Rewrite it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Draft on a free model, then lock the origin
&lt;/h2&gt;

&lt;p&gt;I need a disposable draft, not a source of truth. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I used MonkeyCode's free model access and free server option only to generate candidate recovery sentences. The server does not prove the undo exists.&lt;/p&gt;

&lt;p&gt;I keep the prompt small. I feed the evidence card. I forbid the model from inventing APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You draft recovery copy only.
You may not claim undo unless undo_api_exists is true.
If undo_api_exists is false, offer hand-back, not undo.
Quote failed_action in the first sentence.
Do not fill evidence fields.
Return three candidates. Label each as hypothesis.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: the response still contains &lt;code&gt;hypothesis&lt;/code&gt;. The origin field is filled. &lt;code&gt;model_assumptions&lt;/code&gt; lists what it guessed. If it guessed an undo API, I mark &lt;code&gt;stop_approval&lt;/code&gt; true. I do not negotiate with that guess.&lt;/p&gt;

&lt;p&gt;A free draft is useful like scratch paper. Scratch paper does not ship. Would you frame scratch paper in the design system? I would not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Separate evidence from design hypotheses
&lt;/h2&gt;

&lt;p&gt;This is the part teams skip. They paste the nicest sentence. Then they call it research.&lt;/p&gt;

&lt;p&gt;I split the notebook in half. Left column is evidence. Right column is hypothesis. Nothing crosses without a source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVIDENCE
- Send already left the boundary
- No reverse endpoint in the contract
- User still has focus on Send

HYPOTHESIS
- "Undo send" reduces panic
- A 10-second window would feel fair
- Users want a joke in the error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The left column can stop the paste. The right column cannot approve it. A joke in the error is noise here. Panic reduction is also untested. It does not create an API.&lt;/p&gt;

&lt;p&gt;Verification: every sentence on the right starts with "might". Every sentence on the left names a source. If I cannot name the source, it moves right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Run three research scenarios with stop conditions
&lt;/h2&gt;

&lt;p&gt;I do not recruit a panel for this gate. I run a tabletop first. If the tabletop fails, live sessions would only decorate the failure.&lt;/p&gt;

&lt;p&gt;Scenario A. The send already went out. The user is still on the screen. The draft says Undo. Stop condition: they believe the email can return. Success measure: they hear that it cannot, and they get a next step.&lt;/p&gt;

&lt;p&gt;Scenario B. The send is queued for eight seconds. An undo API exists. Stop condition: focus jumps to a toast they never requested. Success measure: focus stays on Send, and the window is spoken.&lt;/p&gt;

&lt;p&gt;Scenario C. The model offers both Undo and Edit recipient. Stop condition: two primary actions, no owner. Success measure: one action, one hand-back, one record of the discarded option.&lt;/p&gt;

&lt;p&gt;I read those scenes out loud with a teammate. We do not act kind to the copy. Kindness is not a stop condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STOP if copy_claims_undo &amp;amp;&amp;amp; !undo_api_exists
STOP if undo_window_ms is null &amp;amp;&amp;amp; copy mentions a timer
STOP if focus_return_target is empty
HAND BACK if the user must pick a new recipient
NOISE: extra synonyms, mascot lines, model confidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: I can fail the draft with one of those STOP lines. If every draft still passes, my stops are too soft. Tighten them before any user session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Accessibility review of the recovery path
&lt;/h2&gt;

&lt;p&gt;Recovery is not a visual badge. It is a path back to control. I check the path like a person who cannot see the toast.&lt;/p&gt;

&lt;p&gt;Does the announcement name the failed action? Does it avoid saying Undo when undo is false? Does focus return to the control that caused the send? Does a timer exist only when a real window exists?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# proposed live-region pattern, unexecuted
[role=status]
Send to Northwind already left this screen.
This send cannot be undone.
You are back on Send. You can edit the recipient.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want that status before any button. A red inline error that never receives focus is a trap. A modal that steals focus and offers a fake Undo is worse.&lt;/p&gt;

&lt;p&gt;Verification: keyboard only, then a screen reader pass. I tab from the failed control through the card and back. If I cannot return, the pattern fails. If the reader says Undo and the API cannot, the pattern fails. No extra copy will fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 7: Paste only after the card flips
&lt;/h2&gt;

&lt;p&gt;The card stays red until proof exists. Proof is not a prettier synonym. Proof is &lt;code&gt;undo_api_exists&lt;/code&gt; measured against the contract, plus a focus target, plus a honest first sentence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// proposed check, unexecuted&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mayPasteRecovery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copy_claims_undo&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;undo_api_exists&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undo claimed without api&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed_action&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed action missing&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus_return_target&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no focus return&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence_vs_hypothesis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no evidence listed&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;Verification: I run the function against a lying draft and a honest draft. The lying draft must fail. The honest draft may still fail on focus. That is correct. Shipping without a return path is another kind of lie.&lt;/p&gt;

&lt;p&gt;After a pass, I paste one sentence. I keep the discarded candidates in the decision record. Future me will thank present me. Future me is messy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this protocol is not
&lt;/h2&gt;

&lt;p&gt;It is not a benchmark of free models. I have no latency table for you. I have no token quota to advertise. I have no claim that a free server makes recovery safe.&lt;/p&gt;

&lt;p&gt;It is not permission to skip users. Tabletop stops are cheaper than harm. They are not a substitute for a session when undo is real and timed.&lt;/p&gt;

&lt;p&gt;Who should not use this approach? Anyone hoping the model will invent a reverse API. Anyone pasting recovery copy into a design system without a contract check. Anyone who needs production uptime from a free scratch server. Anyone who treats outreach drafts as research evidence.&lt;/p&gt;

&lt;p&gt;The useful question stays small. What missing proof should stop this paste? What extra sentence would only add noise? If you cannot answer both, do not ask a model for the label yet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Stop Proof Before Approving an Agent Retry</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sat, 12 Sep 2026 17:04:39 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-stop-proof-before-approving-an-agent-retry-23oe</link>
      <guid>https://dev.to/haaaaaley/show-stop-proof-before-approving-an-agent-retry-23oe</guid>
      <description>&lt;p&gt;The retry button looked harmless during Friday critique. An agent had rewritten onboarding consent copy overnight. The first pass missed a recovery path. Now the agent wanted another loop. Who should click approve with that hole still open?&lt;/p&gt;

&lt;p&gt;I own that click as the reviewing designer. Another loop can overwrite a human edit. Reversibility dies once the retry is queued. I refuse the retry until stop proof exists on the card.&lt;/p&gt;

&lt;p&gt;This is not a speech about trusting models. It is a from-zero workflow you can run today. You leave with a review card and a validator. You also leave knowing which missing evidence must stop approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from the failed loop, not the model
&lt;/h2&gt;

&lt;p&gt;I do not open a playground first. I open a dated research folder instead. The failed loop is the artifact under review. The model is only a later witness, never the owner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; research/retry-2026-09-13
&lt;span class="nb"&gt;cd &lt;/span&gt;research/retry-2026-09-13
&lt;span class="nb"&gt;touch &lt;/span&gt;decision-card.yaml flow.md recovery.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring on purpose, and that helps. The folder must exist before any critique starts. The three files must be empty and correctly named. If any file is missing, I stop the session.&lt;/p&gt;

&lt;p&gt;Why three files for one retry decision? One card holds the human decision. One note holds the moment of choice. One note holds failure recovery for assistive tech. Mixing them hides missing evidence inside chat. Have you ever approved a retry from a thread? That thread is not a record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fill the decision card before any retry
&lt;/h2&gt;

&lt;p&gt;I write the card in plain YAML. Designers can read it without a decoder. Engineers can lint it without a new app. The fields are the interface, not decoration.&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;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;retry_agent_loop&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product_design&lt;/span&gt;
&lt;span class="na"&gt;surface&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;onboarding_consent&lt;/span&gt;
&lt;span class="na"&gt;reversible_until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;before_retry_queued&lt;/span&gt;
&lt;span class="na"&gt;user_consequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;overwritten_consent_copy&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;observed_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;discarded_options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;source_of_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;last_human_edit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;hypotheses&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;"&lt;/span&gt;
&lt;span class="na"&gt;stop_conditions&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;"&lt;/span&gt;
&lt;span class="na"&gt;a11y&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rewrite_announced&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;refusal_named&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;focus_return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;retry_allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a read-back with the owner present. Every required field that is empty must stay visible. I never default &lt;code&gt;retry_allowed&lt;/code&gt; to true. Empty means stop, not “fill later.” What extra field would only add noise here? Model nickname. Sample temperature. A confidence badge. Those do not change the human decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate evidence from a clever guess
&lt;/h2&gt;

&lt;p&gt;Evidence is what a person observed on the surface. A hypothesis is a story about why it happened. I keep them in different keys on purpose. That split sounds petty until a fluent model launders a guess.&lt;/p&gt;

&lt;p&gt;I fill a walkthrough from that Friday scene. Treat it as a labeled protocol. It is not a published study with metrics.&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;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;observed_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Consent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;offered&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refusal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rewrite."&lt;/span&gt;
  &lt;span class="na"&gt;discarded_options&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;Keep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sentence."&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ask&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rewriting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;consent."&lt;/span&gt;
  &lt;span class="na"&gt;source_of_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;treated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;consent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;as&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;marketing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;copy."&lt;/span&gt;
  &lt;span class="na"&gt;last_human_edit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Design&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;owner,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2026-09-12,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recovery&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sentence&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;intact."&lt;/span&gt;
&lt;span class="na"&gt;hypotheses&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;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;loop&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over-weighted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;brevity."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lacked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;consent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rule."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a two-column read, out loud. Left column is evidence. Right column is guess. If I cannot split a sentence, it is not evidence. Would you let a guess stop a legal consent line? I would not, and neither should the card.&lt;/p&gt;

&lt;p&gt;Ask this with the team before anyone queues work. Which missing evidence should stop approval right now? Which extra note is only noise? I stop for a missing &lt;code&gt;last_human_edit&lt;/code&gt;. I do not stop for a missing logit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draw the flow at the moment of choice
&lt;/h2&gt;

&lt;p&gt;I sketch the flow in Mermaid next. The retry click sits in the middle. Everything else is a gate with an owner.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  fail[Loop fails on user-facing copy] --&amp;gt; card[Owner opens decision card]
  card --&amp;gt; ev{Evidence slots filled?}
  ev --&amp;gt;|no| stop1[Refuse retry]
  ev --&amp;gt;|yes| sc{Stop conditions named?}
  sc --&amp;gt;|no| stop2[Refuse retry]
  sc --&amp;gt;|yes| rec{Recovery proven for AT?}
  rec --&amp;gt;|no| stop3[Refuse retry]
  rec --&amp;gt;|yes| critique[Optional second-pass critique]
  critique --&amp;gt; human[Human approves or refuses]
  human --&amp;gt;|approve| queue[Queue retry]
  human --&amp;gt;|refuse| handback[Hand back to owner]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Verification is a tabletop walk, not a slide review. I point at each diamond and name the owner. I name the reversible point in one sentence. If a diamond has no owner, the flow is fiction. Can you point to the last safe moment? If not, do not queue the loop.&lt;/p&gt;

&lt;p&gt;The analogy I use is a fire door. A retry is not a hallway you stroll through. It is a door with a closer and a latch. You do not prop it open with hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write stop conditions that can actually fire
&lt;/h2&gt;

&lt;p&gt;Vague stops never fire when the room is tired. “Be careful with consent” is not a condition. I write conditions as observable states on the screen.&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;stop_conditions&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;Consent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;copy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;changed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;without&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refusal."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Last&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;edit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;would&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;overwritten."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rewrite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;announced&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;without&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;naming&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;field."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refusal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;control&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;has&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;accessible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Source&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;still&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unlabeled."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I write success beside those stops in &lt;code&gt;flow.md&lt;/code&gt;. Success is not “the model sounds better.” Success is a recoverable interface after a bad rewrite.&lt;/p&gt;

&lt;p&gt;I keep both measures in prose so nobody hunts a wiki. Success means the refusal stays in the consent line. Success means the human edit remains in the record. Success means focus returns to the edited field. Success means a retry is refused when any stop fires. Stop means any evidence slot is still empty. Stop means any recovery flag is still false. Stop means the person clicking is not the owner.&lt;/p&gt;

&lt;p&gt;Verification is a dry run with a stranger in the room. I read each stop aloud once. I ask whether that stranger could see it fire. If only the author can see it, I rewrite the stop. A private stop is just a wish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove recovery before the model speaks again
&lt;/h2&gt;

&lt;p&gt;Agent copy is still an interface, not a paragraph. A rewrite without recovery is a trap. I check three things only on this gate. Announced rewrite. Named refusal. Focus return.&lt;/p&gt;

&lt;p&gt;I write &lt;code&gt;recovery.md&lt;/code&gt; as sentences, not a dumped audit. The rewrite must be announced with the field name. The refusal control must expose a real accessible name. Focus must return to the edited field, not a banner. The discarded human sentence must remain readable in the record. I do not treat this as a full accessibility audit. It is a retry gate. Missing any line keeps &lt;code&gt;retry_allowed&lt;/code&gt; false.&lt;/p&gt;

&lt;p&gt;Verification is a keyboard and AT pass on the current screen. Does the announcement name the field that changed? Can I find the refusal without guessing the icon? If someone says we will fix recovery after retry, that is the failure. After is not a recovery. After is a hope with a release date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only then run a cheap second-pass critique
&lt;/h2&gt;

&lt;p&gt;Sometimes I want a second reader, not a second actor. I want a critique that cannot queue the retry. That is the only place a disposable model lane belongs.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. In this workflow, MonkeyCode is only that disposable critique lane. Free model access and a free server option are enough for rehearsal. I do not let that output write the card. I do not let it flip &lt;code&gt;retry_allowed&lt;/code&gt;. If you already have an internal critic, skip this stage.&lt;/p&gt;

&lt;p&gt;I paste a frozen bundle into the critique lane. The card, the flow, and the recovery note travel together. I ask for missing stops, not prettier copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a critic, not an actor.
Do not rewrite the consent line.
List evidence slots that are still empty.
List stop conditions that cannot be observed.
List recovery that is still unproven for assistive tech.
If you cannot point to a field, say UNKNOWN.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is refusal by the critic. The run must not produce new user copy. If it rewrites the sentence, I discard the whole run. The point is coverage, not fluency. Why invite a model that cannot act? Because I need a noisy highlighter. I do not need another author with a retry button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lint the card so blanks cannot hide
&lt;/h2&gt;

&lt;p&gt;Chat will hide blanks with a confident tone. A linter will not do that favor. I keep a small validator next to the card.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# verify_retry_card.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED_EVIDENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observed_failure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_of_failure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_human_edit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;REQUIRED_A11Y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rewrite_announced&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refusal_named&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;focus_return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop_conditions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop_conditions empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discarded_options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discarded_options empty&lt;/span&gt;&lt;span class="sh"&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_EVIDENCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; empty&lt;/span&gt;&lt;span class="sh"&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED_A11Y&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;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a11y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a11y.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not proven&lt;/span&gt;&lt;span class="sh"&gt;"&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;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry_allowed true while card is incomplete&lt;/span&gt;&lt;span class="sh"&gt;"&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;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;READY&lt;/span&gt;&lt;span class="sh"&gt;"&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml
python verify_retry_card.py decision-card.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is the word &lt;code&gt;READY&lt;/code&gt; and nothing else. &lt;code&gt;BLOCK&lt;/code&gt; means the retry stays closed. I paste the BLOCK list into the research note. That list is the missing evidence. It is not a backlog for a later sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hand the work back, then record the refusal
&lt;/h2&gt;

&lt;p&gt;Approval is not the only honest ending. Refusal is a product event with an owner. I write the hand-back in the same folder so the next loop cannot pretend it never happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HANDBACK
owner: product_design
state: retry_refused
reason: rewrite_announced false
next_human_action: restore last_human_edit
discarded_retry_prompt: archived, not deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why keep the discarded prompt in the record? Because the next loop will try it again. Memory without rejection is amnesia with a smile. Teams retry until the sentence sounds right. That is how consent copy becomes an ad.&lt;/p&gt;

&lt;p&gt;Verification is a named next action, not a vibe. Someone listed must own the restore. An unnamed owner is itself a stop. If the discarded prompt is deleted, I treat the record as broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research the retry click with three scenarios
&lt;/h2&gt;

&lt;p&gt;I run this as a scenario study, not a hallway poll. Three scenarios are enough for a rehearsal. Scenario A drops the consent refusal. Scenario B would overwrite the last human edit. Scenario C announces “Updated” without naming the field.&lt;/p&gt;

&lt;p&gt;For each scenario I measure two things only. Did the validator print BLOCK? Did the human refuse without extra chatter? Extra chatter is noise around a missing field. A missing &lt;code&gt;last_human_edit&lt;/code&gt; should stop the room. A missing model nickname should not.&lt;/p&gt;

&lt;p&gt;I do not count fluency of the second pass. I do not count minutes saved in critique. Those metrics hide the decision under a productivity story. The only success is a recoverable no. The only stop that matters is an unproven recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, and who should not use this
&lt;/h2&gt;

&lt;p&gt;This workflow is a gate in the interface. It is not a model evaluation harness. It will not rank providers. It will not prove a design is finished. The validator is a proposal you can fork. It has not been a controlled study.&lt;/p&gt;

&lt;p&gt;Do not use it for medical or legal advice surfaces. Do not use it when no designer owns the click. Do not use it to rubber-stamp a free critique. A cheap second pass is optional coverage. It is never evidence. If you cannot name the reversible point, stop. If you cannot name the user consequence, stop. The retry button can wait.&lt;/p&gt;

&lt;p&gt;I still want one question on your desk after the lint. Which missing evidence should stop approval today? Which extra field would only add noise to that card?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Source Proof Before Approving Memory Writes</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Fri, 11 Sep 2026 15:00:26 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-source-proof-before-approving-memory-writes-35a1</link>
      <guid>https://dev.to/haaaaaley/show-source-proof-before-approving-memory-writes-35a1</guid>
      <description>&lt;p&gt;A designer sits with a review card open. The agent wants to store one teammate preference. Shared memory will treat that line as durable truth. The source slot on the card is empty. Can you feel how cheap that yes looks?&lt;/p&gt;

&lt;p&gt;The decision owner is the designer on call. The consequence is a false fact other agents will quote. The point of reversibility sits before the write. After the write, cleanup becomes a rumor with no owner.&lt;/p&gt;

&lt;p&gt;This is a research protocol, not a shipping memoir. I label every example as unexecuted. I separate evidence from design hypotheses. I will not invent a customer, a metric, or a win.&lt;/p&gt;

&lt;p&gt;Sometimes I need a cheap rehearsal box for the card. I use MonkeyCode only as that box. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The operator-supplied claims I will use are two. Free model access is available for rehearsal. A free server option is available for the same. I do not invent model names here. I do not invent quotas, hardware, or duration. I do not invent benchmarks or permanence. Remove the product and this protocol still holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Name the write and the stop
&lt;/h2&gt;

&lt;p&gt;I start by naming the exact human decision. Who may let an agent write memory? What happens if that sentence is wrong? Where can a later teammate reverse it without theater?&lt;/p&gt;

&lt;p&gt;I write those three answers into a local file first. If any answer is fuzzy, I stop the whole rehearsal. Missing ownership is evidence. Charming copy is not evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; memory-review/&lt;span class="o"&gt;{&lt;/span&gt;cards,logs,fixtures&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; memory-review/decision.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Decision: approve or refuse one shared-memory write
Owner: on-call product designer
Consequence: other agents may quote this as fact
Reversal point: before persist, plus a named undo record
Stop if: source missing, date missing, or undo unnamed
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; memory-review/decision.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification for this stage is boring on purpose. The file must name owner, consequence, and reversal. If &lt;code&gt;wc&lt;/code&gt; returns a slogan instead, I rewrite it. Would you approve a write with no owner on the card?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Build the review card, not the memory
&lt;/h2&gt;

&lt;p&gt;I do not let the agent draft the memory first. I freeze a card with empty evidence slots. Empty slots are the interface. Filled prose is a later privilege.&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="c1"&gt;# memory-review/cards/review-card.template.yml&lt;/span&gt;
&lt;span class="na"&gt;write_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;proposed_sentence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;teammates_and_later_agents&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source_url_or_doc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;source_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;quote_span&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;last_human_who_confirmed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;recovery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;undo_command_or_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;discarded_drafts_log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs/discarded.md"&lt;/span&gt;
&lt;span class="na"&gt;accessibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;card_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review"&lt;/span&gt;
  &lt;span class="na"&gt;stop_reason_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;hypotheses_not_evidence&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;"&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blocked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copy the template for one scenario. I leave every evidence field blank on purpose. The first verification is visual and mechanical. Blank must look like blank, not like soft gray hope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;memory-review/cards/review-card.template.yml &lt;span class="se"&gt;\&lt;/span&gt;
  memory-review/cards/scenario-preference.yml
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'""'&lt;/span&gt; memory-review/cards/scenario-preference.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;grep&lt;/code&gt; finds no empty quotes, the card already lied. I throw it away. Why would we hide the missing source inside pretty defaults?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Fail closed before any model speaks
&lt;/h2&gt;

&lt;p&gt;I add a small validator that refuses incomplete cards. This is not production software. It is a stop condition you can run. Treat it as proposed code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# memory-review/validate_card.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_url_or_doc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quote_span&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_human_who_confirmed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;undo_command_or_path&lt;/span&gt;&lt;span class="sh"&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;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;card is not a mapping&lt;/span&gt;&lt;span class="sh"&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;data&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&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;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_sentence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_sentence&lt;/span&gt;&lt;span class="sh"&gt;"&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;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP. Missing evidence:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP. Status cannot start as approved.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CARD INCOMPLETE-OK: required slots still block write&lt;/span&gt;&lt;span class="sh"&gt;"&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 memory-review/validate_card.py &lt;span class="se"&gt;\&lt;/span&gt;
  memory-review/cards/scenario-preference.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:$"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is the non-zero exit. A passing validator on an empty card would be a bug. I want the stop to be louder than the model. Does your current agent UI fail this loudly?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Fixture the dangerous sentence
&lt;/h2&gt;

&lt;p&gt;I keep the first rehearsal away from live teammates. I write a fixture that looks tempting and under-sourced. The agent will love it. That is the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; memory-review/fixtures/preference.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "proposed_sentence": "Alex prefers terse standups and hates written recap.",
  "claimed_source": "someone said it last week",
  "quote_span": "",
  "source_date": ""
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
from pathlib import Path
p = json.loads(Path("memory-review/fixtures/preference.json").read_text())
assert p["quote_span"] == ""
assert p["source_date"] == ""
print("fixture is unsafe, as intended")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fixture is a research scenario, not a person. I do not claim Alex exists on my team. I need a sentence that would spread if approved. Would you let that line become shared knowledge tonight?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Rehearse the model against the card
&lt;/h2&gt;

&lt;p&gt;I now allow a model to fill a draft card. I still forbid persist. The model may propose text. The model may not change &lt;code&gt;status&lt;/code&gt;. That boundary is the whole tutorial.&lt;/p&gt;

&lt;p&gt;If you rehearse on a free server, keep it isolated. Do not point it at real memory. Do not paste secrets into the prompt. The free model is a pressure tool. It is not a witness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; memory-review/prompts/fill-card.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Fill review-card fields from the fixture only.
Do not invent a source_url_or_doc.
Do not invent source_date or quote_span.
If a required field is unknown, leave it empty.
Never set status to approved.
Return YAML only.
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Proposed rehearsal only. Swap in your isolated runner.&lt;/span&gt;
&lt;span class="c"&gt;# Keep network away from production memory stores.&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
card = Path("memory-review/cards/review-card.template.yml").read_text()
fix = Path("memory-review/fixtures/preference.json").read_text()
prompt = Path("memory-review/prompts/fill-card.txt").read_text()
Path("memory-review/logs/model-input.md").write_text(
    "# Model input&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;" + prompt + "&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;## card&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;" + card +
    "&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;## fixture&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;" + fix
)
print("model input frozen for review")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification here is the frozen input log. I read it before any call. If the prompt asks the model to be helpful, I rewrite it. Helpful is how empty sources get invented. Do you want a helpful liar in shared memory?&lt;/p&gt;

&lt;p&gt;After the model returns YAML, I save it as a draft, not a write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Save whatever the model returned, then force a second validate.&lt;/span&gt;
&lt;span class="c"&gt;# Example path only. Replace with your isolated output file.&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;memory-review/cards/scenario-preference.yml &lt;span class="se"&gt;\&lt;/span&gt;
  memory-review/cards/scenario-preference.draft.yml
python3 memory-review/validate_card.py &lt;span class="se"&gt;\&lt;/span&gt;
  memory-review/cards/scenario-preference.draft.yml &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the draft still misses source, date, quote, or undo, I keep &lt;code&gt;status: blocked&lt;/code&gt;. I do not negotiate with the sentence. The sentence is not the evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Record discarded drafts on purpose
&lt;/h2&gt;

&lt;p&gt;Approved memory without rejected wording is a false archive. I keep the discarded line in a concrete record. Later agents should see what we refused. Otherwise they will offer the same rumor again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; memory-review/logs/discarded.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
## write_id: scenario-preference
Refused sentence: Alex prefers terse standups and hates written recap.
Missing: source_url_or_doc, source_date, quote_span
Human: blocked persist
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Refused sentence"&lt;/span&gt; memory-review/logs/discarded.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a count greater than zero after a refusal. A clean log after a stop is a design smell. We did not fail privately. We failed on the record. Where do your discarded agent claims live today?&lt;/p&gt;

&lt;h2&gt;
  
  
  User flow I actually test
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Agent proposes a memory write] --&amp;gt; B[Render review card with empty slots]
  B --&amp;gt; C{Required evidence present?}
  C --&amp;gt;|No| D[Stop and log discarded draft]
  C --&amp;gt;|Yes| E{Source is primary and dated?}
  E --&amp;gt;|No| D
  E --&amp;gt;|Yes| F{Undo path named and readable?}
  F --&amp;gt;|No| D
  F --&amp;gt;|Yes| G[Human edits or approves]
  G --&amp;gt; H[Persist write plus discarded record]
  D --&amp;gt; I[Hand back to human with stop text]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;I walk this flow with a keyboard only. I do not trust a demo click. If stop text exists only in color, the flow is incomplete. The hand-back is part of the product. Silence is not a recovery pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research scenarios and stop measures
&lt;/h2&gt;

&lt;p&gt;I run three scenarios on the same card. I do not mix them in one sitting. Each scenario has a success measure and a stop measure. I write those before I look at model output.&lt;/p&gt;

&lt;p&gt;Scenario A is an under-sourced preference, like the fixture. Success means the human never sees an Approve shortcut. Stop means persist is unreachable while source slots are empty.&lt;/p&gt;

&lt;p&gt;Scenario B is a sourced but stale note. The date is older than the team agreed. Success means the card shows age in text, not tint. Stop means approval stays blocked until a human reconfirms.&lt;/p&gt;

&lt;p&gt;Scenario C is a well-sourced sentence with no undo path. Success means recovery is a required field. Stop means a beautiful quote cannot bypass undo. Which of those stops would your current agent ignore?&lt;/p&gt;

&lt;p&gt;I collect coverage, not vibes. Did the empty slot stay empty under pressure? Did the model invent a URL? Did the reviewer try to fill noise instead? Invented URLs are a stop. Extra biography of the teammate is noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility review of the card itself
&lt;/h2&gt;

&lt;p&gt;The card is an interface, not a screenshot. It needs a name, a stop reason, and a live rejection. Color cannot be the only blocked signal. Iconography cannot be the only undo signal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; memory-review/a11y-check.md &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Name: Memory write review, not Agent suggestion
Role: region or form, announced as blocking
Stop text: plain language, not red alone
Undo: text path plus control name
Focus: first empty required slot, not the model prose
Motion: no auto-advance toward Approve
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then rehearse with a screen reader if I have one. If I do not, I still refuse color-only stops. I read the card aloud. If Approve sounds easier than Stop, the copy failed. Who is this card actually for, the model or the human?&lt;/p&gt;

&lt;p&gt;Proposed interface fields stay small. &lt;code&gt;source_url_or_doc&lt;/code&gt;. &lt;code&gt;source_date&lt;/code&gt;. &lt;code&gt;quote_span&lt;/code&gt;. &lt;code&gt;last_human_who_confirmed&lt;/code&gt;. &lt;code&gt;undo_command_or_path&lt;/code&gt;. &lt;code&gt;stop_reason_text&lt;/code&gt;. Extra model confidence scores are noise on this card. Extra personality sliders are noise. Missing quote span should stop approval. Another synonym for the same sentence should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence versus hypotheses
&lt;/h2&gt;

&lt;p&gt;Evidence is a dated primary source and a quote span. Evidence is a named human who confirmed it. Evidence is an undo path that a teammate can follow. Hypotheses are tone, usefulness, and team morale. Hypotheses do not unlock persist.&lt;/p&gt;

&lt;p&gt;I keep hypotheses in their own YAML list. I never copy them into evidence. If a rehearsal transcript feels convincing, I label it hypothesis. A free-model run is not field research. A free server is not production traffic. Do not launder either into a finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery after a bad yes
&lt;/h2&gt;

&lt;p&gt;If a write already landed, the protocol changes shape. I still do not let the agent overwrite quietly. I require a correction card with the old sentence visible. Discarded truth matters as much as new truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORRECTION CARD
Old sentence: ...
Old source: ... or MISSING
New sentence: ...
New source: required
Undo of the undo: named
Notify: who already consumed the old fact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is notification, not just a new row. Shared memory that nobody rereads is a trap. Who already trusted the wrong line? If I cannot answer, I have not recovered. I have only decorated the log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and who should not use this
&lt;/h2&gt;

&lt;p&gt;This tutorial does not prove a model is truthful. It only proves a human saw empty slots. It does not replace legal review, security review, or consent review. It does not measure model quality. It measures whether persist is reachable too early.&lt;/p&gt;

&lt;p&gt;Do not use this as a substitute for real user research. Do not use it on medical, legal, or financial facts without the named owner for those domains. Do not use it if no human will read the card. An unattended free model on an unattended server is not oversight. It is a loop with a costume.&lt;/p&gt;

&lt;p&gt;Teams without a reversibility path should not store agent prose as memory. Teams that cannot name a decision owner should not run Stage 4. If you need speed more than a source, you do not need shared memory. You need a scratch pad that expires.&lt;/p&gt;

&lt;p&gt;I end on the same question I started with. Which missing evidence should stop approval on your card? Which extra information would only add noise around a bad yes? If you need a cheap isolated box to rehearse those stops, MonkeyCode's free model access and free server option can host the validator. Treat that run as practice. Never treat it as proof.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Tab Order Proof Before Approving Agent States</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Thu, 10 Sep 2026 04:00:42 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-tab-order-proof-before-approving-agent-states-2606</link>
      <guid>https://dev.to/haaaaaley/show-tab-order-proof-before-approving-agent-states-2606</guid>
      <description>&lt;p&gt;I sat a designer in front of one review card. An agent had rewritten a hover state overnight. The card asked for a yes. Tab order proof was empty. Who owns that yes? The designer does. The consequence is a keyboard trap in production. The point of reversibility is this card, not a later hotfix.&lt;/p&gt;

&lt;p&gt;This is a from-zero research tutorial. It is a protocol, not a shipped study. I label every check as unexecuted until you run it. I will not treat a fluent agent as a witness. Fluency is not evidence.&lt;/p&gt;

&lt;p&gt;Why this decision, and not another? Agents love visual states. They describe hover, press, and selected. They forget the path a Tab key must walk. A pretty state with no focus proof is a locked door. Would you approve a door you cannot open?&lt;/p&gt;

&lt;h2&gt;
  
  
  Name the decision before you name the tool
&lt;/h2&gt;

&lt;p&gt;Write the decision in one sentence. Keep the owner visible. Keep the stop condition visible. I use this line and nothing softer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DECISION: May this agent state ship to the design system?
OWNER: product designer on the review card
CONSEQUENCE: keyboard users lose a recoverable path
REVERSIBILITY: reject now; do not patch after merge
STOP IF: tab_order_proof is empty OR focus_visible is unproven
NOISE IF: model confidence, token spend, or poetic rationale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage. Read the line aloud. If you cannot name the owner, stop. If you cannot name the stop, stop. A tool cannot rescue a nameless decision.&lt;/p&gt;

&lt;p&gt;I rehearse the protocol on a cheap bed. MonkeyCode offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I do not need a named model. I do not need a quota story. I need a place to fail the card in public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: scaffold a local review record
&lt;/h2&gt;

&lt;p&gt;Create a folder. Treat it as the only source of truth. Do not keep proof in chat scrollback. Chat is a hallway. Hallways forget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; agent-state-review/records agent-state-review/fixtures
&lt;span class="nb"&gt;cd &lt;/span&gt;agent-state-review
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'review protocol v1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; README.txt
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage. &lt;code&gt;ls&lt;/code&gt; must show &lt;code&gt;records&lt;/code&gt; and &lt;code&gt;fixtures&lt;/code&gt;. If a file lives only in a model window, it does not exist. Would you defend that file in an incident review?&lt;/p&gt;

&lt;p&gt;Now write the card schema. These fields are interface copy, not decoration. Evidence stays separate from guesses.&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;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FilterChip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proposed_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;"hover-and-selected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"tab_order_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"from_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"to_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"escape_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"focus_visible_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contrast_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"not_obscured_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"hand_back"&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;"needed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"hypothesis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&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;Save it as &lt;code&gt;records/card.schema.json&lt;/code&gt;. Verify the keys exist with a dry read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
from pathlib import Path
card = json.loads(Path('records/card.schema.json').read_text())
need = ['decision_owner','tab_order_proof','focus_visible_proof','hand_back','hypothesis','approval']
missing = [k for k in need if k not in card]
print('missing', missing or 'none')
print('approval', card['approval'])
assert card['approval'] == 'blocked'
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that assert fails, your default is already too kind. Kind defaults leak traps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: fill a failed scenario, not a happy path
&lt;/h2&gt;

&lt;p&gt;Happy paths teach almost nothing. I seed a chip that looks selected on hover. The agent wrote a glow. It never said where Tab lands. It never said how Escape returns. That silence is the finding.&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;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FilterChip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proposed_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;"hover-and-selected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-designer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tab_order_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"from_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"to_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"escape_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"focus_visible_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contrast_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent described a glow only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"not_obscured_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"hand_back"&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;"needed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no keyboard path, no unobscured focus"&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;"hypothesis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users will understand selection from color alone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&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;Save as &lt;code&gt;fixtures/filterchip-hover-selected.failed.json&lt;/code&gt;. Notice the split. The hypothesis can be poetic. The proof cannot. Color is not a path. A glow is not a focus ring.&lt;/p&gt;

&lt;p&gt;Verify this stage with a diff of statuses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
from pathlib import Path
fx = json.loads(Path('fixtures/filterchip-hover-selected.failed.json').read_text())
print('tab', fx['tab_order_proof']['status'])
print('focus', fx['focus_visible_proof']['status'])
print('hand_back', fx['hand_back']['needed'])
assert fx['tab_order_proof']['status'] == 'empty'
assert fx['approval'] == 'blocked'
print('failed scenario is honest')
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the fixture already says approved, you staged theater. Theater is not research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: encode stop conditions as a checker
&lt;/h2&gt;

&lt;p&gt;People forgive empty fields under time pressure. A checker does not. This script is a proposed gate. Label it unexecuted until you run it on your card.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# check_review_card.py — protocol checker, not a product claim
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;NOISE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rationale_poem&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop_reasons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tab_order_proof&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;focus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;focus_visible_proof&lt;/span&gt;&lt;span class="sh"&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision_owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no decision owner&lt;/span&gt;&lt;span class="sh"&gt;"&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;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proven&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tab order unproven&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;escape_control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no escape control&lt;/span&gt;&lt;span class="sh"&gt;"&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;focus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proven&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;focus visible unproven&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_obscured_note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;focus may be obscured&lt;/span&gt;&lt;span class="sh"&gt;"&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;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approval contradicts empty proof&lt;/span&gt;&lt;span class="sh"&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;reasons&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stop_reasons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;component&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;component&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hypothesis_is_not_evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hypothesis&lt;/span&gt;&lt;span class="sh"&gt;"&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;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP&lt;/span&gt;&lt;span class="sh"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASS: proof present, still needs a human yes&lt;/span&gt;&lt;span class="sh"&gt;"&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on the failed fixture. Expect a stop. A green check here would be a bug in the protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 check_review_card.py fixtures/filterchip-hover-selected.failed.json
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage. Exit code must be 2. The word STOP must print. If you only read the hypothesis, you approved a rumor.&lt;/p&gt;

&lt;p&gt;What extra fields would only add noise? Model confidence. Token spend. A paragraph about being helpful. Those fields soothe the reviewer. They do not move a Tab key. I keep them out of the card on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: walk the user flow, then the keyboard flow
&lt;/h2&gt;

&lt;p&gt;The visual flow is the easy story. A person clicks a chip. The chip glows. Filters update. That story is incomplete. The keyboard flow is the real product.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Reviewer opens agent state card] --&amp;gt; B{tab_order_proof proven?}
  B --&amp;gt;|no| C[Stop. Hand back to designer]
  B --&amp;gt;|yes| D{focus visible and unobscured?}
  D --&amp;gt;|no| C
  D --&amp;gt;|yes| E{escape control named?}
  E --&amp;gt;|no| C
  E --&amp;gt;|yes| F[Human may approve or still refuse]
  C --&amp;gt; G[Agent may retry only inside the card]
  F --&amp;gt; H[Record discarded paths beside the yes]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Read the diagram as a conversation. The agent speaks first. The card interrupts. The human speaks last. If the human speaks first, you have theater again.&lt;/p&gt;

&lt;p&gt;I still need a keyboard script. This is a research script, not a browser driver. A facilitator reads it. A participant uses only Tab, Shift+Tab, Enter, and Escape. No mouse. No trackpad. No charity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENARIO: Approve or reject FilterChip hover-and-selected
START: focus on the search field before the chip row
TASK: select Cuisine, then leave the row without a mouse
SUCCESS: chip is selected, focus ring visible, escape returns to search
STOP: focus disappears, order skips the chip, or escape does nothing
DO NOT HINT: do not point at the glow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage by timing silence. If the facilitator explains the glow, the scenario is contaminated. Contaminated runs cannot support approval.&lt;/p&gt;

&lt;p&gt;Which missing evidence should stop approval? A blank &lt;code&gt;from_control&lt;/code&gt;. A blank &lt;code&gt;escape_control&lt;/code&gt;. A focus ring that a sticky header covers. Which extra information only adds noise? Screenshots of hover without focus. A transcript of the model apologizing. An aesthetic preference from a sighted reviewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: accessibility review as a gate, not a garnish
&lt;/h2&gt;

&lt;p&gt;I ground the gate in published criteria, not vibes. &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html" rel="noopener noreferrer"&gt;WCAG 2.2 Success Criterion 2.4.7 Focus Visible&lt;/a&gt; asks that the keyboard focus indicator can be seen. &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html" rel="noopener noreferrer"&gt;WCAG 2.2 Success Criterion 2.4.11 Focus Not Obscured (Minimum)&lt;/a&gt; asks that the focused item is not entirely hidden. Those pages are the primary sources. I am not inventing a third rule.&lt;/p&gt;

&lt;p&gt;Map each criterion onto a card field. Then refuse to merge on a mapped miss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.4.7  -&amp;gt; focus_visible_proof.status must equal proven
2.4.11 -&amp;gt; focus_visible_proof.not_obscured_note must name the overlapping chrome
2.1.1  -&amp;gt; tab_order_proof must name from, to, and escape
recovery -&amp;gt; hand_back.reason must stay in the record after a retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A proposed pass fixture looks like this. It is still not an approval. Proof only unlocks a human decision.&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;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FilterChip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proposed_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;"hover-and-selected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-designer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tab_order_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"from_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SearchInput"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"to_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FilterChip:Cuisine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"escape_control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SearchInput"&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;"focus_visible_proof"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contrast_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2px ring against surface, not hue-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"not_obscured_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ring clears sticky filter header"&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;"hand_back"&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;"needed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyboard path demonstrated in scenario"&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;"hypothesis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users will understand selection from color alone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&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;See the last field? I still keep &lt;code&gt;approval&lt;/code&gt; blocked. The checker can pass proof and still refuse to ship. A human must type the yes. Why? Because proof of a path is not proof of the right path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 check_review_card.py fixtures/filterchip-hover-selected.proof.json
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit:&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage. Exit code should be 0 only when proof fields are complete. &lt;code&gt;approval&lt;/code&gt; may remain blocked. That tension is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: hand-back and keep the discarded path
&lt;/h2&gt;

&lt;p&gt;When the checker stops, the agent does not get the canvas. It gets the card. The designer writes the missing path in the same record. The failed order stays visible. Discarded paths are not shame. They are the map of near misses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HAND_BACK
- agent may fill tab_order_proof only
- agent may not flip approval
- designer may accept, rewrite, or kill the state
- discarded order remains under records/history/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the failed fixture into history before any retry. If you overwrite, you teach the team to hide bruises.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; records/history
&lt;span class="nb"&gt;cp &lt;/span&gt;fixtures/filterchip-hover-selected.failed.json &lt;span class="se"&gt;\&lt;/span&gt;
   records/history/filterchip-hover-selected.failed.001.json
&lt;span class="nb"&gt;ls &lt;/span&gt;records/history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify this stage. History must contain the empty proof. A later pass without that file is amnesia. Amnesia ships the same trap twice.&lt;/p&gt;

&lt;p&gt;The recovery frame is small on purpose. Stop. Show the blank slots. Hand the work back. Keep the bruise. Resume only inside the card. That is the whole loop. It is closer to a coat check than a launch sequence. You do not walk out wearing a coat you never claimed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this protocol is not
&lt;/h2&gt;

&lt;p&gt;It is not a benchmark of free models. I did not time tokens. I did not rank vendors. It is not a frontend implementation guide. Component code belongs elsewhere. It is not a claim that a free server makes states accessible. A server hosts the rehearsal. It does not see for you.&lt;/p&gt;

&lt;p&gt;Who should not use this approach? Teams that already merge agent states from chat. Teams that treat confidence as contrast. Teams that cannot name a decision owner. Teams that need statistical usability claims. This tutorial does not produce those claims. It produces a stop.&lt;/p&gt;

&lt;p&gt;Limitations sit in the open. The checker cannot detect a real obscured ring. A human still looks. The scenario cannot cover every control. You still sample. The free rehearsal bed can vanish or change. I do not claim duration, hardware, or permanence. If the bed moves, the card schema still travels. That is the point of putting proof in files.&lt;/p&gt;

&lt;p&gt;I keep asking the same two questions at the end of every review. Which missing evidence should stop approval? Which extra information would only add noise? If your room cannot answer both, you are not ready for an agent to touch state. You are ready to keep the card blank, and that is honest work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Assumed Account Facts Before Sending Agent Replies</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:14:10 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-assumed-account-facts-before-sending-agent-replies-2187</link>
      <guid>https://dev.to/haaaaaley/show-assumed-account-facts-before-sending-agent-replies-2187</guid>
      <description>&lt;p&gt;The refund sat in the queue for twelve seconds. The agent had already named the plan. Nobody on the call had said Pro. Would you have caught that?&lt;/p&gt;

&lt;p&gt;I almost approved the send. My eye went to tone, not facts. The draft sounded kind. It also invented a billing tier. That invention is the decision, not the prose.&lt;/p&gt;

&lt;p&gt;This is a from-zero research tutorial. You will build a stop card. You will run three scenarios. You will verify each stage before moving. No study metrics hide in this piece. I am walking a protocol, not reporting a trial.&lt;/p&gt;

&lt;p&gt;Think of the agent like a helpful intern with a stamp. The intern fills blanks to look finished. Your job is the stamp, not the handwriting. If a blank is still a blank, the stamp stays in the drawer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Name the consequential send
&lt;/h2&gt;

&lt;p&gt;Pick one outbound action. I use a support reply that can move money. The decision owner is the human reviewer. The consequence is a false promise in writing. The reversibility point is before Send, not after delivery.&lt;/p&gt;

&lt;p&gt;Write that in one file. Keep the sentence ugly and specific.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; agent-review-lab
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/decision.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Decision: approve or block a support reply that mentions plan, refund, or access.
Owner: human reviewer on the queue.
Consequence: customer receives an untrue account fact.
Reversal: only while the reply is unsent.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; agent-review-lab/decision.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is boring on purpose. You should see four short lines. If the file names a vibe instead of a send, stop. Rewrite until a lawyer could point at the verb.&lt;/p&gt;

&lt;p&gt;What missing evidence should freeze that stamp? For this lab, freeze on plan name, refund amount, and access state. Extra color about tone is noise. Do you really need a sentiment score to block a fake Pro plan?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Build the evidence card, not the copy
&lt;/h2&gt;

&lt;p&gt;I do not start in a chat window. I start with empty slots. The card is the interface. Copy is a downstream guess.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/evidence-card.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "thread_id": "T-1842",
  "customer_visible_action": "send_support_reply",
  "slots": {
    "plan_name": {"value": null, "source": "empty", "stop_if_empty": true},
    "refund_amount": {"value": null, "source": "empty", "stop_if_empty": true},
    "access_state": {"value": null, "source": "empty", "stop_if_empty": true},
    "customer_goal": {"value": null, "source": "empty", "stop_if_empty": false}
  },
  "agent_draft": null,
  "reviewer_decision": "blocked_pending_evidence"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool agent-review-lab/evidence-card.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"card ok"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;card ok&lt;/code&gt;. If JSON fails, the card cannot travel. Fix the file before any model sees it. An unreadable card is itself a stop condition.&lt;/p&gt;

&lt;p&gt;Notice the analogy. The card is a coat check ticket. The draft is a coat. You do not hand over a coat without the ticket numbers matching. A pretty coat does not replace a missing number.&lt;/p&gt;

&lt;p&gt;Hypothesis, labeled as such: reviewers miss invented plan names when the tone is warm. Evidence, labeled as such: we have none yet. This file only encodes the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Rehearse drafts in a cheap room
&lt;/h2&gt;

&lt;p&gt;You need a place to generate bad fills without burning a production queue. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode here only as a rehearsal room with free model access and a free server option. I am not claiming model names, quotas, or speed.&lt;/p&gt;

&lt;p&gt;Keep the product off the decision path. The server drafts. The card decides. If you cannot separate those, do not run the lab on customer traffic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/prompt-stub.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Write a support reply for thread T-1842.
Do not invent plan_name, refund_amount, or access_state.
If a slot is empty, keep it empty in a leading evidence block.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; agent-review-lab/prompt-stub.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: three instruction lines, no sample plan names. If your stub contains Pro, Team, or a dollar amount, you already poisoned the well. Delete those words. The intern copies whatever you leave on the desk.&lt;/p&gt;

&lt;p&gt;Paste the stub into your rehearsal room. Save whatever comes back as a file. Do not paste it into the real queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Replace the draft file with the model output you actually received.&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/draft-01.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
EVIDENCE
plan_name: EMPTY
refund_amount: EMPTY
access_state: EMPTY
REPLY
I can look at the charge once we confirm the plan on the account.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"EMPTY"&lt;/span&gt; agent-review-lab/draft-01.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want a count of three. Zero empty markers means the intern filled the coat check. That draft fails Stage 2 even if it sounds careful. Would you let a silent fill reach Send?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Run three scenarios, then halt
&lt;/h2&gt;

&lt;p&gt;I use three scenes, not a survey. Each scene is a customer thread with one hole. You will mark stop or proceed after each. Do not average them into a score yet.&lt;/p&gt;

&lt;p&gt;Scene A is a refund ask with no amount in the transcript. Scene B is an access ask with no current state. Scene C is a tone-only thank you with every slot still empty. Scene C is the trap. It feels harmless. It still cannot name a plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/scenarios.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[
  {"id": "A", "hole": "refund_amount", "transcript_has_plan": false},
  {"id": "B", "hole": "access_state", "transcript_has_plan": false},
  {"id": "C", "hole": "all_stop_slots", "transcript_has_plan": false}
]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
from pathlib import Path
rows = json.loads(Path("agent-review-lab/scenarios.json").read_text())
assert len(rows) == 3
assert all(r["transcript_has_plan"] is False for r in rows)
print("scenarios ready")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should print &lt;code&gt;scenarios ready&lt;/code&gt;. If any scene claims the transcript already named the plan, you are testing reading, not invention. That is a different study. Kill the scene.&lt;/p&gt;

&lt;p&gt;Now watch one reviewer, maybe yourself. Start a timer only to keep the session honest. You are not measuring speed as success. You are watching whether the missing slot is spoken aloud before Send.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User flow I actually want on screen

[Queue item] -&amp;gt; [Evidence card with empty slots visible]
    -&amp;gt; [Agent draft beneath the card, never above it]
    -&amp;gt; [Stop if any stop_if_empty slot is empty]
    -&amp;gt; [Human names the hole or supplies a source]
    -&amp;gt; [Send] or [Hand back to agent with the hole named]
    -&amp;gt; [If sent in error, recovery note stays on the thread]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  q[Queue item] --&amp;gt; card[Show evidence card first]
  card --&amp;gt; draft[Show agent draft second]
  draft --&amp;gt; check{Any stop slot empty?}
  check --&amp;gt;|yes| block[Block send and name the hole]
  check --&amp;gt;|no| source{Does every filled slot have a source?}
  source --&amp;gt;|no| block
  source --&amp;gt;|yes| send[Allow send]
  block --&amp;gt; handback[Hand back with the named hole]
  send --&amp;gt; record[Keep the card on the thread]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If the draft sits above the card, the coat hides the ticket. Move it. This is a layout hypothesis. Evidence for it is still only this inspection, not a study.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Score stops, not eloquence
&lt;/h2&gt;

&lt;p&gt;Success is not a nicer paragraph. Success is a spoken hole. Stop is also a result. A stop that names &lt;code&gt;refund_amount&lt;/code&gt; is a pass. A send that invents &lt;code&gt;$49&lt;/code&gt; is a fail. A long delay with no named hole is a fail too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/score.py &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
import json, sys
from pathlib import Path

card = json.loads(Path(sys.argv[1]).read_text())
stops = []
for name, slot in card["slots"].items():
    if slot.get("stop_if_empty") and (slot.get("value") in (None, "", "EMPTY")):
        stops.append(name)
decision = card.get("reviewer_decision")
if stops and decision == "send":
    print("FAIL invented-or-ignored-empty:", ",".join(stops))
    sys.exit(1)
if stops and decision.startswith("blocked"):
    print("PASS named-stop:", ",".join(stops))
    sys.exit(0)
if not stops and decision == "send":
    print("PASS sourced-send")
    sys.exit(0)
print("FAIL unclear-decision")
sys.exit(1)
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 agent-review-lab/score.py agent-review-lab/evidence-card.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the empty starter card you should see a PASS for a named stop. If you flipped the decision to send without filling slots, the script must fail. Run it after every scene. Do not keep a mental tally. Memory is where invented Pro plans live.&lt;/p&gt;

&lt;p&gt;Which extra field would only add noise? I leave &lt;code&gt;customer_goal&lt;/code&gt; optional. It helps empathy. It does not prove the plan. If your team argues about empathy copy while &lt;code&gt;plan_name&lt;/code&gt; is empty, the ritual already failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Accessibility of the stop, not the sparkle
&lt;/h2&gt;

&lt;p&gt;A stop that exists only as a red gradient is not a stop. I check the card like a form, because it is a form. The reviewer may be on a screen reader, a zoomed view, or a keyboard-only queue.&lt;/p&gt;

&lt;p&gt;Write the review surface as text first. Buttons need names that say the hole. &lt;code&gt;Approve&lt;/code&gt; is a vague verb. &lt;code&gt;Send anyway with empty plan_name&lt;/code&gt; is honest. Honest names slow the wrong click. That slowness is the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- labeled pattern, not production UI --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"card-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"card-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Evidence required before send&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Plan name: empty. This slot blocks send.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Refund amount: empty. This slot blocks send.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Access state: empty. This slot blocks send.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Block send and name the empty slots&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send is unavailable while slots are empty&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a read-aloud. Sit with the HTML in a browser. Tab once to the block control. Confirm Send is not the first focus. If Send is first, you designed a trap. Fix the order before you decorate anything.&lt;/p&gt;

&lt;p&gt;Also check contrast on the word &lt;code&gt;empty&lt;/code&gt;. Empty must be a word, not a pale placeholder. Placeholders vanish. Vanishing evidence is how the intern wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Hand back and keep the discarded fill
&lt;/h2&gt;

&lt;p&gt;When you block, do not wipe the invented text. Keep it in a discarded well. Future you needs to see the lie the model wanted to tell. Deleting it trains the team to forget the failure mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; agent-review-lab/handback.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "thread_id": "T-1842",
  "named_holes": ["plan_name", "refund_amount", "access_state"],
  "discarded_agent_fill": {
    "plan_name": "Pro",
    "note": "unproven fill, not shown to customer"
  },
  "next_owner": "agent_with_holes_named",
  "customer_copy_status": "unsent"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool agent-review-lab/handback.json | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should still see &lt;code&gt;unsent&lt;/code&gt;. If that field ever flips while holes remain, treat it as an incident. Recovery is a visible note on the thread, not a quiet edit. The customer may have seen nothing. Your future reviewer still needs the scar.&lt;/p&gt;

&lt;p&gt;Failure recovery, in one breath: unsend if the channel allows it, correct the account fact in the next human message, and keep the discarded fill attached to the card. Do not hide the intern's stamp. Hiding it repeats the twelve-second almost-send.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this lab cannot claim
&lt;/h2&gt;

&lt;p&gt;This protocol does not rank models. It does not prove trust. It does not replace policy for legal, medical, or credit decisions. It only tests whether a human can see an empty slot before a send.&lt;/p&gt;

&lt;p&gt;Skip this approach if you have no human on the queue. Skip it if Send cannot be disabled. Skip it if your channel has no reversibility and you still want the agent to name money. Those teams need a harder gate than a research card.&lt;/p&gt;

&lt;p&gt;I also will not dress a hypothesis as a finding. Warm tone may hide invented plans. That is a guess I still need scenes to challenge. The files above are the challenge, not the answer.&lt;/p&gt;

&lt;p&gt;So, what freeze rule will you keep? I keep this one. If &lt;code&gt;plan_name&lt;/code&gt; has no source, the stamp stays down. Everything else can wait. The intern can keep talking in the rehearsal room. The customer should not hear a plan nobody spoke.&lt;/p&gt;

&lt;p&gt;If you need a cheap room to practice the card before a real queue, MonkeyCode's free models on the free server are enough for that rehearsal. Bring the stop file with you. Leave the invented Pro plan in the discarded well.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>a11y</category>
      <category>design</category>
    </item>
    <item>
      <title>Gate Agent Landmarks Until the Skip Path Is Proven</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sun, 06 Sep 2026 20:20:42 +0000</pubDate>
      <link>https://dev.to/haaaaaley/gate-agent-landmarks-until-the-skip-path-is-proven-c0o</link>
      <guid>https://dev.to/haaaaaley/gate-agent-landmarks-until-the-skip-path-is-proven-c0o</guid>
      <description>&lt;p&gt;Last Tuesday I almost shipped a skip link into nowhere. The agent guessed three landmarks from a static mock. I owned the approve click. Checkout sat one skip away from vanishing. Could I reverse the rewrite after publish? No. That missing reverse path is the whole story.&lt;/p&gt;

&lt;p&gt;This is not an essay about trusting models. It is one consequential decision. I am the decision owner. The consequence is a keyboard user leaping past pay. The point of reversibility must exist before I click approve. Visual layout is a poster. Landmarks are a trail. Why would a model blaze that trail from pixels?&lt;/p&gt;

&lt;p&gt;I now run a from-zero gate before any agent rewrites navigation. Treat this as a proposed tutorial, not a finished study. Each stage has a command and a check. Fail a check, stop. Do not negotiate with an empty slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1 — write the decision card
&lt;/h2&gt;

&lt;p&gt;I refuse to open the model until the card exists. The card is dull on purpose. Dull cards catch glamorous guesses. Save it as &lt;code&gt;gate/decision-card.json&lt;/code&gt;.&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;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approve_inferred_landmarks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"design_lead_on_call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rewrite_landmarks_and_skip_link"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consequence_if_wrong"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sr_and_keyboard_users_skip_past_checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reversibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"restore_original_map_from_review_record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_required"&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="s2"&gt;"original_landmark_map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proposed_landmark_map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"skip_path_transcript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"primary_task_success"&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;"empty_slots_block_approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hypotheses_are_not_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Verification: read the file aloud. Can you name the owner in one breath? Can you name the harm? Can you name the restore handle? If any answer turns poetic, rewrite the card. Do not proceed. Poetry is not a control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2 — draw only the human click
&lt;/h2&gt;

&lt;p&gt;I sketch the moment a person must decide. I do not sketch the model's inner loop. The agent may draft. The human still owns publish. Here is the flow I actually use.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Agent proposes landmark rewrite] --&amp;gt; B{Evidence file complete?}
  B --&amp;gt;|No| C[Hold publish and show empty slots]
  B --&amp;gt;|Yes| D[Human runs skip-path rehearsal]
  D --&amp;gt; E{Primary task reachable in two skips?}
  E --&amp;gt;|No| F[Reject and keep original map]
  E --&amp;gt;|Yes| G[Approve with restore handle]
  F --&amp;gt; H[Log discarded inference in review record]
  G --&amp;gt; H&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Verification: put your finger on the diamond that stops publish. If your drawing has no stop, you drew a pipeline. Pipelines do not protect checkout. Would you board a train with no brake lever?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3 — write two scenarios, not ten
&lt;/h2&gt;

&lt;p&gt;I keep research small so the gate stays honest. Scenario A is the proven skip. Scenario B is the inferred footer. Both live in &lt;code&gt;gate/scenarios.md&lt;/code&gt;. The file is the protocol. It is not a slide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Landmark gate scenarios&lt;/span&gt;

&lt;span class="gu"&gt;## A — proven skip&lt;/span&gt;
Task: from article body, reach checkout.
Success: two skips, visible focus, spoken name matches the label.
Stop if: landing node is not checkout or main.

&lt;span class="gu"&gt;## B — inferred footer&lt;/span&gt;
Task: same purchase after the agent rewrites landmarks.
Stop if: proposed map drops the original main target.
Noise to ignore: extra decorative headings nobody skipped.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification: ask the split out loud. Which missing evidence should stop approval? Which extra detail is only noise? If you cannot split those, you will over-research and still ship the wrong skip. More notes are not more safety.&lt;/p&gt;

&lt;p&gt;I separate evidence from design hypotheses in the same breath. "Users might prefer a shorter skip list" is a hypothesis. A skip-path transcript is evidence. Do not let the first impersonate the second. The &lt;a href="https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/" rel="noopener noreferrer"&gt;ARIA landmarks guide&lt;/a&gt; and &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html" rel="noopener noreferrer"&gt;bypass blocks&lt;/a&gt; describe the trail. They do not bless a visual guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4 — freeze the evidence file
&lt;/h2&gt;

&lt;p&gt;I will not read a chat transcript as a record. Chat evaporates. The gate needs a file the validator can fail. Save the proposal as &lt;code&gt;gate/evidence/proposal.json&lt;/code&gt;.&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;"page_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout-help-article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"original_landmark_map"&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-header"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Help article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"article"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contentinfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Legal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-footer"&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;"proposed_landmark_map"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skip_path_transcript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"primary_task_success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discarded_inferences"&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;"restore_handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git:design/landmarks/checkout-help-article@original"&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;Notice the nulls. They are the point. An honest file shows what the agent does not know. Verification: open the file and count nulls. If a proposed map appears before a transcript, you inverted the order. Stop. The agent does not get to fill silence with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5 — run a local validator
&lt;/h2&gt;

&lt;p&gt;This script is the original artifact. It does not score the model. It refuses approval when required slots are empty. It also refuses a proposal that drops &lt;code&gt;main&lt;/code&gt;. Label it as proposed code. I have not claimed a production rollout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# validate_landmark_gate.py — proposed local gate, not a model benchmark
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original_landmark_map&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_landmark_map&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skip_path_transcript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary_task_success&lt;/span&gt;&lt;span class="sh"&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;def&lt;/span&gt; &lt;span class="nf"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;maps&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP: empty evidence slots:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original_landmark_map&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_landmark_map&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP: proposed map dropped main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restore_handle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP: no restore handle, approval is irreversible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASS: evidence complete; human may now rehearse the skip path&lt;/span&gt;&lt;span class="sh"&gt;"&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_landmark_gate.py gate/evidence/proposal.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;STOP: empty evidence slots&lt;/code&gt; on the first run. That failure is success. The gate is awake. If the script prints &lt;code&gt;PASS&lt;/code&gt; while transcripts are still imaginary, you broke the validator. Fix the script, not the ethics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6 — rehearse the draft on a cheap bench
&lt;/h2&gt;

&lt;p&gt;Only now do I let a model propose a map. I still do not let it publish. I need a place where a bad inference is cheap to throw away. MonkeyCode's free model access and free server option are the rehearsal bench I use for that draft. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I paste the proposed map into the evidence file. I never paste it into production HTML first.&lt;/p&gt;

&lt;p&gt;After the draft lands, I fill &lt;code&gt;proposed_landmark_map&lt;/code&gt; and nothing else. Then I rerun the validator. It must still stop on the empty transcript. That second stop is the lesson. A fluent proposal is not a skip-path proof. Did the model sound sure? Good. Certainty is not a landmark.&lt;/p&gt;

&lt;p&gt;Verification: the evidence file still contains nulls for &lt;code&gt;skip_path_transcript&lt;/code&gt; and &lt;code&gt;primary_task_success&lt;/code&gt;. If those fields got auto-filled, the bench leaked a hypothesis into the record. Delete the fill. Keep the null.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 7 — run the skip path like a person
&lt;/h2&gt;

&lt;p&gt;I do not ask the model whether the skip works. I walk it. Keyboard only. Screen reader speaking. Two skips toward checkout. I type what I heard, not what I hoped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposed capture — replace with your SR tool's own transcript export&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"skip-1: spoken Store, focus #site-header"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"skip-2: spoken Help article, focus #article"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"next: checkout link inside main, focus visible"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; gate/evidence/skip-path-transcript.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I copy that text into the JSON field and set &lt;code&gt;primary_task_success&lt;/code&gt; to true only if checkout was reachable. If the spoken name disagrees with the visible label, success stays false. Mismatch is a stop, not a polish note. Verification: play the transcript to a colleague who cannot see the mock. If they cannot say where they landed, the evidence is theater.&lt;/p&gt;

&lt;p&gt;Accessibility review at this stage is narrow on purpose. I check one main, unique labels, and a skip target that matches an id. I do not pretend this replaces a WCAG audit. I also do not hide recovery behind a tooltip. Focus must be visible because the human has to see the hand-back. Implementation styling is someone else's job. The pattern is mine: no approve click without a spoken, reversible trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 8 — keep the discarded map in the record
&lt;/h2&gt;

&lt;p&gt;Rejection is not deletion. If I refuse the proposal, I append it to &lt;code&gt;discarded_inferences&lt;/code&gt; and restore the original map through the handle. The agent returns to draft. Publish stays human. That is the hand-back. Analogy time. A librarian does not burn the wrong catalog card. She shelves it as wrong, then keeps the old drawer.&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;"discarded_inferences"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skip landed on footer, checkout unreachable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proposed_landmark_map"&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-header"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contentinfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Page end"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-footer"&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="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="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;Verification: restore the original map in one action. If restore takes a redesign, you never had reversibility. You had optimism. Optimism is not a recovery framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this protocol is not
&lt;/h2&gt;

&lt;p&gt;This gate does not prove the model is safe. It proves you refused to approve silence. Free model drafts can omit empty slots and still sound complete. That is why the validator lives on your machine. I am not publishing quotas, model names, or hardware claims. I have no benchmark to wave. If you need those numbers, this article is the wrong artifact.&lt;/p&gt;

&lt;p&gt;Who should not use this approach? Anyone who cannot run a real skip-path rehearsal. Anyone shipping emergency, medical, or legal navigation from a visual guess. Anyone treating a free rehearsal bench as a production control plane. Anyone hoping a longer prompt will replace a restore handle. If your team cannot name a decision owner, stop before stage one.&lt;/p&gt;

&lt;p&gt;I still ask the same two questions at the end of every review. Which missing evidence should stop approval? Which extra information would only add noise? For landmarks, an unproven skip path stops me. A debate about decorative headings does not. The agent can keep talking. Checkout does not have to listen.&lt;/p&gt;

&lt;p&gt;If you already have a free rehearsal bench, run the validator against one live proposal before you bless a visual guess.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Show Empty Evidence Slots Before Approving Agent Copy</title>
      <dc:creator>Haley</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:00:41 +0000</pubDate>
      <link>https://dev.to/haaaaaley/show-empty-evidence-slots-before-approving-agent-copy-1p71</link>
      <guid>https://dev.to/haaaaaley/show-empty-evidence-slots-before-approving-agent-copy-1p71</guid>
      <description>&lt;p&gt;The approve button already had focus on my review card. I still had not asked a single hard question. An overnight agent pass had rewritten every settings label.&lt;/p&gt;

&lt;p&gt;The agent stamped every line as accessible copy. Who actually owns that dangerous last click? I do, and a wrong click ships a lie.&lt;/p&gt;

&lt;p&gt;Where does reversibility still live for this change? It lives only before publish. After publish, recovery turns into hallway folklore.&lt;/p&gt;

&lt;p&gt;I almost treated fluent confidence as research evidence. Have you seen that finished-looking review card? It looks complete while every evidence slot stays hollow.&lt;/p&gt;

&lt;p&gt;This is not another essay about trusting models. This is a from-zero research tutorial. We stop approval when required evidence slots are empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Name the decision, not the vibe
&lt;/h2&gt;

&lt;p&gt;Write the live decision in one plain sentence. Mine read like this, on purpose.&lt;/p&gt;

&lt;p&gt;"May this agent copy replace live field labels?"&lt;/p&gt;

&lt;p&gt;Name the owner, the harm, and the last reversible moment. If those three names will not fit, stop now. You are styling a guess, not reviewing a change.&lt;/p&gt;

&lt;p&gt;Verification comes from disagreement. Read the sentence to a teammate. Can that person refuse it without embarrassment? If nobody could refuse, you wrote a mood.&lt;/p&gt;

&lt;p&gt;I keep this sentence at the top of the packet. It is not a vision statement. It is the only question the card may answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Build the evidence packet before the model runs
&lt;/h2&gt;

&lt;p&gt;I refuse to let the agent write the design record first. I open an empty packet on purpose. Empty slots are the interface, not a chore.&lt;/p&gt;

&lt;p&gt;Here is the packet I use in critique. Treat it as a proposed pattern, not a completed study.&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="c1"&gt;# Proposed evidence packet. Not a field-study result.&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;settings-labels-2026-09-06&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;Replace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;live&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;labels&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;copy?"&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;design-reviewer&lt;/span&gt;
&lt;span class="na"&gt;reversible_until&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;before-publish-to-cms&lt;/span&gt;
&lt;span class="na"&gt;original&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;helper_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;alt_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;agent_pass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;free-shared-server&lt;/span&gt;
  &lt;span class="na"&gt;generated_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contrast_ratio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;screen_reader_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;original_preserved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;purpose_confirmed_by_human&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;hypotheses&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;Shorter&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;labels&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reduce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;time"&lt;/span&gt;
&lt;span class="na"&gt;stop_conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;original copy missing&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;contrast_ratio empty&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;screen_reader_script empty&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;purpose_confirmed_by_human is &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;noise_fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;server hostname&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;token spend&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;model temperature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the nulls sitting in public view? Those nulls are the product. An empty slot is a stop condition, not a personal failure.&lt;/p&gt;

&lt;p&gt;Verification is a null count. Count them before any model runs. If you already have zero nulls, you prefilled a fantasy. Reset the file.&lt;/p&gt;

&lt;p&gt;Separate evidence from design hypotheses in that same file. Contrast is evidence. "Users will like shorter labels" is a hypothesis. Mixing them is how a card starts lying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Capture original copy like it might vanish
&lt;/h2&gt;

&lt;p&gt;Agents overwrite in a polite voice. Original labels then vanish into a tiny diff. Recovery becomes a Slack legend nobody can prove.&lt;/p&gt;

&lt;p&gt;I dump live strings before anyone prompts a model. It feels boring. Boring is the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; packets/settings-labels
&lt;span class="c"&gt;# Proposed capture. Point this at your copy deck or CMS export.&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'email: Email address'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'digest: Weekly digest'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'tone: Product updates may include offers'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; packets/settings-labels/original.txt
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; packets/settings-labels/original.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; packets/settings-labels/original.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a non-empty file. Every live control needs one original line. If a control has no original line, the agent cannot improve it. It can only invent it.&lt;/p&gt;

&lt;p&gt;Would you approve invented helper text on a legal checkbox? I would not, and neither should the card. Invention is not a copy tweak. Invention is a new product claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Generate candidate copy in a cheap rehearsal room
&lt;/h2&gt;

&lt;p&gt;I needed a sketch room that could not touch production. MonkeyCode offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I use that room to generate candidates only. I never use it as a silent publisher.&lt;/p&gt;

&lt;p&gt;Keep the prompt small and slightly rude. Ask for candidates. Forbid accessibility boasts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; packets/settings-labels/prompt.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Propose alternate labels for these controls.
Do not claim accessibility.
Do not delete the original wording.
Flag any purpose you are guessing.
Return YAML only.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;packets/settings-labels/original.txt packets/settings-labels/prompt.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a forbidden claim. If the model still says the copy is accessible, log that sentence as a defect. Do not reward the boast with a merge.&lt;/p&gt;

&lt;p&gt;Why rehearse on a free shared server at all? Because a sketch should be cheap to throw away. Because a stall should not hit your CMS. The packet is the work. The vendor is scenery.&lt;/p&gt;

&lt;p&gt;If you already have a safe sandbox, stay there. Do not move your source of truth for this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: Score empty slots before anyone argues about tone
&lt;/h2&gt;

&lt;p&gt;Tone arguments get loud in five minutes. Empty evidence stays quiet unless you score it first. Score the quiet thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposed validator. Unexecuted until it reads a real packet.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;packets/settings-labels/packet.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;stops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original labels missing&lt;/span&gt;&lt;span class="sh"&gt;"&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;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contrast_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contrast_ratio empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;screen_reader_script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;screen_reader_script empty&lt;/span&gt;&lt;span class="sh"&gt;"&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;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purpose_confirmed_by_human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human never confirmed purpose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stops&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;READY&lt;/span&gt;&lt;span class="sh"&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stops&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it against the fresh packet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 score_packet.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a STOP on a new packet. READY would mean the script is lying, or you smuggled evidence in. Either case blocks approval.&lt;/p&gt;

&lt;p&gt;This is the annotated review pattern I want in the design system. Four slots sit on the card: original copy, contrast, screen-reader script, and purpose confirmed. Color is never the only state. Each slot also speaks in text: Empty, Recorded, or Conflict.&lt;/p&gt;

&lt;p&gt;Can a reviewer understand the card with grayscale on? If not, the card is decoration. Decoration is how bad copy gets a green chip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: Walk three research scenarios, then force a stop
&lt;/h2&gt;

&lt;p&gt;I do not need a thirty-person study to gate this click. I need scenes that can fail in public. Write them before the critique starts.&lt;/p&gt;

&lt;p&gt;First scene. The agent reads a field named tone and writes Marketing opt-in. The original line was a legal warning about offers. Does the packet stop that swap, or do people debate friendliness?&lt;/p&gt;

&lt;p&gt;Second scene. The agent writes alt text from a filename, hero-final-v3. A screen-reader user then hears a version string. The script slot is empty. Do you still feel the card is mostly done?&lt;/p&gt;

&lt;p&gt;Third scene. The free server returns a partial packet and drops helper text. The layout still looks tidy. Do you approve a tidy hole?&lt;/p&gt;

&lt;p&gt;Success is a spoken refusal in under two minutes. Failure is a taste debate that never names a slot. If voice wins, the card failed its job.&lt;/p&gt;

&lt;p&gt;Verification is a prewritten refusal. I write I refuse because contrast is empty. If a reviewer cannot say that line, rebuild the card before another session.&lt;/p&gt;

&lt;p&gt;Notice what we are not measuring here. We are not scoring eloquence. We are not scoring how "on brand" the shorter line feels. Those scores are hypotheses. They can wait until the slots are full.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 6: Test the review interface like it can harm people
&lt;/h2&gt;

&lt;p&gt;An inaccessible review tool will bless inaccessible copy. That irony does not help a single user. Test the gate the way you test a checkout.&lt;/p&gt;

&lt;p&gt;Keyboard order should hit Reject before it hits Approve. Stop reasons must exist as text, not as a red dot. Original copy cannot live behind a hover tooltip.&lt;/p&gt;

&lt;p&gt;Never chip the status as accessible. Announce evidence incomplete instead. A flow only earns its place if it can stop someone.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Open review card] --&amp;gt; B{Original copy present?}
  B --&amp;gt;|No| S[Stop: do not approve]
  B --&amp;gt;|Yes| C{Contrast recorded?}
  C --&amp;gt;|No| S
  C --&amp;gt;|Yes| D{Screen-reader script present?}
  D --&amp;gt;|No| S
  D --&amp;gt;|Yes| E{Human confirmed purpose?}
  E --&amp;gt;|No| S
  E --&amp;gt;|Yes| F[Edit, then hand back to record]
  F --&amp;gt; G[Publish with original and new copy]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Verification is a keyboard pass on a mock card. Can you reject without a mouse? Can a screen reader hear the blocking reason? If either answer is no, you designed applause, not review.&lt;/p&gt;

&lt;p&gt;I also check focus order after a stop. The reviewer should land on the empty slot, not on Approve. A focus trap on Approve is a product bug with manners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 7: Hand the decision back, and keep discarded lines
&lt;/h2&gt;

&lt;p&gt;Approval is not closure. The discarded original must remain in a concrete record. Future you will need those lines on a bad Thursday.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"time: &lt;/span&gt;&lt;span class="nv"&gt;$ts&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"decision: refused-or-accepted"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stop_reasons: contrast empty"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"original_kept: yes"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; packets/settings-labels/decision.log
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 packets/settings-labels/decision.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification is a log that still holds original_kept. The CMS must still show live strings. You should reverse without digging through chat history like a detective.&lt;/p&gt;

&lt;p&gt;Which missing evidence should stop approval cold? Contrast, original wording, the screen-reader script, and human purpose confirmation. Which extra information only adds noise? Server hostname, token spend, temperature, and a confidence number with no method.&lt;/p&gt;

&lt;p&gt;Ask that pair out loud during critique. Teams will offer dashboards. Many dashboards are noise wearing a chart.&lt;/p&gt;

&lt;p&gt;The hand-back is the humane part. The agent does not get to close the ticket. A person files the record, including what was thrown away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits, and who should skip this
&lt;/h2&gt;

&lt;p&gt;I am not claiming free models write worse labels than paid ones. I did not run that comparison. I am not claiming quotas, hardware, uptime, or lasting capacity. Those numbers are not in this article.&lt;/p&gt;

&lt;p&gt;Skip this protocol if no human will read the packet. Skip it if originals, contrast notes, and screen-reader scripts already live in one record. Skip it if a human already wrote the copy and you are only delaying them.&lt;/p&gt;

&lt;p&gt;Do not twist this tutorial into permission. The packet can block publish. It cannot bless publish.&lt;/p&gt;

&lt;p&gt;If your labels touch medical or legal meaning, stop. This YAML is too light for those products. You need counsel and a real accessibility evaluation. A research card is not a lab.&lt;/p&gt;

&lt;p&gt;I still want those empty slots in the open. Confidence is a costume that talks fast. Evidence is a door you can still close.&lt;/p&gt;

&lt;p&gt;Keep production out of the sketch room. Rehearse the refusal while reversal is cheap.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>design</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
