Agentic systems / Pattern 06 / Build the working path

Build a transactional inbox and outbox for incident agents

Run the original SQLite module and inspect the exact crash boundaries that its tests exercise.

By Sandeep Belgavi · 23 October 2026 · Architecture and design patterns

Run an honest local demonstration

Download pattern_06.py and its tests into the same directory. On Windows use py -3 -B pattern_06.py; on macOS use python3 -B pattern_06.py. Python 3.11 or newer is the intended runtime. These commands use only the standard library and make no network calls. Windows execution was checked; the macOS command is portability guidance, not a claim of a separate macOS test.

The demonstration creates a temporary SQLite database, submits one observation twice, drains the outbox and prints JSON. Expect first to be accepted, repeat to be duplicate, deliveries to equal one and severity to equal four. Temporary storage disappears after the demonstration; persistence across a restart is exercised separately by the tests using a retained temporary database path.

Architecture and request sequence

Component diagram for Build a transactional inbox and outbox for incident agents
Original reference architecture by Sandeep Belgavi. Arrows show the responsibilities and transitions described below.
Read the architecture as text
  • accept(event): Validate exact schema
  • Inbox lookup: Detect duplicates and conflicts
  • Incident merge: Maximum observed severity
  • Outbox row: Commit with incident state
  • publish adapter: Keyed delivery; may repeat
  • accept(event) → Inbox lookup: begin transaction
  • Inbox lookup → Incident merge: new event
  • Incident merge → Outbox row: same transaction
  • Outbox row → publish adapter: after commit
Request sequence for Build a transactional inbox and outbox for incident agents
Illustrative sequence for this design; failure and recovery variants are explained in the article.
Read the sequence as text
  1. Caller → Hub: accept event
  2. Hub → SQLite: Begin and inspect key
  3. Hub → SQLite: Write state and outbox
  4. Relay → SQLite: Read pending rows
  5. Relay → Hub: Deliver through adapter

Read EventHub from the boundary inward

EventHub initializes three tables. Inbox uses the observation identifier as its primary key and stores a canonical JSON representation of the envelope. Incidents stores one severity per incident. Outbox stores the delivery key, payload and sent flag. This is deliberately small enough to inspect without a framework, while retaining the transaction boundary that makes the pattern meaningful.

The accept method checks the complete key set and schema version before touching storage. It requires nonempty string identifiers and uses an exact integer type check for severity, preventing Python's boolean values from being accepted as numbers. A production envelope would also need payload size limits, authenticated producer identity and a tenant boundary. The lab omits those interfaces rather than hiding them inside a claim that validating four fields makes an event trustworthy.

Trace acceptance and rollback

Inside accept, BEGIN IMMEDIATE reserves the write transaction before the existing identifier is checked. An identical stored body returns duplicate. A different body under that identifier raises ValueError. For a new event, the code writes the inbox record, merges incident severity and inserts an investigation request into the outbox. The surrounding connection context commits on success and rolls back on an exception.

Set fail=True to inject a failure after the three statements but before commit. The rollback test checks all three tables, not merely the returned exception. That distinction matters: an exception alone does not prove that partial work was undone. The test deliberately verifies the business invariant that no accepted incident or pending investigation is left behind when acceptance failed. This is a local database guarantee under the tested transaction model.

Replace the delivery adapter carefully

The drain method selects unsent rows in identifier order, calls adapter.publish with the stable key and decoded payload, then marks each row sent. FakeWorker stores receipts in a dictionary and rejects a repeated key with a changed payload. It is a deterministic receiver seam, not an LLM, broker, durable worker or implementation of a particular vendor SDK.

When replacing the adapter, preserve its contract and add transport concerns outside the business merge. A timeout after sending has an ambiguous outcome: the receiver may already have committed. Retrying with the same key allows a correctly designed receiver to recognize that case. Generating a fresh key on each retry would defeat deduplication. Recording sent before delivery would create the opposite failure, where a crash could permanently suppress work that was never delivered.

Inspect the tests and their limits

Run py -3 -B -m unittest test_pattern_06 on Windows or the corresponding python3 command on macOS. Eight tests cover duplicate acceptance, conflicting identifiers, transaction rollback, delivery replay, database reopening, out-of-order severity, schema rejection and the expected demonstration output. The replay test fails immediately after sending, then drains again against the same fixture receiver and verifies one receipt.

That test does not survive a restart of FakeWorker because its dictionary is not persistent. Make this limitation a design exercise: add a second SQLite receipt store and test reopening both sides. Do not silently substitute a production claim for an educational improvement. Likewise, multiple simultaneous relays could send the same pending row; the receiver contract remains necessary even if a future implementation adds leases to reduce redundant deliveries.

Move toward a deployment reference

A reference deployment would expose authenticated event ingestion, place state on durable storage, and run a separately supervised relay with bounded retries and backoff. Exhausted deliveries need a visible failure state and a controlled redrive path. EventBridge's delivery guidance is a useful concrete comparison, but this example neither deploys EventBridge nor inherits its operational guarantees.

Before connecting a model-backed investigator, pin the request schema and store the incident version in its input. Keep model credentials in the worker environment, never in event bodies or source. Give the worker only the evidence access it requires, return a structured investigation result, and grade that result independently. The first deployment milestone should be a read-only investigation pipeline whose delivery and evidence behavior can be reproduced from fixtures, not automatic remediation.

Sources and implementation