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

# Gateway Durable Runs

> Gateway agents journal every turn so an interrupted turn can be resumed by run id — recorded tool results replay instead of re-firing side effects

Gateway agents journal every turn by default, so an interrupted turn can be resumed by run id without re-firing side-effecting tools or re-billing LLM calls. The gateway does **not** auto-resume on restart — you resume a turn yourself with `ExecutionConfig(durable=True, resume_run_id=..., journal_path=...)`, the same journal, and the recorded task (see [Resuming an interrupted turn](#resuming-an-interrupted-turn)). No flag needed for journalling — it auto-enables whenever your session store is durable (the shipped default). Two explicit opt-outs are supported for the zero-overhead path.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Gateway Durable Runs"
        Conn[📡 WebSocket turn] --> Agent[🤖 Gateway agent]
        Agent --> Journal[(📒 RunJournal)]
        Journal --> Crash[💥 Restart]
        Crash --> Resume[🔁 Resume by run id]
        Resume --> Done[✅ Same result, no re-billing]
    end
    classDef start fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef store fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef warn fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    class Conn start
    class Agent,Resume process
    class Journal store
    class Crash warn
    class Done success
```

## Quick Start

> [!NOTE]
>
> **Migration (PR #4218).** Prior releases required `gateway.durable_runs: true` to opt in. The default is now on whenever your session store is durable — no config change needed to gain crash-safe journalling ([resume](#resuming-an-interrupted-turn) stays a manual step). Want the old zero-overhead behaviour? Set `gateway.durable_runs: false` or `gateway.reliability: "off"`.

**1. Zero-config default (every turn is journalled)**

Sessions persist by default, so durable runs are already on — no `gateway:` block required:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
agents:
  order_processor:
    instructions: "Charge the card and ship the order."
    tools: [charge_card, ship_order, notify_customer]
```

Restart the gateway mid-run; the interrupted turn is left `running` in the [`RunJournal`](https://praison.ai/docs/features/run-state-journal). To finish it without re-charging the card or re-billing the model, resume it by run id — see [Resuming an interrupted turn](#resuming-an-interrupted-turn).

**2. Opt out (zero-overhead path)**

Two explicit escape hatches turn durable runs off:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml — either one opts out
gateway:
  durable_runs: false     # explicit off
  # reliability: "off"    # immediate-teardown posture also forces off
```

In-memory sessions disable it too — with `session.persist: false` there is no durable store to journal against, so durable runs silently stay off.

**3. Override per agent**

A per-agent `durable` flag wins in both directions — opt one agent out even when the gateway default is on, or opt one in even when it's off:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
gateway:
  durable_runs: false      # gateway-wide off

agents:
  order_processor:
    instructions: "Charge the card and ship the order."
    durable: true          # this one agent is durable
  chit_chat:
    instructions: "Small talk only."
    # inherits the gateway default (non-durable)
```

**4. Toggle the default off from the environment**

`gateway.durable_runs` accepts an `${ENV_VAR}`-substituted string. Because `${DURABLE}` renders as a string, the value is coerced explicitly — `"false"`/`"0"`/`"no"`/`"off"`/`""` disable, and `"true"`/`"1"`/`"yes"`/`"on"` enable:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# ${DURABLE} → "false" from the environment
gateway:
  durable_runs: ${DURABLE}   # "false"/"0"/"no"/"off"/"" disable; "true"/"1"/"yes"/"on" enable
```

> [!WARNING]
>
> `"false"` really means false here. Without this coercion, `bool("false")` would be truthy and silently enable durability.

***

## How It Works

Each gateway agent journals its turn to the core `RunJournal` whenever durable runs are on — decided from config plus the effective session store. Resuming is a separate, manual step: a turn resumed by run id replays journalled steps and runs only the un-journalled ones.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Client
    participant Gateway
    participant Agent
    participant Journal

    Client->>Gateway: WebSocket turn
    Gateway->>Agent: run (durable=True)
    Agent->>Journal: record tool result (auto)
    Note over Gateway,Journal: 💥 gateway restarts mid-turn
    Note over Journal: turn left "running"
    Agent->>Journal: operator resumes with resume_run_id
    Journal-->>Agent: replay recorded result
    Note over Agent: same result, no re-billing
```

| Role | Responsibility |
| - | - |
| Gateway | Consults `gateway.durable_runs` when explicitly set; otherwise auto-enables when the effective session store is durable (`self._session_store is not None`) |
| Agent | Journals every model and tool boundary to the core journal |
| RunJournal | Records results so a turn resumed by run id replays instead of re-executing |
| Per-agent `durable` | Overrides the gateway default for a single agent |

### Choosing whether durable runs run

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start[⚙️ Gateway loads config] --> Q1{gateway.durable_runs<br/>set explicitly?}
    Q1 -->|Yes| Explicit[✅ Use the operator's value]
    Q1 -->|No| Q2{gateway.reliability<br/>== "off"?}
    Q2 -->|Yes| Off[⏸️ Off zero-overhead path]
    Q2 -->|No| Q3{Durable session store<br/>actually present?}
    Q3 -->|Yes default| On[🔁 Auto-on crash-safe journalling]
    Q3 -->|No| Off

    classDef start fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef on fill:#10B981,stroke:#7C90A0,color:#fff
    classDef off fill:#8B0000,stroke:#7C90A0,color:#fff
    class Start start
    class Q1,Q2,Q3 decision
    class Explicit,On on
    class Off off
```

**Precedence ladder:** per-agent `durable` > explicit `gateway.durable_runs` > `gateway.reliability: "off"` (forces off) > effective session store (auto-on when durable, off when in-memory).

> [!NOTE]
>
> The durable-runs decision is re-evaluated on **every** load path — initial load, full-restart reload, and selective reload — so restarting the gateway keeps durability wired.

> [!NOTE]
>
> **Zero overhead when off.** On an opted-out path the execution hot path is unchanged and `ExecutionConfig` is never imported.

> [!NOTE]
>
> **Graceful degrade.** If `ExecutionConfig` can't be imported (an older core), the gateway logs a warning and the agent runs non-durably — the gateway never fails to start over durability.

***

## Resuming an interrupted turn

Nothing resumes interrupted turns automatically. List them from the journal and resume each with its recorded task:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, ExecutionConfig
from praisonaiagents.runtime import RunJournal

journal = RunJournal()  # default ~/.praisonai/runs/journal.db — pass the gateway's journal_path if set
for run_id in journal.interrupted_runs():
    meta = journal.run_meta(run_id)
    agent = Agent(
        name=meta.agent,
        instructions="Charge the card and ship the order.",
        execution=ExecutionConfig(durable=True, resume_run_id=run_id),
    )
    agent.chat(meta.task)  # must be the recorded task
```

> [!WARNING]
>
> Autonomous and goal loops (`run_autonomous`, `run_goal`) cannot be resumed by run id — only a single `chat()` turn can. A crash inside those loops loses the loop cursor (iteration, stage, spend baseline). Treat gateway durability as per-turn resume, not per-loop resume.

***

## How to opt out

There are three ways to end up with `durable_runs=false`, plus one silent fallback:

1. **Explicit off** — set `gateway.durable_runs: false`.
2. **Reliability off** — set `gateway.reliability: "off"` (the immediate-teardown posture forces durable runs off for the zero-overhead path).
3. **No durable store** — set `session.persist: false`; with no store to journal against, durable runs auto-default to off.
4. **Silent fallback** — `session.persist: true` but the persistent store failed to initialise (e.g. absent or read-only `~`). The gateway logs the fallback and stays non-durable — worth checking, because operators otherwise assume their intent is honoured.

***

## Configuration Options

| Config path | Type | Default | Effect |
| - | - | - | - |
| `gateway.durable_runs` | `bool` (accepts env-substituted string forms) | `true` when a durable session store is present (the shipped default); `false` otherwise or when `reliability: "off"` | When truthy, every gateway agent is constructed with `execution=ExecutionConfig(durable=True)` so each turn is journalled to the core `RunJournal`. |
| `gateway.reliability` | `str` | unset | `"off"` forces `durable_runs` off (immediate-teardown, zero-overhead posture). See [Gateway Reliability](https://praison.ai/docs/features/gateway-reliability.md). |
| `session.persist` | `bool` | `true` | `false` removes the durable store, so the auto-default disables durable runs. See [Gateway Session Persistence](https://praison.ai/docs/features/gateway-session-persistence.md). |
| `agents.<id>.durable` | `bool` (accepts string forms) | inherits `gateway.durable_runs` | Per-agent override — opts a single agent in (or out) regardless of the gateway-wide default. |

The underlying agent primitive is [`ExecutionConfig(durable=True)`](https://praison.ai/docs/features/durable-tool-runs) — see that page for `journal_path`, `resume_run_id`, and the resume contract.

***

## Common Patterns

**Fleet-wide durability with a chit-chat escape hatch** — durable runs are already on by default, so opt out only the agents that make small talk:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# no gateway.durable_runs needed — on by default

agents:
  order_processor:
    instructions: "Charge the card and ship the order."
    tools: [charge_card, ship_order]
  chit_chat:
    instructions: "Small talk only."
    durable: false           # no journalling for stateless banter
```

**Toggle by environment** — durable in production, off in ephemeral CI:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
gateway:
  durable_runs: ${DURABLE}   # export DURABLE=true in prod, DURABLE=false in CI
```

***

## Best Practices

> [accordion] Point journal\_path at a persistent volume in containers
>
> The default `~/.praisonai/runs/journal.db` is lost when a container is recreated. Configure a mounted volume so the journal survives restarts — see [Durable Tool Runs](https://praison.ai/docs/features/durable-tool-runs.md#configuration-options).

> [accordion] Verify the store actually initialised
>
> Durable runs only auto-enable when the session store instantiates. On an absent or read-only home directory the store degrades to in-memory and durable runs silently stay off — check the gateway log for the fallback if you expected crash-safe journalling.

> [accordion] Per-agent durable is your escape hatch
>
> Small-talk agents don't need journalling. Set `durable: false` on them, keeping the hot path clean for stateless turns even while the gateway default is on.

> [accordion] Compose with session persistence
>
> Durable gateway runs and [session persistence](https://praison.ai/docs/features/gateway-session-persistence.md) are different layers — one journals the in-flight turn so it can be resumed, the other restores conversation history. Both survive a restart and work together.

***

## Related

> [card] [Durable Tool Runs](https://praison.ai/docs/features/durable-tool-runs.md)
>
> The underlying agent primitive — `ExecutionConfig(durable=True)`.

> [card] [Run-State Journal](https://praison.ai/docs/features/run-state-journal.md)
>
> The SQLite journal durable runs write to.

> [card] [Gateway Session Persistence](https://praison.ai/docs/features/gateway-session-persistence.md)
>
> The durable store durable runs auto-enable against.

> [card] [Gateway Reliability](https://praison.ai/docs/features/gateway-reliability.md)
>
> `reliability: "off"` forces durable runs off.


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