Skip to main content
On boot, the gateway resumes turns that were in-flight when the previous process stopped — and proactively tells channel users what happened, so a Telegram or Slack user never sits in silence after a restart.
Channel-originated sessions (Slack/Discord/Telegram) get this server-initiated notice. Direct-client sessions keep resuming via the client’s own reconnect + since cursor — see Session Continuity.

Quick Start

1

Start with an agent

2

Enable session persistence (the only opt-in)

3

What a channel user sees

After a restart interrupts their request, the originating channel receives:
4

What operators see

The boot scan logs a one-line summary:

How It Works

On boot — before serving new traffic — the gateway scans persisted sessions, notifies the originating channel of any interrupted turn, and re-drives it when the agent is still registered. The whole scan is wrapped in a try / except at the start() site — a failure in the resume loop never blocks the gateway from serving new traffic.

What Triggers a Resume

A persisted session is resumed when all of these hold:
  • is_executing == True or pending_inbox is non-empty (a turn was in flight).
  • channel_target is set on the persisted snapshot — channel-originated only; direct-client sessions resume via reconnect.
  • A durable session store is configured (the scan is a no-op without one).

The Notice Contract

The originating channel receives one verbatim message per interruption:
Exactly-once. The notice is delivered through the durable delivery router with an idempotency key restart:<session_id>:<run_epoch>, where run_epoch is the event_cursor at persist time. The router’s idempotency store survives restart, so a crash loop (boot → notice → crash → boot) notifies the originating channel at most once per interruption. This is why the router path is used instead of a bare bot.send_message().
Fallback when the delivery router is unavailable. If WebSocketGateway.delivery_router is None, the notice falls back to bot.send_message(target, text) via get_channel_bot(channel) — best-effort, no idempotency. If you disable the delivery router, you also opt out of exactly-once: an ill-configured deployment can double-notify on a crash loop.

channel_target on Persisted Sessions

channel_target is a "channel:target" string (for example "telegram:12345") recording where to notify a channel user after a restart.
  • Set by the channel adapter at inbound time via session.set_channel_target("telegram:12345").
  • Round-trips through to_dict/from_dict; backward-compatible — None for sessions created before the field existed and for direct-client sessions.
  • Fallback: when the explicit target is absent, it derives from client_id iff client_id itself encodes a channel:target origin (contains a :). This makes the field a live consumer of existing state — it matters for operators upgrading with in-flight sessions the setter never touched.
The persisted snapshot gains one field (default None):

Best-Effort Re-drive

Whether the turn actually resumes depends on the agent still being registered on the fresh process.
  • Agent still registered (same agent_id in the config) → the session is rehydrated and _run_session_queue restarts, so a real reply follows the notice.
  • Agent no longer registered (removed from the config) → the notice above still fires, and its wording already asks the user to resend. No exception, no crash-loop.
Treat the re-drive as a nice-to-have, not the contract. The notice already asks users to resend if no reply arrives.

Configuration Options

No new user-facing config flags. The feature is enabled transitively by the presence of: No operator action is required to opt in beyond enabling session persistence — the feature is on by default when persistence is on.

Operator Observability

Search a log stream for these lines to find this behaviour: The boot scan calls list_sessions() with an explicit large limit, so interrupted turns beyond the store’s default 50-session pagination window are not silently skipped.

Common Patterns

Two deployment scenarios where restart continuation changes behaviour. Rolling restarts. Restart continuation makes rolling deploys safer for channel users, provided session persistence is on and the delivery router is shared across instances. Deliberate maintenance drop. To swallow all in-flight work on a restart (for example, a schema migration invalidates the persisted format), turn off gateway.session.persist for that boot — the scan is a no-op without a store.

Best Practices

The feature is a no-op without a durable store. Set gateway.session.persist: true.
The fallback path is best-effort, not exactly-once. A crash loop can double-notify when the router is disabled.
A non-zero Resumed N count on every boot signals that something upstream is crashing regularly.
If the agent was removed from the config, the turn can’t re-drive. The notice already asks users to resend — treat re-drive as a nice-to-have.
The scan uses an explicit large limit so the default 50-session window doesn’t hide interrupted sessions, but very large stores still benefit from ordering by recency.

Session Continuity

Client-initiated reconnect resume — the sibling capability

Session Persistence

The persistence layer this feature rides on

Undelivered Notice

Peer user-facing message when something went wrong

Approval Durability

The durable rehydrate-on-boot pattern this feature mirrors