
How to add persistent memory to a LangGraph agent with FishMem
August 23, 2026Guides

A production write protocol for receipts, idempotency, terminal state, retries, and accounting when model work outlives HTTP.
Memory extraction is not an ordinary database insert. A provider can accept work after the client times out. A queue can deliver the same message twice. A worker can commit records and crash before acknowledging delivery. If the API models all of that as one synchronous request, the client cannot know whether to retry, wait, or investigate.
A durable write protocol separates acceptance from completion. The HTTP request creates one command identity and returns a receipt. A persisted task owns execution. A terminal event owns the outcome.
Consider a client that sends an inferred memory write and waits five seconds. The provider call takes six seconds. At second five the client sees a timeout. At second six the worker commits two memories. If the client retries without a stable identity, the system may repeat inference and create duplicates. If it refuses to retry, it may leave the user believing the write was lost.
The protocol must make these states distinguishable:
| State | What it proves | Safe client action |
|---|---|---|
| Accepted | The command is durably recorded | Keep the receipt and observe the event |
| Pending | No worker currently owns a valid lease | Wait; repair or dispatch may reclaim it |
| Running | A worker owns an unexpired lease | Do not create a second command |
| Succeeded | Canonical records and required state committed | Read the result |
| Failed | Automatic execution reached a terminal error | Inspect error; authorize an explicit retry if appropriate |
FishMem Cloud validates the request, binds the authenticated workspace, hashes the normalized command, claims the idempotency key, reserves credits, and creates one memory_infer operation task. Only then is work dispatched. The HTTP response is 202 because the system has accepted responsibility for the command, not completed it.
The task payload contains the structural scope, inference input, idempotency identity, and hosted usage authorization needed to execute after the original request has disappeared. Successful completion replaces sensitive raw input in the durable task with a command fingerprint and the minimum result evidence needed for audit and replay.
An idempotency key is not a duplicate-removal hint. It means one caller intent has one identity.
Hash the semantic command fields, not transport noise. Scope, inference mode, message content, and memory options matter. A request ID, connection timestamp, or retry counter normally does not.
Cloudflare Queue, a cron job, or any other dispatcher may wake a worker, but delivery cannot be the only evidence that work exists. Messages can be duplicated, delayed, or exhausted. The persisted task records attempts, status, lease owner, lease expiry, next attempt time, terminal error, and final result.
A worker claims a task with compare-and-swap semantics. If it crashes, another worker can reclaim the expired lease. If the queue message never arrives, scheduled repair can find a pending task and dispatch it again. Both paths converge on the same operation identity.
Retrying a model call can produce different facts. That may be acceptable before any canonical write exists, but it is unsafe after a partial commit. FishMem's core journal freezes stable record identities and the prepared mutation plan before completing projections. A retry replays the same plan rather than inventing a second interpretation.
The operation journal tracks canonical and projection status separately. If the record commit succeeds but a vector write fails, retry repairs the vector for the existing record. It does not add the memory again.
A client should not have to parse worker logs to learn the outcome. The Event API exposes a privacy-safe projection of the durable task with status, attempts, timestamps, error information, and result references. SDKs can provide a convenience wait helper, but the underlying resource remains observable by any client that keeps the event ID.
Errors should be stable enough to drive a decision:
Hosted inference cannot maintain separate truths for work and money. FishMem reserves the required credits before enqueueing. On terminal success, the reservation settles once. On terminal failure, it is released and the request ledger records the refund. A manual retry must re-authorize usage before executing; an old failed task is not an unlimited claim on future credits.
This avoids three bad outcomes: charging for work that never ran, refunding after a successful commit, or charging twice after an ambiguous retry.
| Failure | Automatic retry? | Reason |
|---|---|---|
| Provider timeout before result | Bounded, if command is still safe | May be transient; idempotent task controls duplication |
| Invalid structured output | Only under an explicit extraction policy | Repeated prompts may not repair a semantic mismatch |
| Insufficient credits | No | Requires new authorization or plan change |
| Projection write failure | Yes | Repair the existing canonical record |
| Idempotency conflict | No | The caller reused one identity for two commands |
infer=false remains a synchronous deterministic write because the caller already supplies the durable record. infer=true is asynchronous by default because model work can outlive HTTP and fail independently. Conflating the two paths would either make deterministic writes unnecessarily complex or make inferred writes falsely synchronous.