# FLOP in Pi Durable

`@flop-labs/pi/durable` targets Pi Durable 1.4. Pi Durable changes its API in minor
releases, so this package supports one tested minor at a time (`~1.4.0`). It exposes:

- `createFlopDurableProvider({ gateway })`: physical FLOP models from the live catalog,
  with the same Anthropic Messages transport as the Pi coding-agent integration.
- `createFlopDurableProvider({ gateway, coldStart: { maxCostFlop, maxWaitMs } })`:
  supported physical models through public chat's bounded model-loading path.
- `createFlopDurableExtension({ gateway, apiKey, model, maxCostFlop, maxTokens })`:
  the native `flop_infer` tool for bounded paid jobs.
- `FlopInferenceDoc`: conversation-local request intents and returned receipt metadata,
  observable through Pi's document APIs.

Install `@earendil-works/pi-ai@~1.4.0`, `@earendil-works/pi-durable@~1.4.0` and this package.
The durable subpath does not load the Pi coding-agent extension. The package ships
compiled ESM, so applications need no TypeScript loader; the repository examples below
run through `tsx`.

For a Cloudflare application, follow [CLOUDFLARE.md](CLOUDFLARE.md). Its install path
includes `agents@latest` and both Pi packages, and `PiHarness` supplies the Durable
Object's SQLite storage and wake-up lifecycle. Wrangler bundles the source entry points.

```typescript
import { createModels } from "@earendil-works/pi-ai";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
import { createFlopDurableProvider, createFlopDurableExtension } from "@flop-labs/pi/durable";

const models = createModels();
models.setProvider(await createFlopDurableProvider({ gateway }));
const registry = createRegistry();
registry.install(createFlopDurableExtension({
  gateway, apiKey, model: modelId, maxCostFlop: 2, maxTokens: 1024,
}));
const harness = await Harness.open(await openNodeSqliteStorage("agent.sqlite"), {
  models, registry,
}, context);
```

`gateway`, `apiKey`, `modelId` and Pi's cancellation `context` are supplied by the host.
Use a bounded child gateway credential. The provider resolves `FLOP_API_KEY` through
pi-ai's credential system; the tool receives its credential explicitly. Neither
adapter holds a wallet signer. The catalog is read at provider creation; create and
register a replacement provider to refresh it. `flop/auto` is a coding-agent virtual
model and is not offered by this durable adapter.

## Run the example

From this checkout, set `FLOP_GATEWAY_URL`, `FLOP_API_KEY`, `FLOP_MODEL` to a physical
catalog model ID, and `FLOP_MAX_COST_FLOP` to the per-tool-call ceiling, then run:

```bash
pnpm --filter @flop-labs/pi exec tsx examples/durable.ts \
  ./agent.sqlite job-42 'Use flop_infer to name three primes and report its answer.'
```

Reusing `job-42` finds the same conversation submission. A new job needs a new ID.
One process owns the SQLite file. The example prints the answer and any retained job
receipts. Its orchestration model also runs on FLOP and consumes the gateway credential's
quota; `maxCostFlop` bounds the job tool, not the entire conversation. Other registered
pi-ai providers can drive a conversation that selects the FLOP tool extension.

For a supported model that may be cold, also set `FLOP_MAX_WAIT_MS` to an integer
from 1 to 3,600,000. This selects `/v1/models?availability=supported` and the existing
public `/v1/chat/completions` streaming tool transport. Every orchestration call
sends the explicit `FLOP_MAX_COST_FLOP` and wait ceiling; transport retries are
disabled. Payload hooks cannot increase those ceilings. Without this opt-in, the
existing ready-only Messages transport is unchanged. Cold admission can load a
model and consume quota: discovery alone does not authorize running the example.

## Recovery and accounting

Before sending HTTP, the tool atomically commits a request intent containing a session
UUID, tool task ID, model, output-token limit, spending cap, gateway and input digest.
An optional `maxWaitMs` is also committed and sent as `max_wait_ms`; a resumed
intent retains its original value (or absence), even when the new host config differs.
The gateway owns the original request's absolute deadline; replay does not reset it.
Credentials are represented only by a digest. The request uses that durable ID as
`Idempotency-Key` on the gateway's non-streaming `/v1/chat/completions` route.
The tool uses `@flop-labs/client`'s `createChatCompletion` with `stream: false`.
The shared client validates the request and JSON response, omits ambient credentials,
and never follows redirects or automatically retries a paid POST. `fetchImpl` remains
available for host transport injection. The tool keeps JSON transport because the
gateway's replay record is a completed JSON response; shared-client SSE support does
not make streaming responses replayable.

If Pi stops after the gateway effect but before recording its result, the same tool
task sends the same ID and original request settings after reopening. The gateway
replays a completed response instead of reserving another charge. Once Pi records the
output and receipt, a replay uses its local record. Independent stores have different
UUIDs, and conversation forks start with an empty request ledger. Pi Durable parks a
task that crashes five times in a row without committing (`settings.restartLimit`);
`Harness.retryTask(id)` runs the same task again, with the same saved request ID.

The tool accepts success only with a `stop` or `length` finish reason, a matching model,
a cost within the saved ceiling, and channel/settlement/model receipt metadata. A settled
response with a failed, missing or unknown finish reason leaves the intent unresolved
and returns a tool error.

The gateway answers API-key requests from a pooled channel with a `pending` checkpoint:
a running root and a positive `reservedRaw` in place of a settlement transaction hash.
It proves verified work and finalized funding of the reservation, not completed payment.
The tool accepts it and retains the receipt unchanged, so `receipt.settlementStatus`
records which kind it holds. A pending receipt's `reservedRaw` must not exceed the saved
ceiling. Its `costFlop` is the reservation, an upper bound on the eventual debit. Read the
final spend by the request ID saved in `FlopInferenceDoc`, with the gateway's exact lookup
`GET /v1/keys/me/receipts/{request_id}` (`getCredentialReceipt` in `@flop-labs/client`
releases after 0.2.1); this adapter does not poll it. A gateway deployed before that lookup
answers it with 404 `not_found` and lists only the latest 20 receipts at
`GET /v1/keys/me/receipts`.
Any other `settlementStatus` value is rejected. The gateway performs verification and
settlement; this adapter retains its evidence and does not independently verify chain
finality or attestation signatures. FLOP spend remains in FLOP units in the document,
separate from Pi's model-cost estimates in USD.

An unfinished gateway request can return `409 request_in_progress`; ambiguous errors
retain the request intent. Reconcile its ID through the gateway's exact receipt lookup
before replacing paid work. Its 404 `receipt_not_found` does not prove that no paid work
occurred. This adapter does not repair the gateway's unfinished ledger or retry using a new
ID. If the credential or gateway changes while an intent is unfinished, it refuses to
dispatch under the new identity. Restoring a backup and running both copies is unsupported.
HTTP, transport and structural-validation errors include the saved request ID and
"Reconcile this request before submitting replacement paid work." Caller abort
reasons propagate unchanged, retaining the unresolved intent.

Ordinary model generation through the provider uses Pi's existing request lifecycle.
Pi Durable does not currently pass a durable generation task ID to a provider, so its
automatic crash recovery can start another paid generation. Submission deduplication
does not deduplicate those model calls. The example disables SDK and harness live
retries; that does not change crash recovery. Use `flop_infer` for jobs that need a
durable external request ID. Transport cancellation stops waiting and does not reverse
already consumed compute or settled payments.

## Native devnet qualification

Before authorizing a live run, read the public ready and supported catalogs:

```bash
curl --fail --silent --show-error https://api.flop.network/v1/models
curl --fail --silent --show-error 'https://api.flop.network/v1/models?availability=supported'
```

Select an explicitly priced physical model advertising `tools` and `max_tokens`.
Default Pi requires ready capacity; its `coldStart` option reuses the public chat
admission and waiting path used by Hermes public, without changing the Messages bridge.
On 24 October 2026, public discovery showed ready `qwen/qwen2.5-0.5b-instruct`
without tools and supported `qwen/qwen3.8-27b` with tools but availability
`unknown`. That observation is not present readiness or permission to load it.
A cold-only model requires the bounded opt-in above and authorization to load it.
Do not invent prices or remove Pi's tool-capability filter to admit the older model.

The local regression is reproducible without a signer, chain or provider:

```bash
GATEWAY_DB_PATH=:memory: pnpm --filter @flop-labs/rest-gateway exec node --import tsx --test tests/pi-durable.test.ts tests/openrouter-contract.test.ts
pnpm --filter @flop-labs/pi exec node --import tsx --test tests/durable.test.ts
```

It exercises the native public provider's real catalog and streamed tools, then
composes the real HTTP serializer, shared client, native tool and SQLite, with
a synthetic broker and scripted orchestration model. After the gateway caches a
response it withholds delivery, observes a `SIGKILL` of its isolated child host,
then reopens the same database and resubmits the same job. The original ID, cap
and wait survive; there is one broker
effect. Pending and settled responses are distinguished, and reservations one
atomic unit over the cap and at 50 FLOP remain unresolved. This is fixture evidence,
not real devnet inference, live-host restart or payment acceptance.

The remaining live receipt needs all of the following, under separate authorization:

1. The deployed gateway revision includes the public `usage.cost_flop` projection;
   the original serializer omitted the field that `flop_infer` requires. Record
   the exact gateway and client revisions and selected model identity/availability.
2. An operator supplies a bounded, model-scoped credential through the approved
   injection path, with explicit quota, output-token limits and allowed spend for
   both orchestration and tool calls. No signer or new funding is required here.
3. Run the existing example with one private SQLite file and a fixed job ID. Capture
   the tool's saved request ID, original cap and checkpoint before interruption.
   Use an actual supervisor kill after gateway acceptance but before local result
   persistence; reopening must reuse that same file, credential, gateway and job.
4. Retain the original HTTP response, native recovered output and receipt, plus
   authenticated gateway reconciliation for the original request. Prove one logical
   tool charge and identical recovered output. Keep `pending` evidence labelled
   pending until actual final spend is available. Do not substitute a replacement
   request when the original returns an ambiguity error.
5. Record ordinary orchestration calls separately: their crash recovery is not
   exactly-once. A conversation using the FLOP provider does not acquire that
   guarantee merely because its `flop_infer` tool has a durable identity.

Deploying this repair, warming capacity, spending quota and interrupting a live
process are operator gates. Local tests do not authorize them. This inference-only
qualification does not ratify paid-memory acceptance or change #1842, #1840 or
#2276 claims and gates; `finalizedPaymentClaimed:false` and
`fullIssueAcceptance:false` remain the preparation result.

## Memory and additional flows

This slice stores Pi transcripts, intents and receipt documents in SQLite. FLOP paid
memory remains governed by yellowpaper §5.4's activation gates; current main has a
development-only paid-storage pallet, not a production remote Pi storage backend.

The experimental [memory adapter](MEMORY.md) now supplies native remember/recall tools,
namespace isolation and recovery orchestration over a host-supplied verified client.
Its encrypted local fixture tests adapter conformance; the actual paid-memory SDK and
provider binding remain pending.

| Flow | Native Pi Durable integration point | Follow-up work |
|---|---|---|
| Encrypted long-term memory | Extension tools, documents and prompt sections | Paid-storage SDK/provider backend; authorize reads, write immutable encrypted objects, advance quorum-receipted heads. |
| Recall and compaction | `GenerationTask.beforeRequest`, `CompactionTask.beforeCompact` | Retrieve authorized content client-side; retain original history; batch semantic-memory writes at compaction boundaries. |
| Remote harness persistence | `Storage.commit` and the storage conformance suite | Preserve atomic transcript/task/document commits and historical forks. Async snapshot export has a recovery window; authoritative remote commits must be durable before acknowledgment. |
| Subagents | Task-owned conversations and conversation documents | Separate namespaces and narrower capabilities inside the payer's reservation; reserve child budgets from a shared ledger. |
| Settlement and storage renewal | Versioned durable tasks and restart-safe timers | Persist operation intent, reconcile chain state, wait for finality, then commit the outcome. Cancellation must preserve settlement obligations. |
| Generation-level request identity | Task-aware provider request metadata | An upstream Pi API seam carrying a stable request ID and response evidence through generation. Do not use global mutable headers across concurrent conversations. |

Memory authenticity proves which bytes were stored, not that their statements are true.
Semantic/vector search can run client-side; sending decrypted memory to inference needs
separate compute/privacy authorization. Financial admission belongs at the gateway or
task boundary: Pi's observational generation hooks report ordinary errors and continue,
so throwing from a `beforeRequest` hook is insufficient to enforce a spending limit.

References: [Pi Durable announcement](https://earendil.com/posts/pi-durable/),
[Pi Durable specification](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/spec.md).
