# Pi Durable memory adapter (experimental)

`@flop-labs/pi/memory` adds native `flop_remember` and `flop_recall` tools, a memory
prompt section, and durable operation documents. It connects Pi to a trusted,
host-supplied memory client. It does not implement a paid-storage protocol or activate
production FLOP memory. The SDK binding below supports the experimental owner-only,
prefunded reservation path. Provider prerequisite landing and the authenticated restart
drill remain acceptance gates; production profile and delegation work remain #1568.

## Install the extension

Install Pi Durable 1.4 and pi-ai 1.4 (`~1.4.0`) in the application. The package ships
compiled ESM; only the repository examples need `tsx`.

```ts
import { createFlopMemoryExtension } from "@flop-labs/pi/memory";

registry.install(createFlopMemoryExtension({
  client: authorizedMemoryClient,
  maxCostAtomic: approvedPerOperationCeiling,
}));
```

The ceiling is a positive `bigint` in atomic FLOP units. The adapter persists its
decimal representation and requires a matching decimal-string cost in the result.
This is a per-operation ceiling; the client must enforce aggregate wallet, capability,
lane and byte limits. Pi's model-cost estimates in USD are separate.

The application constructs the client and owns its credentials and encryption keys.
The model receives only the tools, not a client constructor or signer. Injecting a
client does not supply an active profile, reserve funds or authorize a larger budget.
Select the extension and tools through Pi's usual per-conversation agent configuration.

## Host client contract

`FlopMemoryClient` deliberately has only an identity, `prepare`, and `execute`.
Bind these through `createFlopPaidMemoryClient` to the experimental paid-memory SDK, rather than implementing
wire formats inside this package.

- `identity` is a stable, public identifier binding deployment/genesis, reservation,
  credential/capability, and encryption-key generation. It must change when recovery
  under the old authority would no longer be valid. Keep secrets out of the identity.
- `prepare(input, signal)` checks admission, prepares an encrypted immutable version
  or resolves an authenticated read head, and durably retains a secret-free reference.
  It has no paid effects. The same request ID and input must yield the same version and
  reference after a client restart; retain encryption nonces and input bindings in the
  client's durable state. Private keys and plaintext must not be put in the reference.
- `execute(operation, signal)` publishes or reads exactly that prepared version. It
  owns authorization, profile rejection, CAS conflicts, encryption/decryption, receipt
  quorum and checkpoint verification, cumulative vouchers, and payment reconciliation.
  Repeating the same operation reconciles its original effect and charges; it must not
  issue replacement paid work. A verified read returns authenticated decrypted text.

The client maps Pi's opaque application namespace into its authorized SDK namespace and
binds it to its encryption and receipt domain. Each conversation has a fresh namespace;
forks and independent stores cannot silently inherit the parent's private memory.
There is no cross-namespace search or implicit delegation. Any broader sharing needs
explicit host authorization in the future SDK.

Only set `verified: true` after the client's actual checks have passed. Pi additionally
checks the request ID, namespace, prepared version, cost ceiling, receipt presence and
read content. This is correlation and admission checking, not independent cryptographic
verification by Pi. A host that injects an untrustworthy client can falsify its own data.

## Recovery and retention

Before preparation, Pi commits the instance/task request ID, namespace, client identity,
input digest and spending ceiling. It then commits the prepared version/reference before
execution, and completion evidence before returning success. Every tool task has one
member of `FlopMemoryOperationDoc`, a latest conversation document family keyed by
`String(taskId)`. Members survive tool completion and errors. Task-scoped Pi documents
would be deleted on terminal settlement. These latest records are for reconciliation,
not historical document snapshots.

```ts
const record = await harness.snapshot(
  FlopMemoryOperationDoc, conversationId, String(toolTaskId), context,
);
```

After interruption, the same task reuses its original identity, ceiling and prepared
version. A changed client identity blocks unfinished work. Once completion is recorded,
Pi can return its local result without invoking the client again.

Each memory invocation first prunes earlier completion payloads. The host can also run
`pruneFlopMemoryOperations(harness, conversationId, context, client)` when the conversation is
idle. A durable conversation index registers outstanding task IDs atomically with their
intents. Cleanup examines that index in batches, without scanning completed or unrelated
task history, and removes an ID atomically with its retired member. Forks start with an
empty index. Older unfinished tasks stay indexed even when newer tasks finish first.
The sweep retires a member only when its owner is
terminal and Pi's successful tool-result entry retains matching request/version/cost
and receipt metadata. With the paid SDK binding, it also checks the client identity and
canonical recall text before calling SDK retirement. An unresolved original voucher
prevents retirement. It preserves unresolved work and the receipt-persisted but
pre-terminal recovery window. A terminal tool task can still contain an error result;
that alone never authorizes retirement.

After a sweep, extra live payloads are bounded by unresolved operations and completions
since the last sweep. Hosts should schedule idle cleanup for conversations with no new
memory calls. SQLite retirement deletes this latest family's revision payloads; Pi keeps
document lifecycle metadata and its task/transcript history. It does not bound the
whole database or shrink its file; journal retention and compaction remain host/Pi work.
Completed receipts remain available in native transcript entries after pruning.

The SDK compacts completed shard/request duplicates transactionally, retaining range
proofs while an original GET is unresolved. Its client-only range commitments are
authenticated under a separate key derived from the wrapped object key; a response
must match its original range before its lane fence clears. Native pruning then removes
the SDK's duplicate recall content through `retire`. Hosts using the SDK directly must
durably retain the canonical result before calling that method. Executing a retired
SDK operation fails explicitly and never buys a replacement; a fresh recall uses the
retained immutable object key. Minimal operation tombstones, keys, vouchers, exposure
and current-head highwaters remain, so this does not bound the whole SDK database.
Preparations created before authenticated range metadata cannot start new incomplete
reads. Completed legacy reads without producer-minted replay authority and request
capsules cannot reconcile or retire their unresolved requests; their lane fences remain.
The SDK does not mint replacement authority from restored mutable rows.

An error leaves the original record available for reconciliation. A terminal tool error
does not automatically replay; a new tool task is a new operation. Reconcile ambiguous
acceptance through the original client's ledger before requesting replacement paid work.
Cancellation stops waiting and does not undo accepted writes or payments. The client
must retain its own durable preparation and payment state alongside Pi's journal.

Remote object encryption does not encrypt Pi's local transcript: tool arguments and
recalled plaintext are recorded locally, including in completion documents. Protect the
journal and its backups through the host. Sending recalled plaintext to a model requires
separate compute/privacy authorization; authentic stored bytes do not establish truth.

## Run the conformance example

The example uses a scripted Pi model and an encrypted local ledger. It does not call a
FLOP provider, spend FLOP, or implement ratified receipt/ciphertext formats. Its budget
and one-unit debits are simulated test values, not protocol tariffs. One live fixture
client owns its file.

Set `FLOP_MEMORY_FIXTURE_KEY` to a private 32-byte key encoded as 64 hex characters, then:

```bash
pnpm --filter @flop-labs/pi exec tsx examples/memory.ts ./memory-demo
```

It remembers and recalls `violet`, printing the native tool result. Reusing the same
directory and key finds the original SQLite submissions and encrypted fixture ledger.
The fixture keeps record names behind a keyed digest and encrypts content with
authenticated namespace/version bindings. It demonstrates the client contract only.

Tests close and reopen real Pi SQLite after fixture preparation, after one simulated
accepted write, after receipt persistence, and with a changed client identity. They
require the same immutable operation/version and one simulated write debit, then recall
the original content. They also exercise terminal-error retention, fork/store isolation,
unverified/mismatched evidence, activation or budget rejection, a CLI restart
between remember and recall, and pruning across multiple outstanding-task batches.
Read-identity checks after SQLite recovery, repeat sweeps and a subsequent invocation
prove that cleanup skips already-pruned and unrelated history while retaining unresolved
and other-conversation records.

The experimental SDK binding is `createFlopPaidMemoryClient` from
`@flop-labs/pi/memory/paid-storage`. It delegates preparation, client encryption,
vouchers and certificate/checkpoint verification to #1840's actual SDK. Pi retains
operation IDs, caps, secret-free preparation references and verified results in its
trusted local journal; tool inputs, recalled content and the transcript contain plaintext.
Provider prerequisites and the
authenticated restart/charge drill remain landing gates; unit fixtures are not live evidence.

Before first remember, open the Pi conversation and call
`initializeFlopMemoryNamespace(session, conversationId, context)`. Pass its returned
UUID and the known allowed key to `sdkClient.policyScope(namespace, key)`. The owner
must finalize `advance_policy` for both that opaque namespace and that opaque object
before invoking the tool. Preparation never provisions policy or pays. A fork gets a
new UUID and requires its own explicit policy provisioning.

The native binding waits only for typed `MemoryCheckpointPending` during preparation
or execution, under the Pi task's abort signal. Each finalized-head notification permits
another verification attempt with the exact original request and preparation; it is not
a checkpoint proof. Other policy, payment or integrity errors propagate. Cancellation
retains the durable intent for replay after restart, without replacement signing or debit.

SDK `costAtomic` is a compatibility alias for `signedExposureAtomic`, not finalized
charges. Receipts expose `unresolvedRequestIds`. A degraded successful recall can
retain an uncertain provider request: the host calls `sdkClient.reconcile` with the
original operation request, immutable preparation and original cap to replay only those
signed requests. It creates no replacement voucher and refunds no exposure. Persisted
result/content/head remain unchanged. Client-keyed authority binds the original lane set,
and separate request capsules bind exact signed bytes before dispatch. Reconciliation uses
that retained authority without fresh admission, including after policy revocation;
providers still decide whether an identical request was previously admitted. Capsules
are removed only with an authenticated response and ACK. New work still requires active
owner policy. If the original unaccepted request has expired or its policy was revoked,
reconciliation fails explicitly and retains its lane fence; new work on that lane requires
authoritative recovery. The separate provider/chain drill proves actual redeemed charges.

A context-only crash before voucher issuance can renew its unsigned request from
fresh admission. Context selection and the voucher's exact GET range commitment
commit together. Existing vouchers and signed bytes never renew; a voucher-only
request past its original lifetime keeps its exposure/fence and rejects a new owner
signature. New unpaid work still requires current owner policy before any charge.

An already stored, verified CAS certificate can finish checkpoint proof acknowledgment
after revocation. The SDK authenticates immutable preparation and the exact original
CAS admission/placements, then reads retained proofs against one fresh finalized root
snapshot. It does not sign, propagate CAS, upload shards or issue vouchers. Missing
publication stays pending; missing legacy context or certificate requires fresh admission.
Completion requires intact original budget, vouchers and resolved PUT lane fences.
A pre-CAS crash retains the original signing context; fresh admission remains mandatory
before creating its first signature, and the original lifetime cannot be refreshed.

Intact crash restart is supported. Restoring arbitrary old financial store snapshots
requires authoritative reconciliation: a known chain/local spend gap blocks new signatures.
Key wrapping uses a fresh random salt and one-use derived key, rather than claiming a
self-stored nonce counter can detect arbitrary store rollback. Preserve fresh OS randomness;
restoring a whole VM snapshot including its random generator is outside this contract.
Compaction batching, delegated memory and a remote Pi `Storage` backend follow those
contracts; this adapter retains SQLite as the authoritative Pi journal.
