> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Run[▶ recipe run] --> Exec[🧠 Execute]
    Exec --> Result[📦 RecipeResult]
    Result --> Persist{PRAISONAI_RECIPE_HISTORY?}
    Persist -->|on default| Store[💾 ~/.praison/runs]
    Persist -->|off| Skip[⏭ Skip]
    Store --> Runs[📋 recipe runs]
    Runs --> Judge[⚖ recipe judge run-id]

    classDef in fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef proc fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef store fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef out fill:#10B981,stroke:#7C90A0,color:#fff

    class Run,Persist in
    class Exec,Result proc
    class Store,Skip store
    class Runs,Judge out
```

## Quick Start

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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-abc123
```

## Commands

### runs

List recipe run history.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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:**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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 --json
```

<Tip>
  Copy a **Run ID** from the table and pass it to `praisonai recipe judge <run-id>` to evaluate that execution.
</Tip>

If the history module can't be imported, the command prints `Run history unavailable: <error>` and exits with code `1`.

### runs stats

Get storage statistics.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai recipe runs stats [--json]
```

**Output:**

```
Run History Stats:
  Total runs: 42
  Storage size: 1.5 MB
  Path: ~/.praison/runs
```

### runs cleanup

Clean up old runs based on retention policy.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai recipe runs cleanup [--json]
```

### export

Export a run for replay or debugging.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai recipe export <run_id> [options]
```

**Options:**

| Option | Description |
| - | - |
| `-o, --output <path>` | Output file path |
| `--json` | Output JSON format |

**Examples:**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Export to default filename
praisonai recipe export run-abc123

# Export to specific file
praisonai recipe export run-abc123 -o my-export.json
```

### replay

Replay a run from an export bundle.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai recipe replay <bundle> [options]
```

**Options:**

| Option | Description |
| - | - |
| `--compare` | Compare output with original |
| `--json` | Output JSON format |

**Examples:**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Simple replay
praisonai recipe replay export.json

# Replay with drift detection
praisonai recipe replay export.json --compare
```

## Export Format

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "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:**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export PRAISONAI_RECIPE_HISTORY=false
praisonai recipe run my-recipe   # not recorded
```

**Re-enable (default):**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
unset PRAISONAI_RECIPE_HISTORY
# or
export PRAISONAI_RECIPE_HISTORY=true
```

## Storage 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 stream
```

## Data Policy

Runs respect the recipe's data policy:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# In TEMPLATE.yaml
data_policy:
  retention_days: 30
  export_allowed: true
  pii:
    mode: redact
```

## Python API

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 `fcntl` advisory lock on a sidecar lock file (cross-process serialisation). On Windows this degrades to a no-op; the `RLock` still serialises threads in the same interpreter.
* An atomic `_save_index` — writes go to a temp file in the same directory and `os.replace` over `index.json`, so a `SIGKILL` mid-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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 h2
```

## Next Steps

* [Recipe Registry](/docs/cli/recipe-registry) - Publish and pull recipes
* [Security Features](/docs/cli/recipe-security) - SBOM, signing, auditing
* [Policy Packs](/docs/cli/recipe-policy) - Manage tool permissions


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.