Different scope from the Run-State Journal. The inbound journal deduplicates and recovers webhook messages. The run-state journal persists the execution cursor of a single run. A webhook may spawn a run; the two journals track the two halves of the pipeline.
On by default for gateway/bot runs — Every bot started via
praisonai bot start, onboard, or bot.yaml gets the journal automatically. No code needed. The journal lives at ~/.praisonai/state/<platform>/ingress.sqlite. Set delivery.durable: false to opt out.Automatic crash-replay
Every gateway-managed channel callsreplay() for you at start-up and hot-reload — no startup hook needed.
Every channel started via praisonai bot start, onboard, bot.yaml, or on hot-reload of a single channel automatically calls InboundJournal.replay() for its journal. What replay() does:
- Resets stale
claimedrows back topendingso they can be reprocessed via redelivery or session-resume (_resume_interrupted_turns(), PraisonAI #3379). - Quarantines genuine poison entries to the inbound DLQ under the shared age-gated policy (see Inbound DLQ).
- Does not synthesise per-adapter native message objects to self-dispatch — that duplication is deliberately left to the session-resume path.
Best-effort: any failure is logged and skipped, so channel start never aborts. Reset counts are logged at
INFO so operators can see the recovery happened.Default behavior (no config needed)
Every bot started throughbuild_session_manager (all shipped adapters) automatically gets an Inbound Journal at:
~/.praisonai/state/telegram/ingress.sqlite. A Discord bot uses ~/.praisonai/state/discord/ingress.sqlite. Each platform is fully isolated — no cross-platform dedup collisions.
Set PRAISONAI_HOME to override the base directory:
Opt out or override path
Add adelivery: block to any channel in your bot.yaml or gateway.yaml:
Quick Start (advanced: manual setup)
Most users get the journal automatically. For custom setups outsidebuild_session_manager:
1
Create journal that survives restarts
2
Enable on your bot
You don’t need to call
complete() yourself when using BotSessionManager.chat() — successful chats mark the journal entry as completed automatically. You only need to call complete() manually if you’re driving InboundJournal directly without BotSessionManager.How It Works
Before PR #1980, the default
chat() path claimed entries but never called complete(). On startup, journal.replay() could re-issue messages the user had already received answers to. Upgrade to a release after 2026-06-19 — replay then only covers genuinely incomplete entries.Automatic crash recovery under the gateway
The gateway callsInboundJournal.replay() for you — at channel start-up and on every hot-reload — so a gateway killed mid-turn recovers stranded messages without you invoking replay() yourself.
replay() resets stale claimed rows back to pending so a redelivery — or the gateway’s session-level resume (Issue #3379) — can reprocess them exactly once, and quarantines genuine poison entries to the inbound DLQ. Reset counts are logged at INFO so operators can grep for recovery:
Gateway Restart Continuation
How in-flight turns resume after a gateway restart
Gateway Session Continuity
Session-level exactly-once resume path (#3379)
When to use which option
Configuration Options
Age-gated quarantine
replay() and the redelivery path no longer quarantine on attempts alone. An exhausted entry is only moved to the DLQ once it is both attempt-exhausted and at least dead_letter_min_age (default 6h) old — unless the failure is a known-permanent error class, which short-circuits immediately. A routine LLM outage that burns the claim budget in seconds keeps being retried instead of quarantining every in-flight message. See Durable Outbound Delivery → Age-gated dead-lettering for the shared policy and decision diagram.
Introduced in PraisonAI PR #3521.
Per-platform examples
- Telegram
- Discord
- Slack
- WhatsApp
- Email
- AgentMail
Common Patterns
Pattern 1: Dedup-only (webhook redelivery protection)
Pattern 2: Manual replay (only when driving InboundJournal outside the gateway)
Gateway-managed bots get startup and hot-reload replay for free (see Automatic crash-replay). This pattern is only for custom setups that drive
InboundJournal directly.Pattern 3: Combining with InboundDLQ for full durability stack
Best Practices
Use the same path across restarts
Use the same path across restarts
The journal’s SQLite file must survive restarts for crash recovery to work. Use an absolute path or a location that persists across deployments.
Tune claim_timeout to match your agent latency
Tune claim_timeout to match your agent latency
Set
claim_timeout to be longer than your p99 agent.chat() latency to avoid false stale entry detection.Under the gateway: replay() is automatic
Under the gateway: replay() is automatic
When running under the gateway (
praisonai bot start, onboard, bot.yaml, bot.WebSocketGateway), replay() is called automatically at channel start-up and hot-reload — you do not need to call it yourself. Reset counts are logged. See Automatic crash recovery under the gateway.Driving InboundJournal directly: call replay() in your startup hook
Driving InboundJournal directly: call replay() in your startup hook
When you drive
InboundJournal yourself (bare BotSessionManager, custom adapter loops), replay stale entries when your bot starts to recover from crashes.Keep account stable per bot instance
Keep account stable per bot instance
The
account parameter is part of the deduplication key. Keep it consistent for each bot instance.Set claim_timeout below the platform's redelivery window
Set claim_timeout below the platform's redelivery window
Platforms redeliver webhooks at fixed intervals (Telegram retries within seconds, Slack waits ~3s before its first retry, etc.). Set
claim_timeout shorter than your platform’s redelivery window so that a crashed worker’s claim becomes stale before the next redelivery arrives — that’s what enables automatic recovery without a restart.Related
Delivery Config
Full reference for the
delivery: channel config block — defaults, opt-out, and path overrideInbound DLQ
Failure-side durability when agent execution fails
Durable Outbound Delivery
Outbound counterpart — persist outgoing messages with retry and idempotency
Bot Routing
Multi-channel session routing for complex bot setups
Visible-Outcome Guarantee
The delivery-side half of “no inbound message ends without a recorded outcome”
Automatic crash-replay introduced in PraisonAI PR #3622 (fixes #3621).

