A reviewer approves an invoice reminder. The worker restarts before execution, or stops after the provider accepts the send. The product still needs one authorized send and a checked receipt.
The trial exercises that job through AI SDK 7.0.120, OpenAI Agents SDK 0.18.0, and LangGraph 1.4.18. All 21 framework/scenario trials passed after the integration adjustment below. The result demonstrates a working implementation with each SDK; the result does not rank the frameworks.
Reproduce the trial
Download the complete trial, inspect the result report, or read the source on GitHub. The bundle includes the lockfile, installation instructions, workers, shared operation, and actual results.
Node 24 runs the controller and starts a fresh worker process for preparation and every resumption. AI SDK and OpenAI Agents SDK receive scripted model outputs. LangGraph receives the same prepared proposal directly. The local provider is a SQLite table with a unique operation key. No model API, customer account, or real email is used.
Separate the SDK from the product service
The same saved proposal names invoice-1 at version 3. Each SDK pauses before the send. The product controller records the review separately from SDK state.
The shared service checks the exact proposal, server-bound tenant, acting account’s current permission, capability flag, and invoice version. The simulated provider accepts an operation key once. The product writes a receipt after acceptance. The SDKs do not supply those application guarantees.
| Layer | Responsibility in this trial |
|---|---|
| SDK adapter | Expose approval, save framework state, resume the call |
| Product service | Check current authority, scope, proposal binding, and freshness |
| Simulated provider | Accept a stable operation key once |
| Product ledger | Record accepted work and reconcile the receipt |
| Controller | Change conditions and start replacement worker processes |
Inspect the seven scenarios
| Scenario | Expected and observed result in all three adapters |
|---|---|
| Approve unchanged proposal | One accepted send and one receipt |
| Decline | No tool execution or send |
| Change invoice version during review | stale_record blocks the send |
| Revoke acting account during review | revoked_access blocks the send |
| Invoice belongs to another tenant | wrong_tenant blocks the send |
| Disable capability during review | disabled_capability blocks the send |
| Stop after provider acceptance | Worker exits before the receipt; replacement reconciles one receipt with one accepted send |
Every preparation run also checks that the send tool has not executed before review. The report records event order, sends, receipts, versions, and outcomes. Policy-rejection scenarios test the shared service through each SDK; those scenarios do not demonstrate native SDK authorization features.
Compare the stored state
AI SDK: the worker saves messages including the approval request. A replacement loads those server-owned messages, adds the decision, and calls ToolLoopAgent again. The application owns storage. AI SDK approval flow
OpenAI Agents SDK: the worker persists RunState.toString(). A replacement rebuilds the registered agent, loads RunState.fromString(), resolves the interruption, and resumes the same run. Trace export is disabled. OpenAI approval lifecycle
LangGraph: a SQLite checkpointer stores graph state under job-1. A replacement uses the same thread ID. A review node contains the interrupt; a separate commit node performs the operation. A crashed commit resumes with null input. LangGraph interrupts
The review node enters again after resumption. A send before the interrupt would therefore need duplicate prevention even when review has not finished. Separate review and commit nodes make the boundary easier to inspect.
Record the integration adjustment
The pinned LangGraph release rejected a bare false resume value in this graph with EmptyInputError. The harness reproduces the error before verifying the working payload:
new Command({ resume: { approved: false } })The review node reads decision.approved. The result is a declined job without a send. The observation applies to version 1.4.18 and this graph; other releases remain untested.
Understand the lost-reply test
The worker exits immediately after inserting provider acceptance. The provider row survives while the receipt is absent. A replacement retries the pending operation. The service finds the same operation key and saves a reconciled receipt without a second accepted send.
A real provider may lack an idempotency key or lookup. The integration then needs an explicit unknown-result state and reconciliation policy. A checkpoint or serialized approval cannot make an external email transactional.
Use the result to make a decision
Compare the team’s existing runtime, storage, UI, debugging, and integration effort next. Add live model quality and provider failure tests before drawing a deployment conclusion. The framework guide covers the broader decision. The starter prompts help define the job and acceptance checks.
Research method: executed locally on macOS arm64 with Node 24.17.0 on September 29, 2026. One pending call, one worker at a time, trusted local files, synthetic provider. Concurrent reviewers, hostile client edits, live model quality, stream reconnection, multiple approvals, rate limits, and production databases remain untested. The report and lockfile identify the exact run and dependencies.