# FLOP with Cloudflare PiHarness

Cloudflare's [Pi harness guide](https://developers.cloudflare.com/agents/harnesses/pi/)
and [October 2 announcement](https://developers.cloudflare.com/changelog/post/2026-10-02-pi-harness/)
provide the hosting layer for FLOP's native Pi Durable adapter. `PiHarness` supplies
Durable Object SQLite and Lifecycle wake-up jobs; FLOP supplies its physical-model
provider and bounded `flop_infer` tool. The harness remains beta.

## Install

In a Cloudflare Worker application, use the announcement's pnpm command:

```bash
pnpm add agents@latest @earendil-works/pi-durable@~1.4.0 @earendil-works/pi-ai@~1.4.0
pnpm add /path/to/flop-core/sdk/pi
```

For experimental memory, also declare the SDK as a direct application dependency:

```bash
# From the FLOP checkout, build the packages whose exports use dist/.
pnpm install --frozen-lockfile
pnpm --filter @flop-labs/contracts build
pnpm --filter @flop-labs/client build
pnpm --filter @flop-labs/sdk build
# In the host application, add the built local SDK explicitly (pnpm strict resolution).
pnpm add /path/to/flop-core/sdk/typescript
```

For an application outside the checkout workspace, use locally packed SDK/client/contracts
artifacts with the workspace dependencies resolved to those local artifacts; this is an
unreleased checkout integration, not a new public package release.

The second command installs FLOP from a checkout. This integration uses Pi Durable 1.4 and
is checked against published `agents@0.26.0`; commit the application's lockfile.
The Pi packages are optional peers of `agents`, so install both explicitly.

Experimental owner-only memory uses `@flop-labs/sdk/experimental` and
`createFlopPaidMemoryClient` from `@flop-labs/pi/memory/paid-storage`. Import
`@flop-labs/sdk/experimental/cloudflare` **first**, before Pi, Agents, FLOP or
Polkadot imports; then await `initializeCloudflareMemoryCrypto()` from that module
before creating the memory client. This selects Polkadot's existing ASM sr25519
implementation because Workers disallow compiling WASM byte buffers. The tracked
Worker uses this bootstrap order; the Workerd test verifies native service signatures.

Use `durableObjectMemoryStore(ctx.storage)` with a SQLite Durable Object. It splits
SDK JSON records into small KV values in one storage transaction, including replacement
cleanup, so a record larger than the 2 MB KV value limit remains atomic. Pi's transcript
continues to use the native PiHarness SQLite journal; that transcript contains local
plaintext. Provider uploads contain SDK ciphertext and encrypted private manifests.
The host owns the KEK and owner signer; neither belongs in Pi tool arguments or references.
Applications disabling pnpm's automatic peer installation must also install the
required peers listed by `agents`. Cloudflare dependencies are development-only
in the FLOP package; ordinary Pi users can continue using `pi install npm:@flop-labs/pi`.

## Host it

The Worker example (`sdk/pi/examples/cloudflare/worker.ts` in the FLOP repository) follows the upstream
[example's factory pattern](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/pi).
Its `FlopPiAgent` registers `PiHarness` with `this.lifecycle.use(...)`. The factory
uses the supplied `storage` and `context` in `Harness.open`; do not open Node SQLite
or call `resume()` yourself. It registers FLOP's catalog provider and tool extension
before opening Pi. The default model references an available physical FLOP model.

The Worker reads `FLOP_API_KEY` through Pi's binding-local `authContext`, and passes
it explicitly to the paid tool. It neither changes `process.env` nor stores bearer
bytes in Pi documents. Use a gateway-enforced bounded child credential whose
aggregate budget covers orchestration generations and tools across its conversations.

Set these bindings/variables in the host:

| Binding | Meaning |
|---|---|
| `FLOP_SESSIONS` | SQLite-backed `FlopPiAgent` Durable Object namespace. |
| `FLOP_GATEWAY_URL` | Gateway origin reachable from the Worker. |
| `FLOP_API_KEY` | Secret bounded gateway credential. |
| `FLOP_MODEL` | Physical model ID from the gateway's drivable live catalog. |
| `FLOP_MAX_COST_FLOP` | Positive per-`flop_infer` ceiling in FLOP units. |
| `FLOP_MAX_TOKENS` | Positive integer output-token limit for that tool. |

Its Wrangler config (`examples/cloudflare/wrangler.jsonc` beside it) declares the SQLite class
migration and `nodejs_compat`. Replace its example gateway/model and cap values for
your environment. Put the key in a gitignored `sdk/pi/examples/cloudflare/.dev.vars` for local use;
manage deployed credentials as Worker secrets. Keep the key out of Wrangler `vars`.
Its compatibility date is pinned to `2026-08-22`, supported by the local test runtime.

## Submit over private RPC

The separately selected [authenticated A2A application](A2A.md) can wrap this execution
path with bounded admission and caller isolation. Its template also has no public routes;
the private default below is unchanged.

`FlopPiService` is a private Worker entrypoint. It has no public routes, disables
workers.dev/preview URLs, and returns 404 for HTTP. Bind it from an authorized
application Worker:

```json
{
  "services": [{ "binding": "FLOP_PI", "service": "flop-pi" }]
}
```

The calling application authenticates users and maps them to stable conversation IDs.
One named Durable Object owns each conversation's root Pi session. Reuse an inbound
event ID when retrying a submission:

```typescript
const receipt = await env.FLOP_PI.submit(chatId, prompt, eventId);
// Later, from another request to the same application:
const result = await env.FLOP_PI.wait(chatId, receipt.operationId);
```

Submitting the same operation ID in the same conversation returns `accepted: false`
and the original operation. Different conversation objects can use the same event ID.
The native tool stores its gateway request ID, ceiling and correlated settlement
metadata in `FlopInferenceDoc` on the object's SQLite storage. API-key requests return
a `pending` checkpoint; its final spend comes from the gateway's receipts (DURABLE.md).

## Retention policy

The example retains each conversation's transcript, tasks and paid-operation records
for its lifetime, without automatic expiry. Use it for bounded conversations. For a
long-running deployment, the calling application must enforce a maximum conversation
duration and operation count before forwarding submissions, and stop admitting work
when either bound is reached. Reusing an object does not bound its history.

The application owner owns retirement: stop admission, reconcile all active or ambiguous
paid work, and archive the completed transcript and correlated payment evidence. Retain
unresolved operation state until reconciliation completes. Persist the retired conversation
ID outside its object and permanently reject later submissions/retries to that ID before
an administrator deletes its Durable Object storage. Keep that retirement record when
removing the archived history: recreating an old object can issue new paid request IDs.
The service exposes no cleanup RPC; implement and verify this owner-controlled retirement
path before using the example for long-running traffic.

## Accounting and memory boundaries

The tool's original gateway ID survives Pi recovery. Gateway idempotency owns duplicate
effect prevention; Pi owns intent/receipt persistence. A `409 request_in_progress` or
invalid receipt leaves work unresolved and requires reconciliation before replacement.
See [DURABLE.md](DURABLE.md) for the exact receipt/admission contract.

Ordinary model generations still lack durable external request IDs. Cloudflare recovery
can repeat a paid model call; submission deduplication does not deduplicate its billing.
The example disables SDK and harness live retries, and the gateway's aggregate credential
budget must bound all generation and tool spend. Cancellation does not undo settled work.

The [memory extension](MEMORY.md) is available from `@flop-labs/pi/memory`. Install
it on the same registry before `Harness.open`, with an authorized
verified client and its own atomic-unit storage ceiling. Durable Object transcripts
remain local plaintext. The experimental binding supplies the native SDK connection;
provider activation and the authenticated payment/restart/recall drill remain separate
acceptance gates. Hosting Pi on Cloudflare does not activate production storage or
authorize sending decrypted memory to inference.

## Check locally

From this checkout:

```bash
pnpm install --frozen-lockfile
pnpm --filter @flop-labs/pi typecheck
pnpm --filter @flop-labs/pi test:cloudflare
pnpm --filter @flop-labs/pi build:cloudflare
pnpm --filter @flop-labs/pi dev:cloudflare
```

The tests use published Cloudflare/Pi packages in local workerd, real Lifecycle SQLite
and mocked gateway HTTP. They check binding authentication, native tool caps/receipts,
submission deduplication, object isolation and rejected configuration/quota/HTTP paths.
Mocked receipts and charges are integration fixtures, not live billing or attestation
evidence. These checks do not establish mid-flight crash/alarm recovery. The build
command is a Wrangler dry run; local verification needs no deployment or live inference.
