· 6 min read ·

Agents Were Designed to Be Local. zmx Wants to Change That.

Source: lobsters

Most AI coding agents carry a quiet assumption: the machine running the agent is the machine where the code lives. Claude Code, Aider, and similar tools are built around direct filesystem access and local subprocess invocation. That assumption is fine when you’re coding on a laptop with nothing unusual in your setup. It becomes a constraint the moment you want better hardware, a reproducible sandboxed environment, or a remote build system that your agent can hand work off to.

zmx is a small tool that surfaced on Lobsters recently with a clear pitch: run local code agents on remote machines. It frames the connection as a portal, a transparent bridge that lets the agent work as if everything is local while the actual filesystem operations and shell commands execute remotely. That framing is doing more work than it might appear.

What Actually Happens in an Agent Session

To understand why naive remote execution doesn’t work here, it helps to enumerate what an agent actually does during a single task. It might read twenty or thirty files to build context, write edits to several of them, run a compiler or test suite, parse stdout, decide to edit again, run the tests once more. That loop can complete in under a minute on a local machine. It involves dozens of discrete I/O operations, many of them short-lived, all of them ordered relative to each other.

The classic remote development approach is SSHFS: mount the remote filesystem over SSH and let the agent treat it as local. SSHFS works well for human developers doing occasional file reads. It’s harder to use for agents. SSHFS adds a round-trip to every file operation, and agents do file operations at machine speed. On a typical home internet connection, that latency compounds quickly. More problematically, SSHFS has well-known consistency issues when you have both a remote process and a local mount writing to the same paths concurrently.

The other naive approach is to just SSH into the remote machine and run the agent there. That works, but then you lose the local setup: your editor, your config, your tool integrations. The whole premise of these agents is that they work with your existing environment. Shipping your entire local environment to every remote machine you might want to use is its own problem.

A Structured Message Layer Over a Dumb Pipe

What zmx appears to do instead is decompose the agent’s operations into a structured protocol and forward them over a message transport. Rather than pretending the remote filesystem is local via a mount, it translates discrete operations (read this file, write this content, run this command, stream this output) into messages that a remote daemon can execute and respond to.

The name zmx suggests ZeroMQ as the underlying transport. ZeroMQ is worth choosing here for specific reasons. It handles connection management and reconnection transparently, which matters for long-running agent sessions where you don’t want a single dropped packet to kill everything. Its dealer/router socket pattern lets the local side issue many concurrent requests without waiting for replies in order, which maps well to how agents actually behave: they might want to read several files in parallel before deciding what to write. And ZeroMQ’s framing layer means you’re sending typed messages rather than byte streams, which makes it easier to add fields to requests without breaking older implementations.

A request/reply cycle over ZeroMQ for a file read looks something like:

import zmq

# local side
ctx = zmq.Context()
sock = ctx.socket(zmq.REQ)
sock.connect("tcp://remote-host:5555")

sock.send_json({"op": "read", "path": "/project/src/lib.rs"})
reply = sock.recv_json()
content = reply["content"]

For shell commands you’d want something more like a streaming response, which pushes you toward the dealer/router pattern and a separate socket for event streams. ZeroMQ supports both in the same process without additional threads, which keeps the implementation tight.

The Statefulness Problem

What makes agent-to-remote bridging harder than typical RPC is that agents are stateful in a way that most distributed services are not. A web API call is self-contained. An agent session builds cumulative context: the agent knows what files it has read, what edits it has made, what the test output said three steps ago. That state lives locally in the agent’s context window. The remote machine only sees individual operations, not the narrative they’re part of.

This is mostly fine, but it creates a specific class of failure. If the remote daemon restarts mid-session, it loses any process-level state it was maintaining: open file handles, environment variables set by previous commands, a long-running background build. The local agent has no way to know the remote side has reset. A robust portal implementation needs a session handshake that can detect restarts and, where possible, restore relevant state before resuming.

ZeroMQ’s built-in reconnection helps at the transport layer, but the application layer still needs to handle the semantic gap. The simplest approach is to make every operation idempotent where possible: reads are naturally idempotent, writes can carry a content hash for verification, shell commands are the hard case.

The Operational Story

I run several long-lived services on remote machines, including a Discord bot that processes hundreds of events per day. The operational question I’d ask about zmx is how the remote daemon is meant to be deployed and managed. Does it run as a systemd service? A Docker container? Does it bind to a fixed port or use a dynamic allocation?

For security, binding a ZeroMQ socket to a public interface requires authentication. ZeroMQ ships with CurveZMQ, an elliptic-curve auth layer built on libsodium, which gives mutual authentication and encryption with minimal setup overhead. Without it, anyone who can reach the port can issue arbitrary shell commands on your remote machine. That’s not a hypothetical risk.

A typical setup for a team would probably involve a lightweight daemon behind a WireGuard tunnel, bound only to the WireGuard interface, with CurveZMQ keys exchanged out-of-band. That’s about as much overhead as setting up Tailscale with SSH, which is already a solved problem for remote development.

How This Compares to Existing Approaches

GitHub Codespaces and Gitpod move the whole development environment to the cloud. That’s the inverse of what zmx does: instead of bringing your local agent to a remote machine, you bring everything to the cloud and connect from a thin local client. The cloud approach has advantages (reproducibility, consistent specs, no local resource contention) but it’s expensive, requires a browser or a heavy client extension, and doesn’t map cleanly to developer setups that rely on local tooling.

Devcontainers, which Claude Code now supports, standardize the local environment definition so it can be reproduced anywhere. zmx standardizes the transport instead. You could use them together: define the remote environment with a Devcontainer spec and use zmx to forward agent operations into it. That combination would give you reproducible remote environments with a local agent UX, which is more flexible than either approach alone.

Aider, being a pure CLI tool, could theoretically be wrapped to run through a zmx portal without any changes to Aider itself. If the portal correctly virtualizes file reads, writes, and subprocess execution, Aider never needs to know it’s talking to a remote machine. That’s the strongest case for zmx’s design: it works at the OS interface layer, not the agent API layer, so it doesn’t require agent-side changes.

What Matters Next

The concept is well-scoped. The gaps that would determine whether this is useful in practice are protocol documentation, error semantics, and latency numbers. Protocol documentation matters because if the portal is only tested against one agent, edge cases in other agents will surface quickly. Error semantics matter because agents don’t handle unexpected failures gracefully by default: they retry, they loop, they sometimes make things worse. A portal that silently drops a write and returns success will cause subtle corruption that’s hard to diagnose.

The latency number that matters most is not peak throughput but p95 round-trip time for a realistic agent workload over a realistic WAN link. ZeroMQ can sustain millions of messages per second on loopback; the question is whether the protocol overhead, serialization, and network transit keep round-trips under the threshold where the agent’s iteration loop noticeably slows down. If a full read-edit-test cycle takes 20% longer through the portal than locally, most users will accept it. If it takes twice as long, they won’t.

The project is early and worth watching. As AI agents take on more resource-intensive tasks, the assumption that they run on the developer’s local machine will keep coming up as a bottleneck. zmx is a concrete attempt to decouple where the agent runs from where the work executes, which is the right direction.

Was this interesting?