Runtime backends
InProcRuntime. The default
No journal, no durability. Crashes lose everything. Used implicitly
when you don’t pass runtime=.
from loomflow import Agent
agent = Agent("...", model="claude-opus-4-7") # InProcRuntime under the hoodHot-path overhead is zero. The agent loop’s runtime.step(name, fn, *args) collapses to await fn(*args) directly.
SqliteRuntime. Single file, no infra
from loomflow import Agent
from loomflow.runtime import SqliteRuntime
agent = Agent(
"...",
model="claude-opus-4-7",
runtime=SqliteRuntime("./journal.db"),
)Each call to runtime.step(...) writes a row to the journal table
keyed by (session_id, step_name). On a fresh SqliteRuntime
pointing at the same DB file, replaying the same session reads the
cached row instead of re-executing.
The schema is a single table; no migrations needed. Multiple agents can share the same DB file (SQLite WAL handles concurrent writers).
Streaming steps
Architectures that stream model responses (runtime.stream_step(...))
journal the chunks as a JSON array. On replay, the chunks emit in
order without hitting the network.
PostgresRuntime. Multi-instance durable
from loomflow import Agent
from loomflow.runtime import PostgresRuntime
runtime = await PostgresRuntime.connect(
dsn="postgres://user:pw@host/db",
schema="jeeves_journal", # optional schema namespace
)
agent = Agent(
"...",
model="claude-opus-4-7",
runtime=runtime,
)Install: pip install 'loomflow[postgres]'.
Schema migrations are idempotent. First connect runs the CREATE TABLE IF NOT EXISTS automatically.
When two instances of your service share the same Postgres journal, both can resume any session. Useful for blue/green deploys and distributed worker pools.
JournalStore. The lower-level primitive
SqliteRuntime and PostgresRuntime are convenience wrappers over
JournaledRuntime + JournalStore:
from loomflow import Agent
from loomflow.runtime import JournaledRuntime, SqliteJournalStore
store = await SqliteJournalStore.connect("./journal.db")
runtime = JournaledRuntime(store=store)
agent = Agent("...", model="claude-opus-4-7", runtime=runtime)Implementations:
| Class | Backing store |
|---|---|
InMemoryJournalStore | Python dict (lost on exit) |
SqliteJournalStore | Single-file SQLite |
PostgresJournalStore | Postgres |
Useful when you want to share a JournalStore across multiple
runtimes (rare), or when you want to swap the backing store without
re-wrapping the runtime.
Per-backend feature matrix
| Feature | InProc | SQLite | Postgres |
|---|---|---|---|
| Single-process durability | ❌ | ✅ | ✅ |
| Multi-instance shared journal | ❌ | partial† | ✅ |
agent.resume(session_id, ...) | ❌ | ✅ | ✅ |
| Async network setup | n/a | minimal | yes |
| Hot-path overhead | zero | small | network roundtrip |
| Production-recommended | dev only | single instance | multi-instance |
† SQLite supports multiple readers/writers via WAL mode but isn’t ideal for high-write workloads across many instances.
Don’t share InMemoryJournalStore across processes. It’s a
Python dict. There’s nothing to share. The class exists for tests
and for the in-proc default; don’t reach for it in production
workflows that need durability.
Cleanup
The journal grows monotonically. For long-running services, expire old sessions periodically:
# SqliteRuntime
await runtime.purge_before(datetime(2025, 1, 1, tzinfo=UTC))
# PostgresRuntime
await runtime.purge_before(datetime(2025, 1, 1, tzinfo=UTC))A reasonable policy: purge sessions older than 30 days, since most resumable workloads are short-lived. For audit / compliance, keep the audit log separately. That’s the permanent record.