Runtime Persistence Adapters
Updated 17 days ago • September 16, 2026
This page explains the backend Kortyx uses to store runtime state for paused runs.
In code, the API is named frameworkAdapter.
Read this page if:
- you use interrupts or resume
- you need paused runs to survive restarts
- you want to choose between in-memory, Redis, and PostgreSQL
If you are only testing locally, you can usually use the default and come back later.
What this adapter stores
- pending interrupt requests
- checkpoints for paused runs
- user-facing session checkpoints for rollback, fork, regenerate, and undo
- runtime execution state, with adapter-specific expiry and retention
Good to know: This adapter is only for Kortyx runtime state. Keep your app's business data in your own DB or service layer.
Recommended path
For most apps:
- local dev: pass nothing and use the default
- short-lived production resume: set
KORTYX_REDIS_URL - month-long history: configure PostgreSQL and complete schema setup before traffic
- create adapters manually when you want explicit control in code
Good to know:
createFrameworkAdapterFromEnv()is not a third backend. It is the default helper that chooses between in-memory, Redis, and PostgreSQL.
Where this is used
Most apps do not need to pass frameworkAdapter manually.
If you do nothing, createAgent(...) falls back to createFrameworkAdapterFromEnv().
That means:
KORTYX_POSTGRES_URLpresent -> PostgreSQL, with optional Redis caching- otherwise Redis env var present -> Redis
- otherwise -> in-memory
You pass frameworkAdapter to createAgent(...) only when you want explicit control.
In-memory
Use this for local development, demos, or quick testing.
- stores pending requests in process memory
- keeps checkpoints only in that running process
- is not restart-safe
- is not shared across multiple app instances
- caps session checkpoints by count per session
- does not have a global session checkpoint memory cap or session checkpoint TTL
Redis
Use this when paused runs must survive process restarts, deploys, or multiple app instances.
- stores pending requests, internal checkpoints, and session checkpoints in Redis
- supports resume after restart
- works better when you have more than one app instance
- uses the same Redis connection for all Kortyx runtime persistence key spaces
- applies TTL to Redis-backed runtime state
maxSessionCheckpoints controls how many user-facing session checkpoints are retained per session. The default is 50.
Default env-based selection
This is the default behavior used by createAgent(...) when you do not pass frameworkAdapter.
Resolution:
- PostgreSQL if
KORTYX_POSTGRES_URLexists (explicit schema setup required) - otherwise Redis if any of these exist:
KORTYX_REDIS_URL,REDIS_URL,KORTYX_FRAMEWORK_REDIS_URL - otherwise in-memory
TTL env variables:
KORTYX_FRAMEWORK_TTL_MSKORTYX_TTL_MS
Practical recommendation
- start with the default in local dev
- use Redis in production if you rely on interrupt/resume, rollback, fork, or regenerate
- use PostgreSQL for durable runtime history beyond Redis's TTL, with optional Redis caching
- lower
maxSessionCheckpointsfor high-volume apps when users do not need deep rollback history - do not use this adapter as a replacement for your app database
What to read next
Go back to Runtime Persistence if you want the high-level distinction between Kortyx runtime state and your app's business data.
PostgreSQL durable history
For month-long runtime history, use the PostgreSQL adapter. PostgreSQL is the source of truth; Redis optionally caches checkpoint payloads. The runtime schema is separate from your business tables and Studio telemetry.
The existing in-memory and Redis factories remain supported. Every built-in adapter implements ManagedFrameworkAdapter, adding the same maintenance.setup(), maintenance.prune(), and close() methods to the shared runtime API. Existing custom FrameworkAdapter implementations remain compatible. A new built-in lifecycle method belongs in the shared contract and must be implemented by every backend.
setup() creates the PostgreSQL schema; it is a no-op for Redis and memory. Redis prune() returns zero deletion counts because Redis already expires keys through native TTL. Memory prune() removes expired approvals in bounded batches; session history remains count-limited, with no global session inactivity policy. PostgreSQL applies the configured history/session retention. The common API does not make these storage guarantees identical.
PostgreSQL setup also applies pending SDK schema migrations in version order. Each migration and its version record commit together under a schema advisory lock. Already-applied versions are skipped, and concurrent setup calls are serialized. If a migration fails, its changes roll back, setup rejects, and the next deployment attempt resumes from that version; earlier successful migrations remain committed. A schema newer than the SDK or a gap in migration history is rejected. Run setup in a deployment command and stop deployment on failure. Applied SQL is verified against a stored SHA-256 checksum; editing it causes setup to reject. Migration advisory and DDL lock waits are capped at five seconds, preserving stricter database settings; a timeout rolls back that migration and requires retry. This bounds lock acquisition, not migration execution time. The original version-only v1 ledger adopts the current v1 checksum once, without rerunning schema SQL; its prior contents cannot be verified retroactively. For rolling deployments, schema changes must remain compatible with older running instances. SQL migrations do not automatically adapt historical workflow state stored inside JSON snapshots.
Configure composition before serving requests. createCachingFrameworkAdapter({ storage, cache }) attaches a cache to the supplied storage adapter and returns that same adapter, preserving its methods, type, and identity. It currently supports PostgreSQL storage with a Redis cache; unsupported combinations are rejected. The cache adapter's ttlMs controls cache lifetime, while the storage adapter's ttlMs controls approvals. Composition uses a separate cache key space and does not call Redis's approval/session stores or run cleanup.
The returned adapter owns both connections. Close it once on shutdown after executions finish. Give each storage adapter its own cache adapter; do not share that cache adapter with another storage adapter or use it independently as authoritative storage. Configuring a second cache on the same storage or using an already-owned cache is rejected.
maintenance.prune() is a server-side library method. Your app schedules it; Kortyx determines what is safe to delete. No cleanup timer or HTTP endpoint is installed automatically. If your scheduler calls an HTTP endpoint, create and authorize that endpoint in your app.
The current head of a retained session, unexpired pauses, and executing runs are protected. Reads enforce expiry before physical cleanup. Session expiry ends access to its history; choose a session window at least as long as the history window if you want the full history available after inactivity. Browsing history does not extend session lifetime. Rollback retains abandoned branches, which checkpoint summaries identify through branchStatus.
Cleanup removes at most batchSize parent records per table (maximum 1000), with graph writes deleted with their owning checkpoint. Inspect result.deleted, result.skipped, and result.hasMore to monitor or repeat the job. Calls are transactional and retry-safe. Call await persistence.close() on host shutdown after executions finish.
Env-based selection uses PostgreSQL when KORTYX_POSTGRES_URL exists, optionally caching through the configured Redis URL. It does not use the application's DATABASE_URL. Complete explicit setup before traffic:
Durable storage supports resume, rollback, and fork. Continue with compatible workflow code and state contracts. Exact reproduction across code/model/tool changes requires additional versioned artifacts and side-effect idempotency; storage alone cannot provide it.
Backend data transfer is outside the adapter contract. Changing storage does not import existing runtime data. PostgreSQL is required even for Redis cache hits, because it validates visibility and revisions. Reuse an adapter per application process to reuse its database connection pool.
Approval consumption is atomic across workers, but a worker crash after consumption does not automatically retry the resume. External actions, such as payments or API writes, still need application-owned idempotency. Retention cleanup deletes Kortyx runtime records for the configured namespace; it does not undo those external actions or delete your business records.
PostgreSQL durability adds database round trips; it does not promise zero added response latency. Tokens stream while the engine saves graph checkpoints asynchronously, and cache population does not delay authoritative reads. Session restoration, execution lease acquisition, and durable pause publication still require acknowledgements. Cache lookups have the configured time budget, and a Redis failure bypasses the cache for five seconds. Maintenance runs through your scheduled job. Measure time to first token and execution completion against Redis using your actual database location, workload, and concurrency.