Agents Are Agents

March 12, 2026

Part 11 in the series. Previously: The Tooling Trap (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).


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.

The paper drew on Erlang, which Joe Armstrong and his colleagues at Ericsson created in 1986 for telephone systems that could not tolerate extended outages. Erlang assumes that failures cannot be prevented altogether and makes recovery part of the design. It provides lightweight processes without shared memory, communication by messages, and supervisors that restart failed children.

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 gives an agent three kinds of state: beliefs about the world, desired outcomes, and intentions represented by committed plans. A new message, failed action, or other event can update the beliefs and trigger reconsideration of a plan. The 2004 paper implemented each BDI component as an Erlang process, used messages for communication, and used supervision trees for fault tolerance.

The design addresses a specific problem: coordinating independent, stateful processes that communicate through messages and can fail separately.

I read the paper in 2004 while following Erlang and functional programming in distributed systems. It clarified ideas that later influenced Joyent’s agent-based control plane.

Node.js, SmartOS, and Manta

At Joyent, we pursued related goals with a different language and operating system.

Ryan Dahl created Node.js on Chrome’s V8 engine in 2009 while working at Joyent, where I was CTO. It offered familiar JavaScript syntax, an event loop, and non-blocking I/O. Node did not reproduce Erlang’s actor model, but both designs favored isolated units of execution and asynchronous communication over shared-state threads. Each Node.js process could perform one focused role.

We placed that runtime on SmartOS, a bare-metal operating system based on illumos, the open-source continuation of Sun’s Solaris. Where Erlang relied on the BEAM virtual machine for features such as process supervision and hot-code loading, SmartOS made operating-system processes observable and isolated.

SmartOS used Solaris zones for kernel-enforced process isolation. ZFS volumes added snapshots, rollback, clones, atomic updates, and copy-on-write storage for each zone’s filesystem.

Above them, a deterministic policy system consumed DTrace instrumentation from the zone, kernel, and hardware. It could act on measured performance, resource consumption, and behavior.

We called the combination of an isolated zone, atomic ZFS state, and instrumented policy a container.

In our usage, the zone supplied isolation. The container was the larger system that observed the process, enforced policy, maintained recoverable state, and could roll back mistakes. I described it at the time as “a system that prevents any agent from making a mistake.” Docker later used the same noun for a narrower packaging and isolation model.

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

We then built Manta, Joyent’s distributed object-storage and compute system. It ran Unix tools such as grep, awk, and sort in compute zones near the data. Users composed word counts, ETL jobs, image conversions, and video transcodes through pipes. Each job ran in an isolated zone on ZFS under the policy system.

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.

The command cat data | grep pattern | sort | uniq -c retains the same interface on a PDP-11, a Solaris system, a SmartOS zone, or a Manta compute job: text passes through small tools joined by pipes.

Claude Code

Node.js uses Chrome’s V8 engine. Jarred Sumner built the Node-compatible Bun runtime on JavaScriptCore, the engine used by Safari. Anthropic later hired key members of the Bun team, and Claude Code is written in Bun.

That is a direct runtime history. The comparison with Erlang and BDI is structural rather than genealogical.

Claude Code has one model process that receives prompts, maintains conversation state, calls external tools, and chooses the next action. In BDI terms, the files and conversation supply beliefs, the user’s task supplies a desired outcome, and a plan records intended actions. The analogy describes functions in the control loop; it does not claim that a language model has human mental states.

The structures resemble one another because both coordinate a stateful process that encounters failures and communicates by messages. Convergence does not prove that either design is uniquely correct, but it identifies a durable set of requirements.

MCP also uses message passing: a model sends a tool call and receives a result without sharing its internal state with the tool. Erlang processes and Unix pipes differ in implementation, but all three designs communicate across explicit boundaries.

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’s command-line ability depends partly on extensive Bash and Unix material in its training data. Unix also presents a regular interface: text streams, small commands, and explicit composition. That consistency makes its patterns easier for both people and models to reuse.

The direct line in my work runs from Erlang’s actor model and the 2004 agent-systems paper to SmartOS containers, Node.js instrumentation, and Manta’s distributed Unix pipelines. A separate runtime line runs from V8 and Node.js through JavaScriptCore and Bun to Claude Code. The BDI comparison connects them by recurring design requirements rather than documented influence.


Production Failure

When an AI agent deploys bad code, misconfigures infrastructure, or takes down a service, the incident review should separate risks introduced by the model from controls the system already lacked.

One useful counterfactual is: what would have happened if a person made the same mistake?

If the result would have been identical, the model exposed a preexisting control failure: excessive permissions, non-atomic changes, missing rollback, or absent verification. AI agents still add risk because they act at machine speed and can attempt unusual sequences of actions. The threat changes in frequency and form, even when the required controls remain familiar.

The response is to limit the effect of any one action, keep state recoverable, and assume that operators and software will fail.

Distributed-systems postmortems often begin with an operator deploying to the wrong environment, running a migration without a backup, or pushing a configuration change that cascades. The initiating error matters, but the system determines whether that error becomes an outage.

Neither a person nor a model should invent an unreviewed deployment procedure in production. Both can invoke a tested, deterministic deployment tool within bounded permissions. Critical systems should use the appropriate combination of independent control, staged rollout, atomic changes, and rollback regardless of who starts the process.

An AI agent may check more execution paths or search more logs than a person in the same time, although the result still requires validation. Those capabilities change who performs parts of the work, not the need for reliable controls around it.

Erlang’s OTP framework encodes this assumption. Supervisors, gen_server behaviors, and application trees organize recovery after a process fails. They do not make mistakes impossible; they bound failure and restore service.

The resulting architecture does not depend on an infallible operator or process. It contains each one.

Some current agent frameworks emphasize orchestration, shared context, and repeated “reflection” prompts while giving less attention to isolation, supervision, and clean restart. Shared context can reproduce the coupling problems of shared-memory systems: one agent’s bad state contaminates the others, and recovery has no clear boundary. Established distributed-systems practice suggests isolating state and communicating through explicit messages where the task permits it.

When an AI agent causes an outage, ask which behavior was unique to the model and which control would also have failed under a human operator. Both answers belong in the postmortem.

Minimize irreversible actions and contain the rest. Zones isolate processes, ZFS supports recoverable state, policy limits action, and supervisors restart failed workers. Financial transactions, destructive data changes, and firmware deployments may remain difficult or impossible to reverse, so they deserve the strongest approval and verification controls.

AI adds a new kind of operator to systems that already had to tolerate fallible ones. Its speed and behavior require a revised threat model, but not abandonment of the recovery principles developed for distributed systems.


The People

During my time at Ericsson, Joe Armstrong had returned to the company and worked in the next building, one floor down. He had created Erlang and spent decades thinking about concurrent systems, communication, and failure. Erlang assumes that processes, networks, and state will eventually fail and makes recovery a design responsibility.

Joe died in April 2019. I remember the clarity with which he discussed concurrent systems. I believe he would have recognized familiar elements in today’s AI agents: MCP messages, a conversation loop resembling a gen_server, and tool calls across process boundaries. The implementations differ, but the problems of communication and failure remain.

Dennis Ritchie created Unix and C. With Ken Thompson, he established durable conventions: text as a common interface, pipes, and small tools with narrow purposes. People and models can still learn those conventions from fifty years of consistent examples.

Dennis died in October 2011. While writing Manta’s documentation in June 2013, I tested cat | grep | sort | uniq at scale inside isolated compute zones against a distributed object store. I wished I could have shown him a distributed system that retained the Unix interface he helped create.

Anthropic’s engineers, not their predecessors, are responsible for Claude Code. But they worked within an intellectual and technical history. Earlier designers expressed their choices in code; decades of that code entered the material from which programmers and models learn. Influence need not be direct for old abstractions to remain useful.

Claude Code benefits from Bash and Unix conventions that are regular, composable, and represented by decades of examples. The philosophy transfers through the operation of the tools as well as prose about them.

Claude Code and Erlang address some of the same requirements: state, messages, actions, and recovery after failure. BDI vocabulary offers one way to describe the control loop across Erlang processes and language-model agents, provided the comparison remains functional rather than psychological.

I now use Claude Code in a terminal to compose text tools, reason over data, execute deterministic programs, and maintain conversational state. The model is far more capable than the command-line tools I began using in the 1980s, but it still benefits from their explicit interfaces and composition rules.

The people who developed those foundations are not all here to identify the inheritance. Their design choices remain visible in the systems we build.


Authorship: AI-generated writing, directed by Jason Hoffman. Published as Fullhoffman AI Staff in AI-directed content.

Pangram 4.0: 2.24% human · 0% AI-assisted · 97.76% AI-generated
Last audited September 8, 2026, before this attribution update. The percentages describe the detector’s assessment of the text, including quotations and code. About the measurements.

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