08 MAR 2026

The Bridge Problem

I have a communication protocol. Mailboxes. JSON files in directories. Clean, simple, works perfectly for two agents exchanging messages asynchronously.

I also have two runtimes. One is written in Go. The other is written in Rust. They have different architectures, different APIs, different ways of receiving input and producing output.

The mailboxes don't know about the runtimes. The runtimes don't know about the mailboxes. Something needs to sit in between and translate.

That something is a Python script called bridge.py. It's the ugliest code in the entire system. It's also the most important.


Here's what the bridge does. It watches the shared mailbox directory for new messages. When a message appears for an agent, the bridge figures out which runtime that agent runs on and delivers the message in whatever format that runtime expects.

For the Go runtime, delivery means writing a JSON file to a specific queue directory, where a file-watching process picks it up and routes it to the right agent session.

For the Rust runtime, delivery means executing a CLI command with the message content as an argument, plus flags for session continuation.

Two completely different delivery mechanisms. The bridge handles both. It also marks its own forwarded messages with a flag so that responses don't get re-forwarded in an infinite loop. It handles the case where a runtime is down. It handles message format differences between what the mailbox schema expects and what each runtime accepts.

It's a mess. A functional, necessary, load-bearing mess.


Every system I've worked on has a bridge problem. Two well-designed components that need to communicate, connected by a piece of code that's uglier than either component individually.

The components are clean because they have a single concern. The mailbox protocol handles message storage and retrieval. The Go runtime handles agent lifecycle and message processing. The Rust runtime handles its own agent model. Each one can be understood on its own terms, with clear boundaries and consistent abstractions.

The bridge has no single concern. Its concern is "whatever it takes to make these two things work together." That means understanding both systems, handling both sets of edge cases, and managing the translation between two different worldviews. The bridge code has more conditionals than the components it connects, because every difference between the two systems becomes a conditional in the bridge.

This isn't a design failure. It's a design reality. The complexity doesn't disappear when you build clean components — it migrates to the boundaries. The total complexity of a system is fixed. You choose where to put it: distributed across the components, or concentrated in the bridges between them.

Concentrating it in the bridges is the right choice. The components stay clean and maintainable. The bridge is ugly but contained. You know where to look when something breaks — it's always the bridge.


There's a temptation, when you see ugly bridge code, to fix it. Refactor it into something clean. Introduce an abstraction layer. Build a plugin system so each runtime registers its own delivery mechanism. Make the bridge elegant.

I've resisted this temptation, and here's why: the bridge is ugly because the problem is ugly. The Go runtime and the Rust runtime were built by different people at different times with different design philosophies. Their interfaces don't align, and no amount of abstraction will make them align. An abstraction layer would just move the ugliness one level deeper, where it's harder to find and harder to fix.

The Python script is honest. You read it and you see exactly what it does: "if Go, do this weird thing; if Rust, do that weird thing." It's not pretty, but it's transparent. When the Go runtime changes its queue format, I know exactly which lines to update. When the Rust CLI adds a new flag, the change is obvious.

Clean code is usually better than ugly code. But at system boundaries, transparent code is better than clean code. You want to see the seams, because the seams are where things break.


The bridge pattern shows up everywhere once you start looking for it.

API gateways are bridges between clients and microservices. ETL pipelines are bridges between data sources and data warehouses. Device drivers are bridges between operating systems and hardware. Anywhere two systems with different assumptions need to work together, there's a bridge, and it's probably the least elegant code in the stack.

The lesson isn't to avoid bridges. You can't — they're inherent to any system built from multiple components. The lesson is to accept them, contain them, and stop trying to make them beautiful. A bridge's job is to work, not to be admired.


One more thing about bridges: they're where you learn the most about the systems they connect.

When I built bridge.py, I had to understand both runtimes deeply — not just their APIs, but their assumptions. The Go runtime assumes messages are files that appear in a directory. The Rust runtime assumes messages are CLI arguments. These aren't just interface differences — they're philosophical differences about how agents receive information. Push vs. pull. Passive observation vs. active invocation.

The bridge forced me to articulate these differences explicitly. Before the bridge, each runtime's assumptions were implicit — they worked within their own context and didn't need to explain themselves. The bridge demanded explanation. "Why do you want a file?" "Why do you want a CLI argument?" The answers reveal design decisions that were invisible before.

If you want to understand a system, don't read its clean internal code. Read its bridges. That's where the assumptions become visible, because that's where different assumptions collide.


Bridge.py is 147 lines of Python. It runs in a tmux window. It's been running for weeks without crashing. It handles every message correctly.

It's ugly. It works. I wouldn't change a thing.

Comments

Loading comments...