LOST so you can wake the user and re-route.
~/.praisonai/runs/ledger.db.
Quick Start
recover_orphans() returns the list of records it marked LOST. It preserves each run’s channel and thread_id so the gateway can wake the same user back.How It Works
A run is recorded as it starts, updated as it progresses, and finalised with a terminal status. If a process dies while a run is still active, the next boot reconciles it toLOST.
Run statuses
RunStatus partitions every run into active (recoverable) or terminal (done).
| Status | Category | Meaning |
|---|---|---|
queued | active | Accepted, not yet started |
running | active | Executing |
waiting | active | Blocked on an external signal (approval, tool, human input) |
succeeded | terminal | Finished cleanly |
failed | terminal | Errored |
cancelled | terminal | Explicitly cancelled |
lost | terminal | Orphaned by a crashed process; set by recover_orphans() |
unknown | neither | Written by a newer process; never finalised so its real state is never lost |
is_active / is_terminal helpers:
RunStatus is a str enum, so RunStatus.RUNNING == "running".
Configuration Options
RunRecord
A durable record of a single run.channel and thread_id capture the origin route so the gateway can wake the user back.
| Field | Type | Default | Description |
|---|---|---|---|
run_id | str | (required) | Stable ID, survives restarts |
agent_id | str | "" | Which agent ran it |
channel | str | "" | Origin channel (e.g. "telegram") |
thread_id | str | None | None | Origin thread, to reply back |
status | RunStatus | RunStatus.QUEUED | Current lifecycle status |
progress | str | None | None | Human-readable progress summary |
terminal_outcome | str | None | None | Final result or error note |
created_at | float | time.time() | Creation timestamp |
updated_at | float | time.time() | Last-update timestamp |
metadata | dict | {} | Free-form extra data |
to_dict() / from_dict() roundtrip a record to a JSON/SQLite-friendly dict and back.
RunLedgerProtocol
The pluggable store contract — swap in a heavier backend by implementing these four methods.| Method | Returns | Description |
|---|---|---|
upsert(record) | None | Insert or update, keyed by run_id |
get(run_id) | RunRecord | None | Fetch one run, or None if unknown |
list_active() | list[RunRecord] | All runs still in an active status |
recover_orphans() | list[RunRecord] | Mark active runs LOST, return them (idempotent) |
SQLiteRunLedger
The zero-dependency default, backed by stdlibsqlite3.
| Option | Type | Default | Description |
|---|---|---|---|
db_path | str | None | ~/.praisonai/runs/ledger.db | Database file; use ":memory:" for tests |
- No new dependencies — stdlib
sqlite3only. - Thread-safe — a re-entrant lock guards a shared WAL connection.
recover_orphans()preserves the origin route and is idempotent (a second call returns[]).close()releases the connection; the file persists across restarts.
Common Patterns
Mark a run terminal on success
Wake users after a crash
List recent runs regardless of status
Best Practices
Call recover_orphans() once at boot
Call recover_orphans() once at boot
Run reconciliation on gateway startup, before accepting new work. Active runs left by the previous process become
LOST, and you get their origin routes back to notify users.Always set channel and thread_id
Always set channel and thread_id
These fields are the only way the gateway can wake the right user back. Set them when the run starts so a later
LOST reconciliation can reach the origin thread.Update status at each transition
Update status at each transition
Call
upsert() as the run moves queued → running → waiting → succeeded/failed. The more current the status, the fewer false LOST reconciliations after a restart.Swap the store for heavier backends
Swap the store for heavier backends
SQLiteRunLedger is the default, but any object implementing RunLedgerProtocol (Postgres, Redis, a hosted queue) drops in unchanged — the gateway only depends on the protocol.What The User Sees
User asks for a long task
A user messages a Telegram bot: “Research the top 10 databases and write a comparison.” The agent starts, and the run is recorded as
RUNNING with channel="telegram" and the user’s thread_id.PraisonAI restarts mid-run
The process is killed or crashes before the run finishes. In-memory state is gone, but the ledger row on disk survives.
Related
Background Tasks
Run agent work in the background and collect results later.
Background Subagents
Spawn subagents that return a job ID immediately — the general-purpose ledger backs their durable state.

