cd /news/ai-agents/from-prose-to-assertions-making-done… · home › topics › ai-agents › article
[ARTICLE · art-162997] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

From prose to assertions: making 'done' checkable when an AI agent writes the code

A developer built DwGuard-Dsh-Hmos, a plugin for DeepSeek Harness that orchestrates HarmonyOS app delivery by freezing a four-dimension acceptance pack, running an adversarial pre-review, dispatching work to DevEco Code in Goal mode, and auditing delivery with independently gathered evidence. The plugin adds optional machine-checkable assertions to acceptance criteria, using eight matchers (equals, contains, regex, range, gte/lte, oneOf, present), so the audit gate recomputes verdicts from reported actual values instead of trusting free-text evidence. The gate errors when a passing check lacks an actual value or contradicts its assertion, warns on other mismatches, and refuses to freeze specs containing invalid assertions.

by read5 min views3 publishedNov 3, 2026

The hardest part of an AI coding loop is not getting code written. It is the moment after the agent says "done".

When one agent writes the code and another agent reviews it, you have not removed the trust problem — you have just moved it. The reviewer reads the diff, reads the tests, reads the ticket, and produces a verdict. If the ticket says "spacing should be 12vp" and the screenshot looks about right, the verdict is "pass". Two auditors can look at the same delivery and disagree, and nothing in the tooling forces that disagreement to resolve.

That is the gap I tried to close in DwGuard-Dsh-Hmos, a plugin for DeepSeek Harness that orchestrates HarmonyOS app delivery: DSH freezes a four-dimension acceptance pack (function / aesthetic / performance / accessibility), runs an adversarial pre-review before dispatch, hands the work to DevEco Code in Goal mode, then audits the delivery with independently gathered evidence.

The change I want to write about is small in surface area and large in consequence: acceptance criteria can carry a machine-checkable assertion, and the audit gate recomputes the verdict instead of trusting prose.

My criteria used to be a sentence plus a verification method:

statement: 冷启动到首帧 ≤ 800ms          (cold start to first frame ≤ 800ms)
verify:    Profiler 启动分析毫秒数        (milliseconds from the profiler)

And an audit check was a result plus free-text evidence:

result:   pass
evidence: 3 runs, 700/720/760ms, screenshot artifacts/perf.png

Three problems showed up immediately:

evidence is free text. No tool can tell whether "1200ms" satisfies "≤ 800ms" without a human eyeballing it. So the audit was still a person voting, with extra steps.

The fix is an optional assertion on each criterion:

{
  "id": "P-01",
  "dimension": "performance",
  "statement": "Cold start to first frame ≤ 800ms",
  "verify": "ms reported by the startup profiler",
  "weight": "must",
  "ruleId": "DW-PERF-01",
  "assertion": {
    "matcher": "lte",
    "expected": 800,
    "unit": "ms",
    "source": "startup profiler"
  }
}

Eight matchers cover the criteria I actually write:

matcher meaning
equals numeric comparison when both sides are numbers, exact string otherwise
contains substring, case-insensitive
regex pattern (unanchored — add ^$ yourself)
range within [min, max] , inclusive
gte /lte numeric greater/less or equal
oneOf equals one of a list
present non-empty

The audit side then must report an actual value next to the verdict:

{
  "criterionId": "P-01",
  "result": "pass",
  "evidence": "three cold starts: 700/720/760ms; screenshot artifacts/perf.png",
  "actual": "720ms"
}

One detail matters more than it looks: numeric matchers take the first number inside actual. That means measured values can keep their units — "12vp" parses as 12, "720ms" as 720, "4.9:1" as 4.9. The assertion compares numbers; the report stays human-readable.

An optional field that nobody enforces is decoration. So the gate has opinions:

situation result
criterion has an assertion, check says pass , but noactual error — you cannot pass an assertion you never ran
criterion has an assertion, check says pass , assertion recomputes tofail error — audit refused; this is the self-contradiction case
criterion has an assertion, check says fail , assertion recomputes topass warning — something else may have failed it; ask for review
criterion has an assertion, check says unverified withoutactual warning (the existing must-gate already errors)
criterion has no assertion unchanged behaviour: human verdict + evidence
the assertion itself is invalid (unknown matcher, range with no bounds, bad regex) the spec refuses to freeze withE_CRITERION_ASSERTION_INVALID

Plus a nudge at authoring time: a must criterion whose statement contains numbers but carries no assertion gets W_CRITERION_NO_ASSERTION, because that is exactly where two auditors will disagree later.

The subtle rule is the third one. Saying "fail" when the number passes is not always wrong — a screenshot can show a layout bug that a timing assertion cannot see. But the gate should make that mismatch visible instead of silently accepting whatever the reviewer wrote.

Because the verdict is now recomputed, the reports gain a column each:

spec.md and the Goal brief gain an audit-<n>.md gains And audit-<n>.json now contains checks[].assertionVerdict, which means CI can fail a build on verdict: fail without parsing markdown. That was the whole point of making the report consumable.

I want to be careful here, because "machine-checkable acceptance" is easy to oversell.

dumpsys reading, a layout tree. Evidence files get copied, hashed, and recorded, but the assertion only decides pass/fail from the number.actual is the same process being audited, an agent can satisfy the letter. That is why the audit playbook insists on externally observable sources — hidumper render stats and hitrace for performance, layout bounds for spacing — and treats in-app timing instrumentation as auxiliary evidence, never the sole basis. The change shipped as 0.5.0 and the suite went from 49 to 55 tests, all green (six new ones specifically about assertions: accepting valid, rejecting invalid, recomputing, refusing contradictions, refusing a pass without actual, and keeping old packs unchanged).

The real payoff is not "the AI is now trustworthy". It is that disagreement became resolvable. When two auditors disagree now, there is a file with a number and a rule that says whether it passes. Either the number is wrong, or the threshold is wrong, or the measurement is wrong — and all three are fixable. Before, the disagreement was just two paragraphs.

If you are building an agent loop where one model checks another, the question to ask is not "is the reviewer good". It is: can the reviewer's verdict be recomputed by something that did not write it?

Plugin: https://github.com/Dogwind221/dwguard-dsh-hmos · npm: https://www.npmjs.com/package/dwguard-dsh-hmos · listed in awesome-dsh-plugin.

── more in #ai-agents 4 stories · sorted by recency
── more on @dwguard-dsh-hmos 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/from-prose-to-assert…] indexed:0 read:5min 2026-11-03 · —