journal • AI Agent SDK
Durable run-state journal for resumable agent execution.
journal
AI Agent
Durable run-state journal for resumable agent execution.
The existing stores persist three unlinked things:
- workspace files — `
checkpoints/(shadow-git),snapshot/`; - finished conversation messages — `
session/store.py`;
but nothing persists the execution cursor of a run — the loop iteration index, the pending/in-flight tool calls, or the partial assistant turn. So if the process dies mid tool-loop, the run cannot resume where it left off and any in-flight tool work is lost (or, worse, re-run on a naive retry).
This module adds the missing piece: a tiny append-only journal, keyed by a `run_id`, that records an event at every meaningful boundary (model decision, tool call, tool result, iteration index, approval decision). On resume, the loop is re-driven from the top and journalled steps return their recorded results instantly — no re-execution of side-effecting tools and no re-billing of LLM calls — while real work restarts at the first un-journalled step.
Design notes
————
- Reuses the zero-dependency stdlib `
sqlite3` persistence pattern already used - by :mod:
praisonaiagents.runs.sqlite_ledgerand :mod:praisonaiagents.session - (WAL, `
busy_timeout`, a single re-entrant-lock-guarded shared connection). - Default-off / zero-overhead: nothing writes to the journal unless a run
- opts in (e.g. `
Agent(..., durable=True)`). This module is lazy-imported from - :mod:
praisonaiagents.runtime, so importing the package stays cheap. - Complements — does not replace — :mod:
praisonaiagents.runs, which tracks run - status (queued/running/…); this tracks the per-event cursor.
Usage::
from praisonaiagents.runtime import RunJournal, JournalEvent
j = RunJournal(":memory:")
j.open_run("r1", agent="coder", task="migrate module")
# ── record each boundary as it happens ──
seq = 0
j.append(JournalEvent("r1", seq, "model_decision", {"text": "call tool X"}))
# ── on resume, memoise recorded steps ──
replay = j.replay_index("r1") # {(seq, kind): payload}
if (rec := replay.get((seq, "model_decision"))) is not None:
resp = rec # cached — do NOT re-call the model
j.close_run("r1", "succeeded")Import
from praisonaiagents.runtime import journalClasses
A single append-only journal event.
RunMetaDurable metadata that binds the three stores under one `run_id`.
Append-only, restart-safe run-state journal backed by SQLite.
Constants
| Name | Value |
|---|---|
KIND_MODEL_DECISION | 'model_decision' |
KIND_TOOL_CALL | 'tool_call' |
KIND_TOOL_RESULT | 'tool_result' |
KIND_APPROVAL | 'approval' |
KIND_ITERATION | 'iteration' |
KIND_VERIFICATION | 'verification' |
VALID_KINDS | frozenset({KIND_MODEL_DECISION, KIND_TOOL_CALL, KIND_TOOL_RESULT, KIND_APPROVAL, KIND_ITERATION, KIND_VERIFICATION}) |
_STATUS_RUNNING | 'running' |
_TERMINAL_STATUSES | frozenset({'done', 'succeeded', 'failed', 'cancelled'}) |
_SCHEMA | "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n agent TEXT NOT NULL DEFAULT '',\n task TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'running',\n outcome TE... |