· 6 min read ·

The Portal Pattern: Running AI Code Agents Across Machine Boundaries

Source: lobsters

AI coding agents have a fundamental tension baked into their design. Tools like Claude Code, Aider, and Cursor work best when they have direct, low-latency access to your filesystem. They read files, run shell commands, watch test output, and iterate fast. The entire feedback loop depends on being local. But there are real reasons you might want that agent running somewhere other than your laptop: a beefier remote machine, a sandboxed VM that you can nuke and restore, a CI-adjacent environment where the agent’s changes can be validated immediately, or simply a shared team environment where multiple agents work in parallel without fighting over one machine’s resources.

zmx, spotted on Lobsters, is a tool that tries to thread this needle. The pitch is direct: run local code agents on remote machines. It creates what the author calls an AI portal, a bridge that lets an agent running locally appear to the remote machine as if it were native, and vice versa.

Why This Is Not Just SSH

The obvious response is “use SSH with remote port forwarding and call it a day.” That works for a lot of things, but AI coding agents aren’t just web services listening on a port. They’re interactive, stateful, and IO-heavy in ways that make naive tunneling frustrating.

Consider what a single agent interaction actually involves: the agent reads a directory tree, opens several files, writes edits, runs a compiler or test suite, reads the output, and loops. That’s dozens of filesystem operations and subprocess invocations per iteration, often running concurrently. Wiring that up over SSH with something like SSHFS or NFS adds latency at every hop and introduces consistency problems when you have both local and remote processes touching the same paths.

The other issue is control plane vs. data plane. SSH gives you a shell. What you actually want when bridging agent environments is a structured messaging layer: something that can multiplex many concurrent requests, handle backpressure, and let you route specific kinds of traffic (filesystem ops vs. shell commands vs. agent state) through different channels with different guarantees.

ZeroMQ as Infrastructure

The name zmx is almost certainly a nod to ZeroMQ, the high-performance asynchronous messaging library that has been a quiet workhorse of distributed systems since around 2007. ZeroMQ is not a broker-based message queue in the traditional sense. It’s a library that gives sockets superpowers: pub/sub, push/pull, request/reply, and dealer/router patterns, all with built-in framing, connection management, and reconnection logic.

For a tool like zmx, ZeroMQ is a credible choice precisely because of the dealer/router pattern. A DEALER socket on the local side can send requests without waiting for replies, letting multiple agent operations in flight simultaneously. A ROUTER on the remote side can multiplex those into separate worker processes or threads. You get the concurrency you need without implementing your own async I/O layer.

ZeroMQ also handles the connection lifecycle gracefully. If the remote machine restarts, the library will keep retrying the connection transparently. For a long-running agent session, that resilience matters. Compared to rolling your own WebSocket multiplexer or depending on SSH’s connection stability, ZeroMQ’s out-of-the-box behavior is hard to beat.

A minimal push/pull setup in Python looks like this:

import zmq

# On the remote machine (receiver)
ctx = zmq.Context()
sock = ctx.socket(zmq.PULL)
sock.bind("tcp://*:5555")
while True:
    msg = sock.recv_json()
    # dispatch filesystem op, shell command, etc.
    print(msg)

# On the local machine (sender)
ctx = zmq.Context()
sock = ctx.socket(zmq.PUSH)
sock.connect("tcp://remote-host:5555")
sock.send_json({"op": "read", "path": "/project/src/main.rs"})

This is the skeleton. The actual implementation needs bidirectional flow (replies, events), serialization of file content, and a way to forward subprocess stdin/stdout. But the primitives are all there, and ZeroMQ handles threading and buffering so you don’t have to.

The Agent Portability Problem

What makes zmx interesting beyond the transport layer is the framing as an “AI portal” rather than just a tunnel. The word portal implies that the agent itself doesn’t need to know it’s running remotely. From the agent’s perspective, it’s still talking to a local filesystem and a local shell. From the remote machine’s perspective, it’s just receiving well-formed commands and sending back results.

This is the same design principle behind Language Server Protocol. LSP separated the editor frontend from the language intelligence backend by defining a clean JSON-RPC protocol over stdio or TCP. Any editor could talk to any language server without either side knowing implementation details of the other. zmx seems to be attempting something similar for the agent/environment boundary: a protocol layer that makes the execution environment substitutable.

If that abstraction holds, you get some powerful properties. You can run multiple agents against the same remote codebase concurrently, each with their own isolated view. You can snapshot the remote environment before an agent run and roll back if the changes are bad. You can even route different agents to different remote machines based on resource availability, which starts to look like agent orchestration infrastructure.

Security Surface

Any tool that forwards shell commands and filesystem access to a remote machine is a significant security primitive. The threat model matters. If zmx is running over a local network or a WireGuard tunnel to a machine you control, the risk is manageable. If it’s meant to connect to arbitrary remote hosts over the public internet, the authentication and authorization story needs to be airtight.

ZeroMQ itself supports CurveZMQ, an elliptic curve encryption layer built on NaCl, which gives you mutual authentication and forward secrecy with minimal configuration overhead. It’s less common than TLS in modern tooling, but it’s well-suited to ZeroMQ’s connection model and adds no external dependencies beyond libsodium.

The other surface is command injection on the remote side. If the portal naively shells out whatever the agent sends, a compromised or malicious agent can do whatever the remote user account allows. A well-designed implementation constrains execution to a specific working directory and a whitelist of allowed operations, keeping the remote machine’s exposure bounded.

Where This Fits in the Ecosystem

GitHub Codespaces and Gitpod solve a related problem by moving the whole development environment to the cloud. That’s a heavier lift than what zmx appears to do. Codespaces gives you a full VSCode-in-a-browser experience with all the overhead that implies. zmx seems aimed at a narrower case: you already have a preferred local agent setup, you just want the execution to happen elsewhere.

Claude Code added support for running inside Devcontainers, which is another angle on the same problem. The difference is that Devcontainers standardize the environment definition; zmx standardizes the transport. They’re complementary rather than competing.

Aider, the open-source coding assistant, can be run entirely from the command line and has no strong opinion about where it runs. But it still assumes it’s talking to a local filesystem. Wrapping Aider with something like zmx would let teams deploy it on a shared machine and let individual developers connect to it without SSH session management.

What I Want to See

The concept is solid. The part I’d want to see documented is the protocol specification itself: how requests are framed, what the error model looks like, how partial reads of large files are handled, and whether there’s a capability negotiation step so a single portal can serve both old and new agent versions.

I’d also be curious about latency benchmarks compared to alternatives. ZeroMQ can sustain millions of messages per second on loopback, but the interesting number is round-trip latency for a realistic agent workload: something like “open 20 files, run the test suite, get back structured output” measured across a WireGuard tunnel between two machines in different regions. That’s the number that determines whether the portal abstraction is usable or just technically correct.

The project is early, and the Lobsters discussion thread is worth reading for community reactions. But the direction is right. As AI coding agents become more capable and more resource-intensive, the assumption that they run on the same machine as the developer is going to erode. Tools that treat the execution environment as a configurable, swappable layer will age better than tools that hardcode it.

Was this interesting?