Skip to main content
Give a background agent a stable ID that survives a restart — if PraisonAI crashes mid-run, the ledger reconciles it to LOST so you can wake the user and re-route.
from praisonaiagents.runs import SQLiteRunLedger

ledger = SQLiteRunLedger()
No config, no new dependencies — SQLite lives at ~/.praisonai/runs/ledger.db.

Quick Start

1

Get the default ledger

from praisonaiagents.runs import SQLiteRunLedger

ledger = SQLiteRunLedger()
2

Track a run

from praisonaiagents.runs import SQLiteRunLedger, RunRecord, RunStatus

ledger = SQLiteRunLedger()
ledger.upsert(RunRecord(
    run_id="run-123",
    agent_id="researcher",
    channel="telegram",
    thread_id="8765",
    status=RunStatus.RUNNING,
))
3

Reconcile on startup

from praisonaiagents.runs import SQLiteRunLedger

ledger = SQLiteRunLedger()
for lost in ledger.recover_orphans():
    print(f"Run {lost.run_id} was interrupted — re-routing to {lost.channel}")

for r in ledger.list_active():
    print(r.run_id, r.status)
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 to LOST.

Run statuses

RunStatus partitions every run into active (recoverable) or terminal (done).
StatusCategoryMeaning
queuedactiveAccepted, not yet started
runningactiveExecuting
waitingactiveBlocked on an external signal (approval, tool, human input)
succeededterminalFinished cleanly
failedterminalErrored
cancelledterminalExplicitly cancelled
lostterminalOrphaned by a crashed process; set by recover_orphans()
unknownneitherWritten by a newer process; never finalised so its real state is never lost
Check the partition with the is_active / is_terminal helpers:
from praisonaiagents.runs import RunStatus

RunStatus.RUNNING.is_active      # True
RunStatus.RUNNING.is_terminal    # False
RunStatus.LOST.is_terminal       # True
RunStatus.UNKNOWN.is_active      # False — neither active nor terminal
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.
FieldTypeDefaultDescription
run_idstr(required)Stable ID, survives restarts
agent_idstr""Which agent ran it
channelstr""Origin channel (e.g. "telegram")
thread_idstr | NoneNoneOrigin thread, to reply back
statusRunStatusRunStatus.QUEUEDCurrent lifecycle status
progressstr | NoneNoneHuman-readable progress summary
terminal_outcomestr | NoneNoneFinal result or error note
created_atfloattime.time()Creation timestamp
updated_atfloattime.time()Last-update timestamp
metadatadict{}Free-form extra data
to_dict() / from_dict() roundtrip a record to a JSON/SQLite-friendly dict and back.
from praisonaiagents.runs import RunRecord, RunStatus

rec = RunRecord(run_id="r1", channel="#ops", status=RunStatus.RUNNING)
restored = RunRecord.from_dict(rec.to_dict())
assert restored.status == RunStatus.RUNNING

RunLedgerProtocol

The pluggable store contract — swap in a heavier backend by implementing these four methods.
MethodReturnsDescription
upsert(record)NoneInsert or update, keyed by run_id
get(run_id)RunRecord | NoneFetch 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 stdlib sqlite3.
OptionTypeDefaultDescription
db_pathstr | None~/.praisonai/runs/ledger.dbDatabase file; use ":memory:" for tests
  • No new dependencies — stdlib sqlite3 only.
  • 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

from praisonaiagents.runs import SQLiteRunLedger, RunRecord, RunStatus

ledger = SQLiteRunLedger()
ledger.upsert(RunRecord(
    run_id="run-123",
    status=RunStatus.SUCCEEDED,
    terminal_outcome="report delivered",
))

Wake users after a crash

from praisonaiagents.runs import SQLiteRunLedger

ledger = SQLiteRunLedger()

def on_boot(send):
    for lost in ledger.recover_orphans():
        if lost.thread_id:
            send(lost.channel, lost.thread_id,
                 "Your last run was interrupted — retry?")

List recent runs regardless of status

from praisonaiagents.runs import SQLiteRunLedger

ledger = SQLiteRunLedger()
for r in ledger.list_all(limit=50):
    print(r.run_id, r.status, r.updated_at)

Best Practices

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.
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.
Call upsert() as the run moves queued → running → waiting → succeeded/failed. The more current the status, the fewer false LOST reconciliations after a restart.
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

1

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.
2

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.
3

The gateway wakes the user back

On boot, recover_orphans() marks the run LOST and returns it with the original channel and thread_id. The gateway posts back to the same Telegram thread: “Your last run was interrupted — retry?”

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.