Quick Start
1
Run an agent bot (writes to the outbox automatically)
A running gateway bot persists every outbound reply to
~/.praisonai/state/gateway_outbox.sqlite β no code change needed.2
Find stuck replies
3
Retry one after the channel recovers
4
Or drop a genuinely-dead one
User interaction flow
An operator moves a stuck reply from failed to delivered without restarting the gateway.Commands
Four subcommands mounted underpraisonai gateway outbox.
outbox stats gives an at-a-glance breakdown instead of a single counter:
--yes):
Where the outbox lives
outbox resolves the SQLite path in a fixed precedence order.
Entry keys
outbox list prints a full tracking key per entry in the form target:idempotency_key:id, e.g. slack:C0abc:msg-d4e5:18. outbox retry and outbox purge consume that key verbatim.
Keys are always printed un-truncated so operators can copy them directly. The key is matched in full against the stored row, so a stale key from another outbox β or a mistyped one β can never mutate the wrong message.
Read-only inspection (safe on a live gateway)
outbox list and outbox stats open the store with read_only=True.
Python API
The CLI mirrors fourOutboundQueue methods. Wire them from Python directly.
list(*, status=None, target=None, limit=100)
Returns β a list of
OutboundEntry (newest first). Never mutates state.
Status filter values: pending, sending, recovered, sent, failed, permanent_failure.
stats() -> dict[str, int]
Returns β a per-status count map; only statuses with at least one entry are present.
retry(key) -> bool (async)
Resets a failed/permanent_failure entry back to pending and clears its attempts so the next drain() re-dispatches it. Returns True if requeued, False if the key is unknown, already sent, in-flight (sending), or crash-recovered.
purge_entry(key) -> bool
Deletes a single entry by its full tracking key. Returns True if removed, False on unknown/mismatched key or an in-flight sending entry.
Best Practices
Copy full keys verbatim
Copy full keys verbatim
outbox list prints keys un-truncated on their own line. retry and purge match the full target:idempotency_key:id β a partial or edited key is rejected, never guessed.retry refuses a recovered entry β on purpose
retry refuses a recovered entry β on purpose
A crash-
recovered entry was in-flight when the process last crashed, so its delivery outcome is unknown. The next drain() already reconciles or re-sends with a βpossible duplicateβ annotator. Flipping it to pending manually strips that safeguard and risks an unlabelled duplicate.Never purge a sending entry
Never purge a sending entry
purge_entry refuses an in-flight sending row. The gateway may have already handed the payload to the channel API and be awaiting mark_sent/mark_failed. Deleting the row makes mark_sent a no-op and the caller report a spurious failure despite delivery. Wait until it transitions.Start from gateway status --deep
Start from gateway status --deep
praisonai gateway status --deep now prints an outbox hint pointing at outbox list, so the diagnostic surface leads straight to the operator commands.Related
Inbound DLQ
Inbound counterpart β same shape,
praisonai bot dlq list|replay|purgeDurable Outbound Delivery
How the outbox is populated, drained, and reconciled
Outbound Resilience
Retry-with-backoff and dead-lettering that feed the outbox
Gateway
Where the durable outbox is wired into the gateway server

