Skip to content

Session stores

Session stores persist snapshots for server-managed agents. They are the storage layer behind sessionId, snapshotId, loadChat(), snapshot reads, branching, background execution, and aborting detached work.

Use the Sessions and state guide first when you are deciding between server-managed and client-managed state. Use this page when you know the server should own state and need to choose or implement the persistence layer.

  • In-memory store (InMemorySessionStore) for tests, local examples, command-line interfaces, and single-process experiments.
  • File store (FileSessionStore) for local development, prototypes, and single-host applications where snapshots must persist across process restarts.
  • Firestore store (FirestoreSessionStore from genkit-google-cloud) for production apps where several server instances share sessions.
  • Custom store (implementing SessionStore) when you need a different shared database such as Cloud SQL, Spanner, Postgres, or Redis.

Configure the store on the agent. The runtime handles reads and writes. With HTTP serving, /getSnapshot and /abort are mounted only when a store is configured.

InMemorySessionStore keeps snapshots in local process memory. It is fast and requires no setup.

from genkit.agent import InMemorySessionStore
store = InMemorySessionStore()
support_agent = ai.define_agent(
name='supportAgent',
model='googleai/gemini-flash-latest',
system='Help customers with their orders.',
store=store,
)

Do not use the in-memory store if session history must survive process restarts or when scaling horizontally across multiple server instances.

FileSessionStore stores snapshots as JSON files inside a local directory. This is the standard choice for local development or single-host deployments.

from genkit.agent import FileSessionStore
store = FileSessionStore('.sessions')
weather_agent = ai.define_agent(
name='weatherAgent',
model='googleai/gemini-flash-latest',
system='You help with weather questions.',
store=store,
)

Snapshots are saved as files inside the .sessions folder.

FirestoreSessionStore in genkit-google-cloud persists snapshots in Cloud Firestore. It is the built-in option for production apps where several server instances share sessions, and it supports snapshot watching for background execution and abort.

from genkit_google_cloud import FirestoreSessionStore
def by_user(context: dict | None = None) -> str:
if isinstance(context, dict) and isinstance(context.get('uid'), str):
return context['uid']
return 'global'
store = FirestoreSessionStore(
collection='genkit-sessions',
snapshot_path_prefix=by_user,
)
support_agent = ai.define_agent(
name='supportAgent',
model='googleai/gemini-flash-latest',
system='Help customers understand their order status.',
store=store,
)

All options are optional:

  • client is an explicit Firestore AsyncClient. It defaults to a new client that picks up Application Default Credentials and FIRESTORE_EMULATOR_HOST.
  • collection is the collection that holds snapshot documents. It defaults to genkit-sessions. Two companion collections, <collection>-pointers and <collection>-shards, are derived from it.
  • snapshot_path_prefix returns a per-tenant prefix from the call context (for example an authenticated user id). When set, snapshots are nested under that prefix so one tenant cannot read another’s data even with a snapshotId. It defaults to global.
  • checkpoint_interval is the number of turns between full-state checkpoints. Between checkpoints the store writes diffs. It defaults to 25.
  • shard_size is the maximum size in bytes of a single shard or diff document. It defaults to 512 KiB.

The Firestore store watches snapshot documents to observe status changes, so background execution and aborts work across instances without polling. It does not prune snapshots, so plan retention for long-running or artifact-heavy conversations as described in Production guidance.

When the built-in Firestore store does not fit, implement a custom SessionStore:

from genkit.agent import SessionSnapshot, SessionStore
class MyDatabaseSessionStore(SessionStore):
async def get_snapshot(
self,
*,
snapshot_id: str | None = None,
session_id: str | None = None,
context: dict | None = None,
) -> SessionSnapshot | None:
# Load by snapshot_id, or resolve the latest leaf for session_id.
...
async def save_snapshot(self, snapshot_id, fn, *, context=None) -> SessionSnapshot | None:
# Atomic read-modify-write: call fn(existing) and persist the result.
async with self.lock:
existing = await self._read(snapshot_id)
updated = fn(existing)
if updated is not None:
await self._write(snapshot_id, updated)
return updated
  • get_snapshot fetches a snapshot by exact snapshot_id or resolves the latest snapshot in the sequence for session_id.
  • save_snapshot must be atomic. Run the read, mutator execution, and write inside a single database transaction, or synchronize with a lock. The mutator must be side-effect free — stores may call it more than once under contention.
  • self.lock: SessionStore automatically provides a loop-local asyncio.Lock via self.lock on every store instance. You can use async with self.lock: inside save_snapshot to synchronize in-process read-modify-write operations without instantiating locks manually.

For detach and abort support, also implement SnapshotSubscriber so clients can poll status changes.

  • Security: Treat snapshots as sensitive user data. They can contain raw message history, tool results, and personal information. Apply authorization checks in your API or store layer before returning snapshot data.
  • Payload size: Because snapshots contain full conversational checkpoints, their size grows over long sessions. Plan database indexes and cleanup/archival routines before launching production systems.