codesbyjit.dev
← cd ../blog
#LLM#AI Engineering#AI Agents#MCP#Agentic AI#Tool Calling#Automation

AI Agents Aren't Just Chatbots: How Agentic Systems Actually Work

acodesbyjit·AUG 29, 2026·1 min read
AI Agents Aren't Just Chatbots: How Agentic Systems Actually Work

The Outbox Pattern: How I Prevented Distributed Systems From Losing Events

When I started building an event-driven e-commerce system, I assumed creating an order would be straightforward.

Save the order.

Publish an event.

Let other services react to it.

Simple.

Until you ask one uncomfortable question:

What happens if the order is saved successfully but the event fails to publish?

That is the problem that led me to the Outbox Pattern.

The Dual-Write Problem

Imagine this workflow:

text
Create Order
     ↓
Save to Database
     ↓
Publish OrderCreated Event

There are two separate operations here:

  1. Writing to the database
  2. Publishing to the message broker

Those operations are not automatically one transaction.

So this can happen:

text
Database Save → Success
Event Publish → Failure

Now the order exists.

But services listening for OrderCreated never receive the event.

Maybe inventory is never updated.

Maybe the payment service never reacts.

Maybe the user never receives confirmation.

The system is now inconsistent.

The opposite failure is also possible:

text
Event Publish → Success
Database Save → Failure

Now other services think the order exists even though it was never committed.

The Outbox Pattern

The solution is to store the event in the same database transaction as the business data.

Instead of doing this:

text
Database
    ↓
Message Broker

The application does this:

text
Database Transaction
├── Create Order
└── Create Outbox Event

If the transaction succeeds, both records exist.

If it fails, neither exists.

A separate process can then read unpublished events from the outbox and publish them to the message broker.

The flow becomes:

text
Create Order
     ↓
Database Transaction
├── Order
└── Outbox Event
     ↓
Event Publisher
     ↓
Message Broker
     ↓
Other Services

That small change removes the dangerous gap between updating business data and recording the event that needs to be published.

Why Retries Matter

Publishing can still fail.

The message broker might be unavailable.

The network might temporarily fail.

The consumer might be overloaded.

The difference is that the event has not disappeared.

It is safely stored in the outbox.

The publisher can retry later.

This is why event-driven systems often use concepts such as:

  • At-least-once delivery
  • Retries
  • Idempotency
  • Dead-letter queues

The system accepts that a message might be delivered more than once.

Instead of trying to make distributed communication perfectly reliable, consumers are designed to safely handle repeated events.

The Other Problem: Duplicate Events

Imagine this:

text
Publisher sends event
        ↓
Broker receives event
        ↓
Publisher crashes before marking event complete

When the publisher restarts, it may publish the same event again.

This is why consumers should be idempotent.

For example, processing the same payment event twice should not charge the customer twice.

A common approach is to give each event a unique ID and record which events have already been processed.

text
Event ID
   ↓
Already Processed?
   ├── Yes → Ignore
   └── No  → Process Event

How I Applied It

While building Zatoshi, an event-driven e-commerce marketplace built around multiple containerized services, I used asynchronous communication to separate responsibilities between services.

The Outbox Pattern was important because service communication cannot be treated like a simple function call.

A distributed system has to expect:

  • Services going offline
  • Temporary network failures
  • Duplicate messages
  • Delayed consumers
  • Partial failures

The goal is not to pretend these problems will never happen.

The goal is to design the system so that it can recover when they do.

The Biggest Lesson

The Outbox Pattern taught me an important lesson about distributed systems:

A successful API request does not necessarily mean the entire system is consistent.

When one service depends on another service eventually receiving an event, the reliability of that event becomes part of the business logic.

That is why patterns such as transactional outboxes are useful.

They move an unreliable network operation out of the critical database transaction while still ensuring that the event is not forgotten.

The architecture becomes more complex.

But that complexity is solving a real problem.

And in distributed systems, understanding why that complexity exists is usually more important than memorizing the pattern itself.

share: