Agents Are Agents

Part 11 in the series. Previously: The Hammer Problem (Part 10), Computational Strategy (Part 9), Model Eats Software (Part 8), On Keeping AI in the Critical Path (Part 7), The Confident Incompetence Problem (Part 6), The Disintermediation Principle (Part 5), Zen of Unix Tools (Part 4).

Act I: The Lineage

Erlang

In September 2004, at the ACM SIGPLAN Erlang Workshop in Snowbird, Utah, Carlos Varela and his colleagues at the University of A Coruña presented “On Modelling Agent Systems with Erlang.” The paper described how to build multi-agent systems using Erlang’s concurrent, fault-tolerant process model. The architecture they laid out was precise: each agent is a group of communicating processes: an event receiver, a state server, an executor, and a main process. Agents maintain beliefs about the world. They have desires: states of affairs they’d like to bring about. They form intentions: plans they’ve committed to achieving. Events arrive as messages. The agent updates its beliefs, produces goals, checks preconditions and invariants, executes actions, and checks postconditions. If a plan fails, the failure is handled structurally.

This was not new in 2004. Erlang itself dates to 1986, created by Joe Armstrong and his colleagues at Ericsson to solve a specific problem: telephone switches that could never go down. The insight that made Erlang work was that you can’t prevent failure in a distributed system, so you design for recovery. Lightweight processes. No shared memory. Message passing. Supervisors that restart failed children. Let it crash.

The 2004 paper demonstrated that Erlang’s process model naturally implements agent architectures. An Erlang process receives a message, does work based on internal state, sends a message. The process might fail. The supervisor restarts it. The system continues. This wasn’t a metaphor. It was a production architecture running telephone networks that served millions of people.

The BDI model — beliefs, desires, intentions — gave agents a cognitive structure. An agent doesn’t just react to messages. It maintains a model of the world (beliefs), a set of objectives (desires), and a commitment to specific plans (intentions). When the environment changes — a new message, a failed action, new information — the agent updates its beliefs and reconsiders its intentions. The 2004 paper showed how naturally this mapped to Erlang’s process model. Each component of the BDI architecture became a process. Communication between components became message passing. Fault tolerance came from supervision trees.

The core problem they solved: how do you coordinate independent processes that have their own state, can fail independently, and communicate through messages?

I read that paper in 2004, and both Erlang and functional programming within distributed systems were areas that I tracked. It crystallized somethings I’d been thinking about. This influenced aspects of Joyent’s architecture: we would start building a highly reliable, highly resilient control plane based on agents.

Node.js, SmartOS, and Manta

But instead of doing it in a language from the 1980s and asking people to learn Erlang, we made a different set of choices.

The language runtime would be a C-based implementation of JavaScript: familiar C-like and javascript syntax, event-driven, single-threaded with non-blocking I/O. Ryan Dahl created Node.js on Chrome’s V8 engine in 2009 while working at Joyent, where I was CTO. The event loop model drew from the same well as Erlang — message-passing concurrency, non-blocking I/O, isolated single-threaded processes — adapted for a language the web development world already knew. Lightweight, asynchronous, one thing at a time: each Node.js process was a focused agent doing its job.

The “VM” nature of Erlang’s language-based kernel — the BEAM virtual machine that gave Erlang its fault tolerance and hot code reloading — we replaced with something more fundamental: an actual minimized operating system kernel capable of running hardware directly. SmartOS, based on the illumos kernel (the open-source continuation of Sun’s Solaris), could run bare metal. It wasn’t a language runtime pretending to be an OS. It was an OS designed to make every process observable and isolatable.

Isolation in SmartOS was a zone — a modernized, hardened version of FreeBSD’s jails, inherited through Solaris. A zone provided complete process isolation at the OS level. Not a namespace hack. Real isolation, enforced by the kernel.

Then we wrapped each zone in a ZFS volume. ZFS gave us atomicity and copy-on-write semantics. Every state change to the zone’s filesystem was transactional. You could snapshot it, roll it back, clone it. The zone’s state was never ambiguous.

Then there was an entire deterministic policy system on top. The policy system took input from DTrace — the dynamic tracing framework that gave us real-time instrumentation of everything happening inside the zone, in the kernel, and on the hardware. The policy system could observe, reason about, and act on what every agent in the system was actually doing, informed by performance data, resource consumption, and behavioral patterns.

That combination — zone (isolation) + ZFS volume (atomic state) + policy system (informed by instrumentation) — is what we called a container.

People forget this. The word “container” wasn’t originally used for isolation. The isolation was the zone. The container was the intelligent wrapper: the system that observed the agent, enforced policy, maintained atomicity, and could roll back mistakes. I literally wrote that a container is “a system that prevents any agent from making a mistake.” Docker later adopted the word and stripped it down to a filesystem namespace. The industry got the noun without the concept.

Erlang called them agents. We called them agents. The container existed to make agents safe.

Then we built Manta. Manta was Joyent’s object storage system, and it was the thing I wished I could have shown Dennis Ritchie, who had passed away a couple of years earlier. Manta was a completely distributed, agent-based, message-passing system that allowed for a distributed Unix command line environment. You could run grep, awk, sort — the full Unix toolkit — inside compute zones that spun up next to your data in the object store. The job patterns were pure Unix philosophy: word count, word frequency, line count by extension, ETL, image conversion, video transcoding. Each job ran inside an isolated zone, on top of ZFS, governed by policy, composed through pipes.

I wrote much of that documentation myself, and testing it I felt the pride of a team that had captured the true spirit of the Unix philosophy and made it work at scale, through an API, in a distributed system. Every job pattern worked because the architecture was right: isolated agents, message passing, text as universal interface, composition over configuration. It was everything Dennis Ritchie and Ken Thompson had built in the 1970s, extended to a distributed system with the fault tolerance Joe Armstrong had built in the 1980s.

cat data | grep pattern | sort | uniq -c — whether that runs on a PDP-11, a Solaris box, a SmartOS zone, or inside a Manta compute job, the abstraction is the same. Text in, text out. Small tools composed through pipes.

Claude Code

Node.js’s JavaScript VM was from Chrome — the V8 engine. A developer named Jarred Sumner took the JavaScript VM from Safari — Apple’s JavaScriptCore engine — and built Bun, a faster JavaScript runtime compatible with Node.js. Anthropic hired key members of the Bun team. Claude Code is written in Bun.

The runtime lineage is real — V8, JavaScriptCore, and Bun share DNA with the evented, single-process model that Node.js established. But the more interesting connection isn’t in the runtime. It’s in the architecture that emerged independently.

Claude Code’s architecture, if you look at it as a systems person: one process (the model) receives events (your prompts), maintains state (the conversation context), executes actions (tool calls to external processes), and has a main loop that decides what to do next. The model maintains beliefs (what it’s read from files, what it knows about your codebase), desires (the task you’ve given it), and intentions (the plan it’s forming). It checks preconditions before tool calls, checks postconditions after. If something fails, it adjusts and tries a different approach.

That’s structurally the same as the BDI architecture from the 2004 paper. Nobody at Anthropic designed it that way on purpose. It converged — independently — because the problem is the same problem: how do you coordinate an autonomous agent that has its own state, can encounter failures, and communicates through messages? When independent teams arrive at the same architecture decades apart, that’s evidence the architecture is correct.

MCP — the Model Context Protocol — is message passing. A tool call goes out, a result comes back. The model doesn’t share memory with the tool. The tool doesn’t know about the model’s internal state. Text in, text out. The same abstraction as Erlang’s process communication. The same abstraction as Unix pipes.

CLAUDE.md is a config file. The conversation is the process mailbox. The terminal is the interface. The model is the most capable filter ever built, sitting in the middle of a pipeline.

Unix is the precedent. Text as universal interface. Small tools, composed through pipes. The model is the most capable filter ever built.

Claude Code got good when it got good at bash. Partly this is a function of training data — bash is well-represented in the corpus. But the deeper reason is that the design of Unix is so coherent that the patterns are learnable: decades of consistent philosophy, embodied in tools, captured in text. The model learned the worldview along with the syntax because the worldview is inseparable from the syntax.

The lineage where it’s direct: Erlang’s actor model (1986) → the agent systems paper that influenced our thinking (2004) → SmartOS zones + ZFS + DTrace + policy = containers (2011) → Node.js on SmartOS with full instrumentation → Manta as distributed Unix pipeline (2013). The lineage where it’s convergent: Chrome’s V8 powers Node.js → Safari’s JavaScriptCore powers Bun → Anthropic hires the Bun team → Claude Code written in Bun → an agent in a terminal, composing tools through text, arriving at the same architecture because the problems are the same.


Act II: The Exposure

When a large language model acting as an agent causes a problem in a production system — deploys bad code, misconfigures infrastructure, takes down a service — the instinct is to say “AI agents are dangerous.”

The correct response is: “What would have happened if a human agent made the same mistake?”

If the answer is “the same thing” — and it very often is — then the AI didn’t introduce the vulnerability. It exposed it. The system was already fragile. The blast radius was already large. The lack of atomicity, the missing rollback capability, the absent verification step — those were there before the AI arrived.

AI agents do introduce new dimensions of risk: they operate at machine speed, and they can attempt failure modes a human would never naturally produce. The threat model is different in frequency and character. But the architectural response is the same: contain the blast radius, make state recoverable, design for failure.

The vast majority of postmortems in the history of distributed systems trace back to agent errors — a human deployed to the wrong environment, ran a migration without a backup, pushed a config change that cascaded. The failure mode was always the same: an agent made a mistake in a system that couldn’t tolerate mistakes.

Should an agent be allowed to come up with a deploy script on the fly? No. Neither human nor AI. Can an agent execute a deterministic, well-tested, formally verified body of software that runs a deploy process? Yes. Human agents do this every day. Should a system be dual-controller? A/B? Atomic? Capable of rollbacks? Yes — regardless of whether the agent is human or AI.

An AI agent can outperform a human in verifying software before deployment — it can check more paths, faster. An AI agent can outperform a human in analyzing a large corpus of logs after an incident — it can process more data, faster. These are strengths in specific contexts, not universal truths. But the fact that “AI” is an adjective in front of “agent” changes nothing about the agent-based approach to highly reliable distributed control systems.

This is what the Erlang community understood in the 1980s. The entire OTP framework — the supervisors, the gen_servers, the application trees — exists because agents fail. Human agents, software agents, any agents. The system’s job is to contain the failure and recover. Not to prevent agents from ever making mistakes — that’s impossible. But to ensure that when they do, the blast radius is contained, the state is recoverable, and the system continues.

Agent-based programming in the 1980s was designed to prevent human error in distributed control systems that always have to work. Telephone switches. Power grids. Industrial control. The architecture didn’t trust agents. It contained them.

The AI agent discourse in 2026 is rediscovering this. But it’s rediscovering it badly. Instead of isolation and supervision, the frameworks offer orchestration and consensus. Instead of letting agents crash and restarting them cleanly, they add “reflection” steps — which are just retry loops with different prompts. Instead of message passing with clear boundaries, they share context windows and system prompts across agents. They’re building the equivalent of shared-memory multithreading: the thing that Erlang, Solaris zones, and SmartOS containers were specifically designed to replace.

If someone says “an AI agent caused an outage,” ask them: would a human agent have caused the same outage? If yes, the problem isn’t the AI. The problem is the architecture. The problem is that the system allows any agent — human or artificial — to make an unrecoverable mistake.

Then get to the actual problem: minimize the set of irreversible actions and contain everything else. That’s what zones were for. That’s what ZFS atomicity was for. That’s what the policy system was for. That’s what Erlang supervisors were for. Some actions will always be irreversible — financial transactions, data deletion, firmware deployments. The architecture’s job is to make that set as small as possible and put the strongest verification around what remains. The tools exist. The patterns are proven. The only thing that changed is the adjective in front of “agent.”

The vast majority of papers, postmortems, and incident reviews in the history of mission-critical software have been about agent errors. The agent was usually human. Now it’s sometimes AI. The architecture requirements are the same.

If an AI agent is a problem in your system, your system has problems.


Act III: The People

I was fortunate in my time at Ericsson because Joe Armstrong had come back to the company and was in the next building, a floor down. Joe created Erlang. He was also a philosopher of computing — someone who thought deeply about why systems fail and what it means to build something reliable. He understood that the hard problem isn’t making software that works. It’s making software that works when things go wrong. When agents make mistakes. When the network drops. When the state is ambiguous. He designed Erlang around the assumption that everything will eventually fail, and the only honest response is to make failure recoverable.

Joe passed away in April 2019. I remember him talking about the nature of concurrent systems with the kind of clarity that made you realize the person speaking had thought about nothing else for decades. He saw distributed systems as fundamentally about communication and failure — not about control. I believe he would have recognized what’s happening now with AI agents immediately: MCP as message passing, the conversation loop as a gen_server, the tool-call pattern as the same architecture he’d been building since the 1980s. He was too much of a philosopher and too much of a lived historian of the space not to.

Dennis Ritchie created Unix and the C programming language. With Ken Thompson, he built the foundation that everything since has been built on. Text as universal interface. Pipes. Small tools that do one thing. The philosophy so clean that it remains learnable — by humans and by neural networks — fifty years later.

Dennis passed away in October 2011. When I was writing the documentation for Manta in June 2013 and testing the job patterns — cat | grep | sort | uniq running at scale inside isolated compute zones against a distributed object store — I was thinking of him. Feeling appreciative. It was the kind of system I wished I could have showed him. He would have found it entirely familiar. The abstraction was his abstraction. The philosophy was his philosophy. We just made it distributed.

Am I saying that I’m somehow responsible for how Claude Code turned out? Of course not. Anthropic’s engineering team built something excellent. What I’m saying is that it didn’t come out of nowhere. The software systems we choose to use — they had creators. Those creators had philosophies. Those philosophies are embedded in the code, and the code is embedded in the training data, and the training data is embedded in the weights. The lineage is real even when it’s not visible.

Claude Code is good at bash because bash is good. Because the design was so clean that the patterns survived compression into weights. Because Dennis Ritchie and Ken Thompson built something whose philosophy transfers through the code itself, not through documentation about the code.

Claude Code’s agent architecture converges with Erlang’s agent architecture because the problems are the same and the correct solutions are the same. Joe Armstrong understood that agents fail and systems must recover. The BDI model — beliefs, desires, intentions — is how autonomous agents actually operate, whether they’re Erlang processes, SmartOS zones, or language models in a terminal.

The tools I use today to get 25-75x acceleration on complex projects — Claude Code in a terminal, composing tools through text, reasoning over data, executing deterministic computation, maintaining conversation state — it’s the same approach I’ve been using since I first got into a command line in the 1980s. The agent got more capable. The architecture didn’t change.

Nothing is new. The patterns are proven. The lineage is real. The people who built the foundations aren’t all here to point that out.

But their work is in the walls.

2 responses to “Agents Are Agents”

  1. […] companion to Agents Are Agents (Part 11 in the […]

  2. […] 12 in the series. Previously: Agents Are Agents (Part 11), The Hammer Problem (Part 10), Computational Strategy (Part 9), Model Eats Software (Part […]

Discover more from Jason A. Hoffman

Subscribe now to keep reading and get access to the full archive.

Continue reading