Durability
A libfx session outlives the process that runs it. libfx saves each prompt and each step of a turn as it happens, so when a server crashes, redeploys, or reaches a function's time limit, the next process continues the turn where it stopped. Your code stays the same.
Available in libfx@dev
Durable sessions are in the dev release of libfx until 0.0.14. Install it with npm install libfx@dev.
Use a session
Create one agent for your server, and open a session for each conversation:
import { createFxAgent } from 'libfx'
export const agent = createFxAgent({ model: 'anthropic/claude-opus-5.5' })
const turn = agent.session().prompt('Plan the migration.')
const { sessionId } = await turn.accepted
// Later, from any process:
agent.session(sessionId).prompt('Start with the schema.')
turn.accepted resolves once the prompt is saved, so an accepted prompt is never lost. To show a conversation, return agent.session(sessionId).stream(cursor) from a route. Any server can serve it, and a client that loses its connection reconnects from the last cursor it read.
One process runs a session's turns at a time. A process that was replaced while it ran is refused at its next write, so a session's history never mixes two writers.
Where sessions live
libfx chooses where to keep sessions from the environment, so there is nothing to configure:
| Environment | Sessions live in |
|---|---|
| Vercel | Vercel's World, the store and queue behind Vercel Workflow |
| Node.js | Files in FX_SESSIONS_DIR, or in the system's temporary directory |
| Browsers | The page's memory, for the life of the page |
On Vercel
Add one route, app/api/libfx/route.js:
import { agent } from '@/lib/agent'
export const POST = agent.wakeHandler()
Then subscribe it to libfx's queue in vercel.json:
{
"functions": {
"app/api/libfx/route.js": {
"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "__libfx_wkf_workflow_session" }]
}
}
}
That's all. prompt() sends each turn to Vercel Queues, and this route runs it. A turn whose function crashes continues about 15 seconds later, and a turn longer than the function's time limit continues in the next invocation. libfx reaches AI Gateway with the deployment's OIDC token, so there is no key to set.
Tools with effects
A turn can stop while one of its tool calls runs. Mark the tools that are safe to run twice with idempotent: true, and pass executionId to the services your other tools call:
const byId = { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] }
const tools = [
{
name: 'get_order',
description: 'Look up an order.',
idempotent: true,
inputSchema: byId,
execute: ({ id }) => orders.get(id),
},
{
name: 'refund_order',
description: 'Refund an order.',
inputSchema: byId,
execute: ({ id }, { executionId }) =>
payments.refund(id, { idempotencyKey: executionId }),
},
]
When the turn continues, an idempotent call runs again. Any other call never runs again on its own: the model hears that it may have partly run, so it can check before trying again or ask the user. executionId stays the same each time a call runs, so a payment service that uses it as an idempotency key never refunds twice.
Your own store
To keep sessions somewhere else, such as Workflow's Postgres World, pass a World to world() from libfx/durable-world. The libfx README covers its options.