Skip to main content

Persistence

flow-state.dev stores three categories of data: scope state (session, user, project), resources (content files with metadata), and items (the accumulated conversation log). All of this goes through a store abstraction. The server ships with an in-memory store by default. Swap it for a file store or MongoDB when you need data to survive restarts.

Store adapters

AdapterPersistenceWhen to use
In-memory (default)None — data is lost on restartDevelopment, testing, demos
FileJSON files on diskLocal development with persistence, single-server deployments
MongoDBMongoDB collectionProduction, multi-server deployments

In-memory (default)

The default. No configuration needed. All state, resources, and items live in memory. Fast, zero dependencies, gone when the process exits.

import { createFlowApiRouter, createFlowRegistry } from "@flow-state-dev/server";

const registry = createFlowRegistry();
registry.register(myFlow);

const router = createFlowApiRouter({ registry });
// Uses in-memory stores by default

File store

Writes state, resources, and items to JSON files in a directory. Each scope gets its own file. Good for local development when you want data to survive server restarts.

import {
createFlowApiRouter,
createFlowRegistry,
createFileStore,
} from "@flow-state-dev/server";

const store = createFileStore({ directory: "./.flow-state-data" });

const registry = createFlowRegistry();
registry.register(myFlow);

const router = createFlowApiRouter({ registry, store });

The directory structure mirrors the scope hierarchy: sessions, users, and projects each get their own subdirectory.

MongoDB

For production. Stores state, resources, and items in MongoDB collections with atomic operations.

import {
createFlowApiRouter,
createFlowRegistry,
createMongoStore,
} from "@flow-state-dev/server";

const store = createMongoStore({
uri: process.env.MONGODB_URI!,
database: "flow-state",
});

const registry = createFlowRegistry();
registry.register(myFlow);

const router = createFlowApiRouter({ registry, store });

MongoDB provides the concurrency safety that CAS (Compare-and-Swap) operations rely on. In-memory and file stores serialize writes, which works for single-process development but doesn't scale.

What gets persisted

DataWhere it livesPersistence behavior
Scope stateSession, user, project scopesAlways persisted (except request scope, which is ephemeral)
ResourcesAttached to scopesAlways persisted with their scope
ItemsSession item logPersisted by default. status items are always transient. state_change/resource_change are transient in production.
Request stateRequest scopeLives for one action execution, then discarded
Sequencer stateSequencer executionIn-memory only, never persisted

Custom stores

The store interface is pluggable. If you need Redis, PostgreSQL, or another backend, implement the Store interface:

interface Store {
getScope(identity: ScopeIdentity): Promise<ScopeData | undefined>;
setScope(identity: ScopeIdentity, data: ScopeData): Promise<void>;
deleteScope(identity: ScopeIdentity): Promise<void>;
listSessions(params: ListParams): Promise<SessionListResult>;
}

The exact interface may evolve. Check the @flow-state-dev/server package source for the current contract.

Choosing a store

  • Developing locally? Start with in-memory (default). Switch to file store when you want persistence across restarts.
  • Deploying a single server? File store works. It's simple and reliable for low-concurrency scenarios.
  • Production with multiple servers? Use MongoDB. You need a shared data store with proper concurrency semantics.
  • Special requirements? Implement a custom store against the interface.