Ayobami John Haastrup · Experiment run on 25 October 2026
When a caller stops waiting for an agent, has the work stopped too? After repeated crashes, will the agent keep trying, finish with an error, or leave the job pending?
Those distinctions matter for an accountability interface. A spinner disappearing is weak evidence. A status needs to say whether work is still running, needs intervention, completed, or was explicitly abandoned.
I tested this boundary in Cloudflare's recent Pi harness integration. The corrected local experiment passed seven cases: one clean control, three retry cases and three abort cases. An earlier diagnostic run contained two adapter failures; that run is published too.
Why this release#
Cloudflare's 22 October announcement introduced Pi 1.4 support in Agents 0.30.0, including parked tasks after repeated crashes. This lab pinned Agents 0.30.1 and Pi Durable 1.4.0, the npm latest versions checked on 25 October. It used Wrangler 4.151.2 and workerd 1.20261019.1; those are recorded test versions, not a claim that every dependency was the latest release.
The Pi documentation distinguishes a pending parked operation from a settled result and provides explicit retry and abort methods. I wanted executable observations of that distinction. This is a version-specific engineering probe, not a claim to have discovered a new recovery algorithm.
The earlier Stripe recovery manuscript remains a separate, archived baseline on Agents 0.28.1 and Pi 1.3.0. The new lab used fresh storage. Cloudflare documents an incompatible Pi 1.4 storage migration, so upgrading the original experiment in place would have weakened that baseline.
What ran#
The fixture ran under local Wrangler/workerd on Linux with Node 24.18.0. It used a deterministic faux model and one disposable tool, marked replay-safe. The tool increments an observation counter, then waits for a local release flag before writing a completion marker. Those writes measure the fixture; they represent no payment, message or business transaction.
For each recovery case, the runner submitted one operation, waited until the tool had started, then killed its own Wrangler process group with SIGKILL. It restarted the fixture against the same local persistence directory. A changed instance UUID confirmed reopening. Each case used a fresh directory, and the configured restartLimit was 2.
Before crashing, the runner cancelled a 300 ms wait and checked that the operation remained running. After parking, it performed another bounded wait and checked the tool counter. Retry cases then released the fixture and called session.retry(). Abort cases instead called session.abort(operationId).
No model-provider API or Stripe call was made. No experimental Worker was deployed to Cloudflare.
Observed results#
| Path | Cases passed | Forced restarts per case | Observed outcome |
|---|---|---|---|
| Clean control | 1 / 1 | 0 | done, with text completed; one tool entry |
| Park, release, retry | 3 / 3 | 2 | parked became done; completion marker set; pending list empty |
| Park, abort | 3 / 3 | 2 | parked became unanswered, reason aborted; no completion marker; pending list empty |
All six recovery cases reached parked after the second forced restart. The counter was two at that point. It stayed two across the short parked-wait probe. Retry cases finished with three tool entries; abort cases finished with two. The counter intentionally records replay attempts, so three entries do not mean three external effects.
In all six cases, cancelling the first wait left the operation running. Waiting on a parked operation also timed out without settling it. After each of the three retry cases completed, submitting the same operation ID returned accepted: false and the immediate tool counter was unchanged. That checks duplicate admission at this boundary; it does not establish exactly-once execution for an external service.
The completed run spans 08:35:42–08:38:23 UTC. The raw successful result contains timestamps, instance IDs, every injected restart and the returned states.
The failure in my test adapter#
The first diagnostic run passed its clean control, then timed out in both recovery paths before injecting a crash. I had passed a bare abort signal to the harness-level wait method, confusing its options object with the session-level signature. The outer HTTP timeout fired instead of the intended bounded wait.
The correction uses harness.session("1").wait(id, signal). The same patch also changes abort to the session handle; the first run never reached that abort call. The subsequent run exercised both corrected paths.
The failed result, original adapter, source hashes and exact two-line diff are retained in the evidence bundle. The original adapter was reconstructed from the patch and verified against its recorded SHA-256. This is a lab implementation error, not evidence of an upstream harness defect. Across both runs there were ten case attempts: eight passed their assertions and two failed in the original adapter.
What I would put in a product#
My design conclusion is to show parked as a state requiring attention, with the operation ID, last observed progress and an explicit recovery action. A client wait timeout should leave the job visible as pending until a separate result establishes otherwise. Retry and abort should produce recorded outcomes that a user can inspect.
That conclusion is supported by the state transitions in this fixture. It is not evidence that a particular interface has already improved user outcomes.
Evidence and boundaries#
Download the source and evidence bundle, its SHA-256 manifest, or the reproduction guide. The bundle includes the exact executed source, dependency lockfile, both result sets and Wrangler logs. A checksum verifies bytes against this publication; it does not independently authenticate the experiment.
This is a small local sample. The parked observation lasted roughly half a second per case, not hours. The runner actively polls after restarting, so it cannot establish unattended alarm recovery or hibernation behaviour. It does not test hosted eviction, storage failure, model-stream recovery, concurrent operators, real-model reasoning or irreversible external effects. The onReport error objects serialised as {} in the JSON, so the observations rely on the explicit operation states and counters, not those empty report fields.
The next useful question is whether the same transitions hold during hosted eviction and competing retry/abort requests, with separately recorded state reads. That is a proposed follow-up, not a result included here.