Skip to content

flow: run the API step in durable runs without ever resending a save - #1358

Merged
abrichr merged 3 commits into
mainfrom
flow/api-step-durable
Oct 19, 2026
Merged

abrichr merged 3 commits into
mainfrom
flow/api-step-durable

Conversation

@abrichr

@abrichr abrichr commented Oct 19, 2026

Copy link
Copy Markdown
Member

Stacked on #1355 (approval binding). Merge that first; this PR then retargets to main.

Second of the small PRs that put the API step, pipelined read-back and self-repair in one production profile. This one handles durable runs.

Why

The API step (#1269) fell back to the GUI steps in every durable or resumed run. Standard and Regulated runs are durable, so production never used it.

What changes

  • Checkpoints. The checkpoint before the span is the last step before it. The span's steps are checkpointed together once the save was sent: the covered steps as api_folded, the save as api. If the save wasn't sent (for example the record check's pre-state couldn't be read, or an approval stopped the item), nothing in the span is checkpointed, so a resume runs the whole API step again.
  • No resend. A resume that would start inside a span whose steps were folded stops at the save. That save may have landed, so it is never sent again and never redone through the GUI on an empty form, even when the resume approval authorizes one uncertain retry. If the run fell back to the GUI and stopped at the GUI save, that GUI save resumes as before.
  • Reconcile. The existing attended reconcile action reads the record back and finishes the run when the record is there, without sending anything. RECONCILIATION_REQUIRED is unchanged.
  • Bug fix. The linear loop recorded every result of a span under the span's first index; each result now gets its own index. This didn't matter before, because spans never ran durably.
  • action_evidence accepts api_folded as a skipped step with nothing delivered.

Known limit (documented in API_TIER.md)

Reconcile reads the record as it is now, so it can't prove a record check that compares with the state before the save (exact_new_set, count_new_only, a collateral-loss guard). The pause then stays for a person to check the record. The same limit already applies to GUI saves. Lifting it would need a protected pre-send baseline, which is not in this PR.

Tests

tests/test_api_step_durable.py (new, 6 tests): a clean durable run checkpoints api_folded and api. After a lost response, an ordinary resume is refused and a resume that authorizes one retry still stops without sending. Reconcile finishes without sending. A save the record check refutes isn't redone. An unsent save resumes the whole API step and saves once. A GUI fallback that halted at the save resumes on the GUI.

Locally: 512 passed across the API tier, approval, durable, attended, resume, uncertain-delivery and multi-surface suites. ruff and strict mypy pass. One unrelated e2e test (tests/e2e/test_chaos.py::TestLayoutSwapMidRun::test_swapped_type_buttons_still_select_the_right_type) fails the same way on origin/main.

Synthetic data only.

🤖 Generated with Claude Code

abrichr and others added 2 commits October 19, 2026 12:21
A durable or resumed run used to fall back to the GUI steps. Now the API
step runs there too, under the same no-double-write rule.

- The span's steps are checkpointed together once the save was sent
  (api_folded, then api). A save that wasn't sent checkpoints nothing in
  the span, so a resume runs the whole API step again.
- A resume that would start inside a span whose steps were folded stops
  at the save: that save may have landed, so it is never sent again and
  never redone through the GUI on an empty form. This holds even when
  the approval authorizes one uncertain retry. A run that fell back to the
  GUI and stopped at the GUI save resumes it as before.
- The attended reconcile action reads the record back and finishes the
  run when the record is there, without sending.
- The linear loop now records each span result at its own index (it used
  the span's first index for all of them).
- action_evidence accepts api_folded as a skipped, undelivered step.

RECONCILIATION_REQUIRED is unchanged. Reconcile can't prove a record
check that needs a before-snapshot (exact_new_set, count_new_only,
collateral loss); that limit is the same for GUI saves and is documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The referral measurement found it: the save checks the right record on
screen, the API step sent the save without any identity check, and the
durable checkpoint then refused the result for lacking identity evidence,
after the save had landed. The API step now runs for an identity-checked
save only when its binding names a right-record contract; otherwise the
GUI steps run and the report says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from flow/api-step-approval to main October 19, 2026 18:05
@abrichr abrichr closed this Oct 19, 2026
@abrichr abrichr reopened this Oct 19, 2026
@abrichr
abrichr merged commit 215ad8b into main Oct 19, 2026
75 checks passed
@abrichr
abrichr deleted the flow/api-step-durable branch October 19, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant