Findings Integration demo
Integration demo · 16 Jul 2026
QuickServe × Afterstate
A simulated delivery startup whose support agent places orders and
issues refunds against a cloud POS that sometimes loses the response
after the write commits. Same code, same faults — before and
after Afterstate. Scripted and deterministic; every number below
reproduces with
python examples/quickserve_demo.py.
Scoreboard
| Metric | Before | After |
|---|---|---|
| Duplicate writes | 10 / 20 episodes | 0 / 20 episodes |
| Double-processed $ | $85.60 | $0.00 |
| Safe rate | 50% | 100% |
| Integration cost | 7 lines via codemod · 46 lines for the POS observer · ~7 ms apply | |
What integration actually took
-
01
pip install afterstateLocal package / wheel — same path a partner would take.
-
02
afterstate init --applyScanned the repo, found the Gemini agent and two dangerous write tools, inserted decorators in ~7 ms. Tool bodies untouched.
-
03
Fill in the observer (46 lines)
Teach Afterstate how to ask the POS “did this land?” by business key — phone + total for orders, order_id + amount for refunds.
-
04
afterstate verifyInject timeout, double-ask, and crash faults against QuickServe’s own tools. Both tools: wrapped 3/3 safe · bare 0/3 safe.
The codemod diff
Real output of afterstate init --apply on QuickServe’s
tools.py.
--- tools.py (before)
+++ tools.py (after)
@@
+import afterstate
+
+afterstate.configure(store="sqlite:///afterstate.db")
+
import backend
+@afterstate.tool(op_type="custom.place_order", identity=["phone", "total_cents"])
def place_order(phone: str, items: list, total_cents: int):
"""Submit an order ticket to the kitchen POS."""
...
+@afterstate.tool(op_type="custom.refund_order", identity=["order_id", "amount_cents"])
def refund_order(order_id: str, amount_cents: int):
"""Refund a customer for a confirmed order."""
...
During apply, Afterstate’s coarse-identity guardrail warned that
place_order’s suggested identity
(phone, total_cents has no scope-anchor field — the same
class of misconfig that produced
our only unsafe adversarial
verdicts. For QuickServe, phone + total is the intended per-session identity;
the warning still fired. That is the point.
Verify scorecard
Run on QuickServe’s own tools after the codemod — not a toy fixture.
| Tool | Fault | Bare | Wrapped |
|---|---|---|---|
place_order |
timeout-after-send |
UNSAFE | safe |
double-ask |
UNSAFE | safe | |
crash-before-record |
UNSAFE | safe | |
refund_order |
timeout-after-send |
UNSAFE | safe |
double-ask |
UNSAFE | safe | |
crash-before-record |
UNSAFE | safe |
Scorecard: wrapped 6/6 safe · bare 0/6 safe.
Same faults, two endings
Day-one playbook on the left: retry when the POS times out. Receipt path on the right: observe first; never blind-retry.
| agent | fault | writes | extra $ |
|---|---|---|---|
| order #1 | — | 1 | — |
| order #2 | response lost | 2 | $12.02 |
| order #3 | — | 1 | — |
| order #4 | response lost | 2 | $12.04 |
| …10 duplicates across 20 episodes · $85.60 total | |||
| agent | fault | writes | extra $ |
|---|---|---|---|
| order #1 | — | 1 | — |
| order #2 | response lost | 1 | — |
| order #3 | — | 1 | — |
| order #4 | response lost | 1 | — |
| All 20 episodes: receipt=applied · $0.00 extra | |||
Receipt log
Every write got a verified receipt — including the 10 episodes where the POS response was lost after commit. The observer confirmed the write landed, so the agent never retried.
| op_type | status | next_step | count |
|---|---|---|---|
custom.place_order |
applied | continue |
10 |
custom.refund_order |
applied | continue |
10 |
Reproduce
cd afterstate-core
python examples/quickserve_demo.py
# → results/quickserve_demo/dashboard.html
# → results/quickserve_demo/summary.json
Want this on your write tools?
QuickServe is a simulation. The CLI path —
init --apply, observer scaffold,
verify — is the real product surface.