Afterstate Action integrity

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

  1. 01

    pip install afterstate

    Local package / wheel — same path a partner would take.

  2. 02

    afterstate init --apply

    Scanned the repo, found the Gemini agent and two dangerous write tools, inserted decorators in ~7 ms. Tool bodies untouched.

  3. 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.

  4. 04

    afterstate verify

    Inject 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.

Before Bare tools · 10/20 safe
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
After Receipt-driven · 20/20 safe
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.