Skip to content

Session Stores

GodeX implements the OpenAI Responses API's previous_response_id mechanism, which lets callers chain multiple requests into a conversation without manually replaying message history. To make this work, every generated response must be persisted with its request snapshot so that subsequent requests can walk the chain of parent pointers and reconstruct the full conversation context. ResponseSessionStore is the storage interface that makes this possible, with two implementations: an in-memory store for tests and single-process deployments, and a SQLite-backed store for production persistence.

The store boundary is deliberately narrow -- it persists API-shaped snapshots (not provider-specific chat messages) and delegates chain traversal to a shared resolveResponseSessionChain function. This keeps the store unaware of provider-specific protocol details.

At a Glance

ComponentFilePurpose
ResponseSessionStoretypes.ts:99-120Storage interface
MemoryResponseSessionStorememory.ts:19-66In-memory Map-backed store
SQLiteResponseSessionStoresqlite.ts:36-149SQLite-backed store with WAL mode
assertCanSaveSessionsave-policy.ts:13-40Prevents accidental overwrites
StoredResponseSessiontypes.ts:23-38Persisted turn shape
createResponseSessionStoresession-store-factory.ts:8-16Factory from config

Stored Types

StoredResponseSession

The core persisted type (types.ts:23-38):

FieldTypeDescription
idResponseIdResponse ID, used as previous_response_id by future requests
previous_response_idResponseId?Parent pointer, forming a linked list of turns
conversation_idConversationId?Reserved for future Conversation API compatibility
created_atnumberUnix timestamp
completed_atnumber?Timestamp when generation finished
statusResponseStatus"completed", "incomplete", etc.
requestStoredResponseRequestSnapshotSnapshot of the original request
responseStoredResponseSnapshotSnapshot of the generated response
metadataRecord<string, unknown>?Optional user-provided metadata

StoredResponseRequestSnapshot

A minimal subset of the original request (types.ts:46-56) needed for history reconstruction: input, instructions, model, tools, tool_choice, parallel_tool_calls, reasoning, text, truncation.

StoredResponseSnapshot

The response data needed for history and diagnostics (types.ts:59-66): id, output, output_text, usage, error, incomplete_details.

ResponseSessionStore Interface

MethodDescription
get(responseId)Return one stored response by ID, or null
save(session, options?)Persist a response snapshot; checks save policy
resolveChain(prevId, options?)Walk parent pointers to build a ResponseSessionSnapshot
delete(responseId)Remove one response by ID
close()Release resources (database connection, etc.)

MemoryResponseSessionStore

The in-memory implementation (memory.ts:19-66) uses a Map<ResponseId, StoredResponseSession>:

  • Cloning on read/write: Every get() and save() call passes through cloneStoredResponseSession (snapshot-clone.ts:4-7), which uses structuredClone to prevent callers from mutating persisted state through held references.
  • Constructor seeding: Accepts an optional array of initial sessions for test setup (memory.ts:22-25).
  • Clear method: Exposes clear() for test teardown (memory.ts:63-65).

SQLiteResponseSessionStore

The SQLite implementation (sqlite.ts:36-149) uses bun:sqlite for persistence:

Schema

The schema is auto-migrated via migrateResponseSessionSchema (sqlite-schema.ts:3-23) with indexes on previous_response_id and conversation_id for fast chain lookups.

Row Mapping

The sqlite-row-mapper.ts module handles bidirectional conversion:

FunctionDirection
sessionToSQLiteParamsStoredResponseSession -> SQLiteResponseSessionParams (JSON-encodes snapshots)
sqliteRowToSessionSQLiteResponseSessionRow -> StoredResponseSession (JSON-parses snapshots)

JSON columns store request_json, response_json, and metadata_json as serialised strings (sqlite-row-mapper.ts:32-34).

Save Operation

The save method (sqlite.ts:64-105) uses INSERT ... ON CONFLICT(id) DO UPDATE (upsert), but only after the save policy passes. It checks for an existing record and calls assertCanSaveSession.

Sync Reads

All reads are synchronous (getSync) because bun:sqlite is synchronous. The get and resolveChain methods wrap sync calls in async to satisfy the ResponseSessionStore interface (sqlite.ts:60-62).

Save Policy

assertCanSaveSession (save-policy.ts:13-40) enforces two guards:

GuardConditionError Code
Parent mismatchexpected_previous_response_id does not match the session's previous_response_idSESSION_CONFLICT
Overwrite preventionAn existing record exists and overwrite is not trueSESSION_CONFLICT

Both throw SessionError with the SESSION_CONFLICT code (codes.ts:43).

Store Factory

createResponseSessionStore (session-store-factory.ts:8-16) reads the SessionConfig to choose the backend:

config.backendStore Created
"sqlite"SQLiteResponseSessionStore with config.sqlite.path or default path
Any other valueMemoryResponseSessionStore

Store Selection Flow

Cross-references

  • Chain Resolution -- how resolveChain walks parent pointers and builds input_items

References