The dual-write problem: an agent updates its state and publishes an event — two writes to two systems, no shared transaction, and a crash between them leaves a run that "finished" with no record anywhere. The transactional outbox is the boring, bulletproof answer.

The pattern is small enough to state fully. In the same database transaction that commits the agent's state change, insert the outgoing event into an outbox table — a plain local row, no external call. A separate relay process (or change-data-capture, via Debezium and friends) reads the outbox and publishes to the broker, marking rows sent. Atomicity is guaranteed because both writes share one transaction; delivery becomes at-least-once because the relay retries until the broker acknowledges. The price is the fine print: consumers must deduplicate, because the relay cannot always know whether a previous attempt landed. Idempotent consumers are not an optimization here — they are the load-bearing wall.

Agent platforms hit dual-write everywhere the data plane produces consequences: run state says "deployed," but the audit stream, the Slack notification, the billing meter, and the knowledge-graph updater are separate systems. Without an outbox, each integration is a private chance to lose a fact — and agent runs generate dozens of facts per minute. With one, every integration reads the same reliable stream, and new consumers attach without touching the agent.

Deployment Checklist

  • Order the outbox: include a per-aggregate sequence number so consumers can restore ordering that database timestamps alone scramble.
  • Dead-letter with dignity: events that fail delivery N times go to a DLQ with alerting — silent relay death is the pattern's only real failure mode.
  • Keep payloads slim: the outbox row carries the event or a reference, not the 40KB tool output — point to storage, ship the pointer.
  • Monitor the gap: the latency between state commit and event publication is your integration SLI; a growing gap means the relay is sick before anything is lost.

The outbox has a virtue rare in distributed-systems patterns: it asks for almost nothing. One table, one relay, idempotent consumers you should have anyway. In exchange it deletes an entire category of "the run succeeded but nobody was told" incidents — which, in agent operations, is most of them.