Run History
Store, query, and export recipe run history
Run History CLI
Every recipe run is recorded automatically. Use recipe runs to see what happened, then feed a Run ID into recipe judge.
Quick Start
# List recent runs (last 20)
praisonai recipe runs
# Filter by recipe and status
praisonai recipe runs ai-url-blog-generator --status failed
# Judge a run using its Run ID
praisonai recipe judge run-abc123Commands
runs
List recipe run history.
praisonai recipe runs [NAME] [OPTIONS]Options:
| Option | Description |
|---|---|
NAME (positional, optional) | Recipe name to filter runs |
--status <status> | Filter by status (e.g. success, failed, policy_denied, timeout, missing_deps, dry_run) |
--session, -s <id> | Filter by session ID |
--limit <n> | Maximum results (default: 20) |
--json | Output JSON format ({"runs": [...], "count": N}) |
The table shows Recipe · Version · Status · When · Session · Run ID, with color-coded status (success green, failed red, policy_denied/timeout yellow).
Examples:
# List all recent runs
praisonai recipe runs
# Filter by recipe
praisonai recipe runs ai-url-blog-generator
# Filter by recipe and status
praisonai recipe runs ai-url-blog-generator --status failed --limit 10
# Filter by session
praisonai recipe runs --session session-abc123
# JSON output
praisonai recipe runs --jsonIf the history module can’t be imported, the command prints Run history unavailable: <error> and exits with code 1.
runs stats
Get storage statistics.
praisonai recipe runs stats [--json]Output:
Run History Stats:
Total runs: 42
Storage size: 1.5 MB
Path: ~/.praison/runsruns cleanup
Clean up old runs based on retention policy.
praisonai recipe runs cleanup [--json]export
Export a run for replay or debugging.
praisonai recipe export <run_id> [options]Options:
| Option | Description |
|---|---|
-o, --output <path> | Output file path |
--json | Output JSON format |
Examples:
# Export to default filename
praisonai recipe export run-abc123
# Export to specific file
praisonai recipe export run-abc123 -o my-export.jsonreplay
Replay a run from an export bundle.
praisonai recipe replay <bundle> [options]Options:
| Option | Description |
|---|---|
--compare | Compare output with original |
--json | Output JSON format |
Examples:
# Simple replay
praisonai recipe replay export.json
# Replay with drift detection
praisonai recipe replay export.json --compareExport Format
{
"format": "praison-run-export",
"version": "1.0",
"exported_at": "2024-12-29T12:00:00Z",
"run": {
"run_id": "run-abc123",
"recipe": "support-reply",
"version": "1.0.0",
"status": "success",
"input": {"ticket_id": "T-123"},
"output": {"reply": "..."},
"metrics": {"duration_sec": 2.5},
"trace": {"session_id": "session-001"}
}
}Automatic History Persistence
Every praisonai recipe run (and every run_stream() execution) is recorded to history automatically. You don’t need --save for a run to appear in recipe runs.
| Behavior | Detail |
|---|---|
| Default | Enabled |
| Env var | PRAISONAI_RECIPE_HISTORY |
| Disable | Set to 0, false, no, or off |
| Failure mode | History write errors log a warning; the recipe run still succeeds |
| Terminal statuses recorded | success, failed, missing_deps, policy_denied, dry_run, timeout |
History is independent of the recipe registry: runs for a deleted or unknown recipe are still listed.
Disable persistence:
export PRAISONAI_RECIPE_HISTORY=false
praisonai recipe run my-recipe # not recordedRe-enable (default):
unset PRAISONAI_RECIPE_HISTORY
# or
export PRAISONAI_RECIPE_HISTORY=trueStorage Location
Run history is stored at ~/.praison/runs/.
~/.praison/runs/
├── index.json
└── run-abc123/
├── run.json # Metadata
├── input.json # Input data
├── output.json # Output data
└── events.jsonl # Event streamData Policy
Runs respect the recipe’s data policy:
# In TEMPLATE.yaml
data_policy:
retention_days: 30
export_allowed: true
pii:
mode: redactPython API
from praisonai.recipe.history import RunHistory, get_history
# Get default history
history = get_history()
# Store a run
from praisonai.recipe.models import RecipeResult, RecipeStatus
result = RecipeResult(
run_id="run-abc123",
recipe="my-recipe",
version="1.0.0",
status=RecipeStatus.SUCCESS,
output={"result": "hello"},
)
history.store(result, input_data={"query": "test"})
# List runs
runs = history.list_runs(recipe="my-recipe", limit=10)
# Get specific run
run_data = history.get("run-abc123")
# Export
export_path = history.export("run-abc123")
# Stats
stats = history.get_stats()
print(f"Total runs: {stats['total_runs']}")
# Cleanup
deleted = history.cleanup(retention_days=30)Thread- and process-safety
RunHistory is safe under concurrent use. Every read-modify-write of index.json in store(), delete(), and cleanup() is wrapped in:
- A per-instance
threading.RLock(in-process serialisation). - A POSIX
fcntladvisory lock on a sidecar lock file (cross-process serialisation). On Windows this degrades to a no-op; theRLockstill serialises threads in the same interpreter. - An atomic
_save_index— writes go to a temp file in the same directory andos.replaceoverindex.json, so aSIGKILLmid-write cannot leave a truncated index.
This matches the concurrency posture of recipe.arun_background() (default max_concurrent=5) and of multiple praisonai run invocations sharing ~/.praison/runs/.
get_history(path) returns the same RunHistory instance for the same resolved path across repeated calls, so multi-threaded stores that pass a path share the in-process lock — important on Windows where the advisory lock is a no-op. The default no-path singleton is unchanged.
from praisonai.recipe.history import get_history
# Same instance, same in-process lock — safe under threads on any platform.
h1 = get_history("/opt/agents/runs")
h2 = get_history("/opt/agents/runs")
assert h1 is h2Next Steps
- Recipe Registry – Publish and pull recipes
- Security Features – SBOM, signing, auditing
- Policy Packs – Manage tool permissions