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

Diagram

Gateway Durable Runs

πŸ“‘ WebSocket turn

πŸ€– Gateway agent

πŸ“’ RunJournal

πŸ’₯ Restart

πŸ” Resume by run id

βœ… Same result, no re-billing

Quick Start

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

    Sessions persist by default, so durable runs are already on β€” no gateway: block required:

    yaml
    # 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. To finish it without re-charging the card or re-billing the model, resume it by run id β€” see Resuming an interrupted turn.

  2. Opt out (zero-overhead path)

    Two explicit escape hatches turn durable runs off:

    yaml
    # 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
    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
    # ${DURABLE} β†’ "false" from the environment
    gateway:
      durable_runs: ${DURABLE}   # "false"/"0"/"no"/"off"/"" disable; "true"/"1"/"yes"/"on" enable

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.

Diagram
JournalAgentGatewayClientJournalAgentGatewayClientπŸ’₯ gateway restarts mid-turnturn left “running”same result, no re-billingWebSocket turnrun (durable=True)record tool result (auto)operator resumes with resume_run_idreplay recorded result
RoleResponsibility
GatewayConsults gateway.durable_runs when explicitly set; otherwise auto-enables when the effective session store is durable (self._session_store is not None)
AgentJournals every model and tool boundary to the core journal
RunJournalRecords results so a turn resumed by run id replays instead of re-executing
Per-agent durableOverrides the gateway default for a single agent

Choosing whether durable runs run

Diagram

Yes

No

Yes

No

Yes default

No

βš™οΈ Gateway loads config

gateway.durable_runs
set explicitly?

βœ… Use the operator’s value

gateway.reliability
== ‘off’?

⏸️ Off zero-overhead path

Durable session store
actually present?

πŸ” Auto-on crash-safe journalling

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


Resuming an interrupted turn

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

python
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

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 pathTypeDefaultEffect
gateway.durable_runsbool (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.reliabilitystrunset"off" forces durable_runs off (immediate-teardown, zero-overhead posture). See Gateway Reliability.
session.persistbooltruefalse removes the durable store, so the auto-default disables durable runs. See Gateway Session Persistence.
agents.<id>.durablebool (accepts string forms)inherits gateway.durable_runsPer-agent override β€” opts a single agent in (or out) regardless of the gateway-wide default.

The underlying agent primitive is ExecutionConfig(durable=True) β€” 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
# 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
gateway:
  durable_runs: ${DURABLE}   # export DURABLE=true in prod, DURABLE=false in CI

Best Practices

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.

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.

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.

Compose with session persistence

Durable gateway runs and session persistence 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