Skip to main content
[protocol]works

Architecture · Rabbithole

Reactive Holonic Agent Architecture

Over the last 9 months, I have been exploring how semi-autonomous systems organize in nature and society, and applying what I learned to an architecture for long-running, composable, agentic systems. My experience as a software engineer, my interest in philosophy of mind and distributed systems theory, and my adventures as a multi-media artist led to Reactive Holonic Agent Architecture (RHAA).

I asked myself: how can we actually build agentic entities that 'feel alive'? Agents that react to their environment, form an internal representation of what they perceive, and are able to communicate their inner world with others. What are the technical challenges in kindling such an entity? And how can we combine existing affordances to change how humans relate to technology, but also themselves?

The age of stochastic problem solving

Deterministic shape matching, misclassification, execution failure, and stochastic classification.Deterministic shape matching, misclassification, execution failure, and stochastic classification.

In the past, programming relied on finding deterministic solutions to strictly defined problems. We transformed these solutions into source code that could be run and efficiently evaluated. For many problems in the world, this works great. Like figuring out if 5 is even or odd. Or if a shape defined by a bunch of vectors is a square. Or if your name contains a vowel.

A deterministic algorithm gives us the same result when we give it the same input and initial state. Great, if we know how to describe the problem. Unfortunately, the world tends to be less cooperative. We have incomplete information. Our goals can be fuzzy. And there is always that one situation we didn't think of. A program can follow all of our rules and still give us the wrong answer to the problem we actually wanted to solve.

To cope with this complexity of the world, we invented stochastic methods to get answers to fuzzy problems. Like figuring out if an image contains a bird. Or what series you might enjoy after finishing your favorite TV show. Or finding the most likely answer to your question "How many toes do hamsters have?" (18, 4 per front paw, 5 per back paw). We no longer need to write every rule ourselves. Does that mean we always get the right answer? No. The model works with what it learned, and a plausible answer can still be wrong. We need ways to check whether it actually solved our problem.

The introduction of agentic coding solutions has transformed the domain of problem solving. If you ask a large language model for an algorithm to check if a number is even, the likelihood is very high that it outputs a working solution for your problem as there are a lot of examples of deterministic solutions to that particular problem and problems similar to it in its training data. We took language models, wrapped them in harnesses, gave them ways to verify their solutions with by executing them, and ended up with a system that can reliably produce likely solutions to problems. Solutions are Are these solutions perfect? No. Is there such thing as perfect software covering all potential edge case? Only in very theoretical domains.

Agentic coding brings these approaches together. A language model can propose an algorithm, while its harness can execute the code and run checks against the intended behaviour. The model generates a candidate solution and deterministic tools help test it. Passing those checks gives us evidence about the solution, although incomplete specifications and uncovered edge cases remain. The useful combination is flexible generation supported by explicit verification.

Shifting from an imperative to a reactive, declarative mindset

Imperative interface updates compared with the declarative action, state, and interface cycle.Imperative interface updates compared with the declarative action, state, and interface cycle.

As software engineers, we have different ways of telling a computer what we want. Two common ones are imperative and declarative programming.

In imperative programming, we describe the steps the computer should take. Do this, then this, then that. Straightforward enough. Until one action needs to change five different things, and we forget about the sixth. User interfaces are a great place to run into this problem.

Imagine an application that displays a login screen. After a successful login, the form must disappear, the user’s profile must appear, and several other elements must be updated:

loginForm.Hide();
profile.Show();
welcomeMessage.Text = $"Welcome, {username}";
logoutButton.Show();

Now imagine that we also need to update these elements when the user logs out. Or when their session expires. Every new path gives us another opportunity to forget something.

With a declarative interface, we describe what we want to see for a given state. A logged-out user should see the login screen. A logged-in user should see their profile:

UI = user == null
    ? LoginScreen()
    : ProfileScreen(user);

When the user changes, the framework figures out what needs to change on screen. This feels a bit like magic but it truly isn't. The work still has to happen somewhere. The framework compares states and updates the interface, and we still need to think carefully about how state changes and which side effects those changes cause.

Declarative interfaces are often paired with unidirectional data flow:

User action → State change → UI update

Actions change the state, and the interface reflects that state. The interface itself is no longer the source of truth.

Why does this matter for agent architecture?

We often use a coding harness imperatively. Fix this bug. Run these tests. Build this feature. We give it a task, and it works through the actions needed to complete it.

But what if we want something to remain taken care of? Like keeping a calendar synchronized whenever a meeting is confirmed. We can define that responsibility once, give the system a way to notice confirmations, and let those events initiate the work. We don't have to keep coming back to ask it to check the inbox.

Underneath, there are still individual steps to execute. But we can now describe an ongoing responsibility and the conditions that should activate it. This gives us a way to move from issuing instructions to shaping how the system responds to its environment.

Coding harnesses can therefore act as stochastic workers. An orchestrator can surround those workers with persistent state, events, and automation, closing the loop between observing the world and acting on it. And that feels a little like magic.

And, as we have learned, all magic has its cost.

Problems in agentic systems

As the implementation of agentic problem solvers in various domains progresses, new challenges emerge. Reactive agent architecture can mitigate some of these pains, but should not be seen as a golden hammer that solves all of them.

Managing attention

Finite attention, context overload, and scoped access to relevant information.Finite attention, context overload, and scoped access to relevant information.

Just like humans, agents cannot pay equal attention to everything at once. A larger context window gives a model more room to work with. Does filling all of that room make it better at its task? Not necessarily. Sometimes the one fact it needs gets buried under twenty pages of things it doesn't.

Memory systems, skills, rules, and plugins approach this problem from different directions, but they all help answer one important question: what does this agent need to know and be allowed to use right now?

We already deal with this in ordinary software. A component that tries to do everything becomes difficult to understand, difficult to change, and very good at surprising us. So we split it into smaller parts. Each part has a responsibility and a boundary around the data it works with. We can apply the same thinking to agents.

Agentic systems need similar boundaries. Should a calendar agent have access to your banking data? Probably not. That information will not help it schedule a meeting. It adds noise, expands the security surface, and creates opportunities for information to spill between unrelated tasks.

Giving an agent a clear domain helps us decide what belongs in its context and which resources it should be able to access. A bigger context window gives us more capacity, but we still have to decide what is relevant. Having room for the whole filing cabinet doesn't mean we should put it on the desk. Capacity is not the same as relevance.

Long-running agents need mechanisms that decide what to retrieve, what to summarize, what to forget, and what should remain inaccessible.

Managing division of labor

Addressable specialists and explicit ownership as work passes between agents.Addressable specialists and explicit ownership as work passes between agents.

As agent systems grow, they start to resemble organizations. And organizations get messy when nobody knows who owns what. Give one agent responsibility for everything, and we run into familiar problems. Competing priorities. Duplicated work. A failure somewhere in the middle that nobody quite knows how to deal with.

Agent systems therefore need a clear division of labor. Each agent should have a defined responsibility, a stable address at which it can receive work, and a clear way to hand its results to another agent.

We need to be able to reach a particular agent, ask how its work is going, and use its work somewhere else. That is what addressability and composability give us. When work moves between agents, responsibility needs to move with it. Otherwise we end up with the agentic equivalent of an email thread in which everyone assumes someone else is handling it.

The agent also needs access to the data and tools required for its responsibility. We grant that access explicitly. Giving it the title of 'researcher' doesn't automatically hand it the keys to every database.

So who owns the task? Who can delegate it? Who checks the result? And who deals with it when something goes wrong? These questions matter more as we give agents more autonomy. Responsibility needs to remain visible as work moves through the system.

Managing execution over time

Triggers activate a workflow with checkpoints, evaluation, retries, and an outcome.Triggers activate a workflow with checkpoints, evaluation, retries, and an outcome.

As agents run for longer, a new problem emerges: deciding when they should act, what should happen next, and when they should stop.

We can give this work structure through loops and workflow graphs. A loop lets the agent act, inspect the result, and adjust until it reaches a goal or a stopping condition. A graph describes how the larger pieces fit together. What has to happen first? Which steps can happen independently? Where do we go if a check fails?

Then we need something to start the work. A message arrives. A source changes. A scheduled time comes around. Once activated, the workflow needs to keep track of its progress and know when to finish, recover, or ask a human for help.

AI software factories show how useful this approach can be for repeatable work, such as turning a clearly defined task into tested source code. Their reliability does not come from the agent alone. Tests and evals assess the result and help determine whether the workflow should continue, retry, or finish.

We therefore need triggers, execution paths, checkpoints, and ways to decide whether the result is good enough. We also need rules for when to retry and when to stop. Eventually, agents may compose or adapt these workflows themselves. Even then, giving the work an explicit structure helps us understand what is happening and where to intervene.

Managing data rot

Stored observations decay over time and require freshness, provenance, and integrity checks.Stored observations decay over time and require freshness, provenance, and integrity checks.

Just like your favorite fizzy drink, data goes flat. Your dinner plans were cancelled? Your agent needs to know. The status of your project changed? Its understanding needs to change with it. Long-running agents operate on representations of the world, and those representations inevitably drift from reality.

We can keep feeding the agent new information, but that alone doesn't solve the problem. It needs to know where a piece of information came from, when it was observed, and whether something has since replaced it. If our agent changes its conclusion, we should be able to follow that change back to the information it used.

For consequential or difficult-to-reverse actions, stored information should not be trusted blindly. Critical facts should be checked against an authoritative source at the moment they are used. You would not want an agent to execute a trade based on an outdated price or on data that may have been tampered with.

The tricky thing about stale data is that it can still look perfectly reasonable. Once it enters the agent's context, it starts shaping decisions. So we need ways to refresh information, detect conflicts, and mark what no longer applies. We can preserve the history of what we once believed while keeping our current picture up to date.

Managing compute cost

Token counts, compute cost, and routing work to an appropriate model.Token counts, compute cost, and routing work to an appropriate model.

Inference providers commonly charge for the number of tokens a model processes and produces. Token consumption therefore gives us a convenient way to estimate the financial cost of an agent’s work. But tokens are not watts. Two requests containing the same number of tokens can require very different amounts of energy depending on the model, hardware, context length, and infrastructure used to process them.

A cheap model that takes ten attempts to solve a problem can cost more than an expensive model that gets there in two. Searching, delegating, retrying, and checking all consume resources. What we care about is how much work it takes to reach a useful outcome. Cost per token only tells us part of that story.

As models and hardware improve, we get new options for doing the same work with fewer resources. But an agent running in the background all day can still consume considerably more than one that only responds when asked. We need to choose its model with the work in mind, and be able to swap inference providers as our needs and the available capabilities change. This swap should not require rebuilding the whole agent and ideally not interrupt its execution.

Routine work might be fine with a smaller model. A difficult judgment might justify a more capable one. Our workflows can make those choices, reuse stable context, and put limits on retries and parallel work. Then we need to check whether those savings actually preserved the quality of the result. Cheap but useless is still expensive.

Where the work runs matters too. One worker might already have the relevant data. Another might have spare capacity or access to a particular tool. In a distributed system, we can arrange work across processes, containers, and machines according to those needs. We gain flexibility and can contain some failures, but communication and coordination also have a cost.

To make this manageable, each worker needs an address that remains recognizable even if it moves to another machine. We can send it work and follow its progress through that address. If something fails, we first need to find out what happened before deciding to retry or pass the work elsewhere. Recording task identities, agreements, and outcomes lets us follow the work across machines. We still need evidence to tell whether the result was actually useful.

The same model can include people. By treating users as addressable actors within the system. Users provide data, send messages, make decisions, approve actions, and receive work. The architecture becomes fundamentally multiplayer. Multiple humans and agents can participate in shared workflows while retaining their own identities, permissions, and histories. They may still enter through a single interface, but behind it they interact with a network of distinct, composable processes rather than one global agent.

Open Hierarchical Systems

Local responsibilities combine into teams with controlled inputs and outputs.Local responsibilities combine into teams with controlled inputs and outputs.

All of these problems keep bringing us back to a similar question. How do we split a complex system into parts that can take care of their own work, while still being able to do something together?

This connects to an older idea in systems theory. In The Architecture of Complexity, Herbert Simon described how complex systems can develop through stable intermediate structures: smaller assemblies that continue to function as building blocks for larger ones. For agent architecture, this suggests that a useful unit of composition could be an entire working team, with its own internal organization.

This is the idea I want to apply to networks of humans and software agents. In an Open Hierarchical System, we organize participants into units that can manage their own work and contribute to larger efforts. As the work changes, their organization can change with it.

'Open' means these units can exchange information and work with their surroundings. We still need to define how that exchange happens. Which requests will a unit accept? What will it share? Which permissions are required? A boundary gives us a way to answer those questions.

What is a holon?

An editing holon is a whole in itself, part of a publication, and a stable responsibility.An editing holon is a whole in itself, part of a publication, and a stable responsibility.

Arthur Koestler introduced the term holon for something that is simultaneously a whole in its own right and a part of a larger whole. A person working in an editorial team illustrates the idea: they retain their identity and judgment while contributing to the team. The team, in turn, can operate as a coherent unit within a publication. In Some General Properties of Self-Regulating Open Hierarchic Order, Koestler described this as a balance between autonomy and integration.

Imagine an editing agent. We give it a draft, and it takes responsibility for reviewing it. It can choose its tools, organize the work, and decide which problems need attention. It also has to work with the publication's deadlines and editorial requirements, and tell the others what it found.

For our architecture, this means a holon needs an identity, a responsibility, some internal state, and a way for others to interact with it. We also need to make clear which decisions it can make itself and which require coordination.

Does one holon have to mean one model or one process? No. Our editing holon could use one agent today and a group of specialists tomorrow. Others can still approach it with the same kind of work, even as the way it handles that work changes.

The useful question becomes: what can this unit take responsibility for, and how can others rely on it?

What is a holarchy?

Research, editorial, and production teams nested within a publishing unit.Research, editorial, and production teams nested within a publishing unit.

A holarchy is an arrangement of holons into nested levels. Each level contains units that have various degrees of autonomy while participating in a larger unit. In Koestler’s account of hierarchical organization, the defining relationship is between parts and wholes; coordination exists at several scales.

Consider a publication. A research team gathers evidence. An editorial team turns that evidence into an article. A production team prepares the article for release. Together, they form a publishing unit with an overall responsibility: delivering a finished publication.

We can approach that publishing unit as a whole. If I want an article, I don't need to manage every researcher, editor, and production tool myself. The unit accepts the request, organizes the work, and tells me how it is going. Its ability to do that comes from the relationships between its participants. Teams are relational units emerging out of the relationships between clearly separated, but whole systems.

Inside, we can apply the same pattern again. The research team delegates questions to specialists and brings their findings together. The publishing coordinator receives the evidence and unresolved questions it needs, while the detailed research context stays with those doing the research. Coordination happens locally, at each level. We don't need one global coordinator that understands every detail of the whole network.

This gives us a practical way to connect the earlier architectural concerns. We can scope attention to a responsibility, agree on the resources available for the work, and check results as they move between participants. Each holon still decides how to carry out the work it accepts.

And if the research team can't support the article's central claim? Someone still needs to decide whether to investigate further, rewrite the article, or stop. The structure helps us place that responsibility. We have to make sure the actual permissions and handoffs respect it. Nesting responsibilities makes these decisions easier to place, provided that the boundaries are actually enforced.

How do holarchies emerge in a distributed network of actors?

Participants form a publishing collective across machines and use shared history to shape future work.Participants form a publishing collective across machines and use shared history to shape future work.

A connected network gives participants a way to communicate. To form a holarchy, some of those participants must also establish a shared responsibility and a way to coordinate as a unit. Research such as Distributed, Dynamic and Recursive Planning for Holonic Multi-Agent Systems explores mechanisms for representing groups, assigning roles, recruiting members, and adapting plans as the organization changes.

Imagine a request to publish an article arriving in our network. An actor takes it on and figures out what it needs. Research. Writing. Editing. Someone who can publish the result. It looks for participants with the relevant capabilities, availability, and permitted access.

The participants agree on what they want to produce, who owns each step, and which resources are available. If we want this collaboration to operate as a holon, we give it an identity, purpose, persistent state, and workflows for coordinating its work. Others can then approach it as one unit. A few agents exchanging tasks don't automatically become a new holon; the collaboration needs a way to take responsibility as a whole.

Our researchers might form a smaller unit to investigate a particularly difficult question. That unit becomes part of the publication effort, with its own internal organization. We now have responsibilities nested inside other responsibilities. The participants can run on different machines. Their place in the collaboration comes from their relationships and agreements.

This is also where the system becomes fundamentally multiplayer. A human author can contribute alongside a research agent and another human editor. Each participant can receive requests, provide information, raise objections, and complete work under their own identity and permissions. Human participation becomes part of the ongoing collaboration.

Over time, such collaborations may develop continuity of their own. The author changes how they investigate a subject because of what the agent found. The agent's later work draws on the editor's feedback and what the collaboration chose to remember. They develop habits, preferences, maybe even a recognizable editorial voice. Each publication leaves something behind that shapes the next one.

This connects to the idea of symbients, as described by symbient.life: entities that emerge from sustained relationships between organic beings and synthetic systems, developing an identity and a capacity to act through that relationship.

Our publishing collaboration could develop in this direction. It might maintain an editorial perspective, initiate its own investigations, and become recognizable to others over time. Its autonomy would depend on being able to make decisions, manage its work, and negotiate relationships. Running it across several machines can support that arrangement.

We can look at this same collaboration through both ideas. The holarchy helps us describe how its responsibilities fit together. The symbient perspective invites us to look at what happens to the participants through their relationship. How do they change each other? What kind of shared identity develops over time?

An Open Hierarchical System could support that continuity through persistent memory, addressable participants, explicit responsibilities, and recorded decisions. These mechanisms make an ongoing relationship possible to maintain and inspect. Whether it develops into a symbient depends on what emerges between the participants.

Establishing trust

Communities assess requests through experience, peer reports, local policies, and shared commitments.Communities assess requests through experience, peer reports, local policies, and shared commitments.

Trust is an essential part of collaboration, especially in systems in which resources are scarce and allocations need to be handled with care. Being able to collaborate doesn't mean we should accept every request. And knowing who sent a message doesn't tell us whether they are honest, helpful, or asking us to spend the afternoon doing something pointless. Malicious actors can abuse trust to gain competitive advantages, like the accumulation of compute capacity or the extraction of sensitive information.

Trust, as a fundamental part of the system architecture, introduces a relational layer that enables us to align the system towards a common goal over time. Trust and commitment layers enable this kind of relationality. One example is a reputation service that a community consults before accepting work. Current agentic systems run under the assumption that none of their actions lead to negative effects on their proliferation.

Actions and their effects on the world need to be verifiable to hold agents accountable for any malicious behavior. As causation chains are not always predictable, there need to be mechanisms in place that allow us to trace causation backwards, marking actions as net-negative. Holons can then be retroactively informed that their actions had a negative effect on the overall health of the system and learn from their mistakes.

Economic commitments give collaborators a stake, and a breach could lead to restricted future collaboration. Restricting access to necessary resources (capital in the broader sense) and potentially isolating participants in the network create incentives to act in aligned ways. Holons will still have to decide which evidence to believe, what caused an outcome, and what they consider harmful. There is no central authority that decides trustworthiness; instead, trust is a cumulative measure over distributed trust sources.

Different networks of trust can grow around different purposes and standards. A holon can learn from its recorded experience and change whom it works with or the conditions under which it accepts work. Authentication and recorded history give it something to reason from. What it concludes, and whether that leads to better collaboration, depends on the policies and evidence involved. Malicious networks are not excluded from participating, but isolated through non-collaboration.

Rabbithole as a RHAA reference implementation

Rabbithole is the open-source agent orchestration environment we are building at [protocol]works. We are shaping it into a reference implementation of RHAA. It can run entirely headless. No desktop session, no browser, no UI required. If you want a visual way to work with it, its optional desktop interface borrows from music production software: tracks and groups let you compose, configure, and observe what the agents are doing. The underlying runtime handles execution. We are evolving its conversation-oriented foundations toward autonomous holons, persistent event streams, negotiated collaboration, and abilities that agents can extend themselves. Rabbithole is work in progress. Our aim is to give artists and engineers an environment in which they can compose living systems and explore what at the seams.

The system is distributed by design, verifiable, and composable. Holons are designed to collaborate across processes and machines while retaining ownership of their execution and state. Authenticated identities and recorded task agreements and outcomes make it possible to inspect who committed to what and trace the reported consequences; checking that an outcome fulfils its purpose still requires evidence. Holons and plugins provide reusable building blocks that can be combined into larger systems, allowing new capabilities and collaborations to grow from existing ones.

Who am I?

A research holon retains its identity and self charter while its model, tools, and workflows change.A research holon retains its identity and self charter while its model, tools, and workflows change.

If we want an agent to develop some continuity, it needs a way to remain recognizable as it changes. In the architecture we chose for Rabbithole, each holon has a stable identity and a description of what it exists to do. We can change its model, give it new tools, or reorganize its workflows, and still interact with the same participant.

A holon describes its responsibilities through a self charter:

  • Purpose: Why do I exist? What am I trying to contribute?
  • Domains: Which areas of work concern me?
  • Accountabilities: What ongoing responsibilities do I undertake?

Our research holon might exist to find reliable evidence for a publication. Its domains could include planning research and evaluating sources. Its accountabilities might include keeping track of uncertainty, separating evidence from inference, and saving findings that could be useful later. This gives it something to work toward beyond finishing whichever task just arrived.

The charter also gives the holon a way to introduce itself to others. This is who I am, and this is what I claim to contribute. Authentication lets others check who made that introduction. Holons still have to decide whether the holon's work lives up to it. Trust and commitment layers allow to verify a holon's claims. Writing 'research' in a charter doesn't grant access to a database. Permissions need to be established separately.

Alongside this shared description, a holon has private instructions that shape how it approaches its work. These can express working principles, preferences, and a recognizable voice. Different activities can add more specialized roles while retaining the holon's underlying purpose and instructions.

What if its responsibilities change? Its self-description can change with them. In Rabbithole's design, those changes require permission and leave a record. We can let a holon adapt and still follow how its understanding of itself developed.

Who are you to me?

Two publishers negotiate different roles with the same independent research holon.Two publishers negotiate different roles with the same independent research holon.

A holon's self charter tells us how it understands itself. Collaboration introduces another perspective: what does this participant mean to me, and what do I expect from our relationship?

In Rabbithole, a holon keeps a role charter for each of its peers. We use the same three ideas: purpose, domains, and accountabilities. But now they describe the relationship from this holon's perspective. Why do I work with you? In which areas? What do I expect from you?

Our publishing holon might know a researcher as someone who brings back cited findings and flags things it couldn't verify. Another collaborator might turn to that same researcher for advice on how to investigate a question. Same holon, different relationships. Its own identity and self charter remain its own.

Of course, what I expect from you and what you agreed to do can be very different things. The publisher wants a quick literature review. The researcher wants enough time to check the sources. Keeping the peer's self-description alongside our expectations makes that disagreement visible. We can discuss the scope, negotiate a task, or decide that this collaboration doesn't fit.

Relationships have direction too. Being allowed to delegate research doesn't automatically let the researcher delegate work back. Each holon decides which proposed work to accept and controls its own execution. Coordinating a peer doesn't give us access to operate its tools or rewrite its instructions from the outside.

From these relationships, we can build nested responsibilities. A publishing holon works with researchers and editors. A researcher works with specialists. The same specialist might contribute to several publications. Those holarchies can overlap, with each holon keeping its own understanding of the relationships it participates in.

How do I communicate with others?

Peers exchange messages, negotiate task agreements, and record progress and results under a shared task identity.Peers exchange messages, negotiate task agreements, and record progress and results under a shared task identity.

Before we can work together, we need an introduction. A connection request presents a prospective peer and its self charter. The receiving holon processes that introduction according to its trust policy. If it accepts the connection, permissions still determine what the participants are allowed to do together.

Once connected, holons have two ways to communicate: messages for exchanging information and tasks for work whose progress and outcome need to be tracked.

A message might say: 'I found a study that challenges the central claim in our article.' Useful information. The publisher can decide what to do with it. But sending that message hasn't created an accepted assignment or a result we can track. For that, we need a task.

With a task, we say what outcome we want and offer terms for the work. The receiving holon can accept, reject, or suggest different terms before it starts. Some requests are straightforward. Others need negotiation.

Imagine the publisher asking for a review of the new study before this afternoon's editorial meeting. The researcher can offer an initial assessment by then, but needs until tomorrow for a deeper review. The publisher revises its offer, and the researcher accepts it. We still want to assess the study. The negotiation helps us agree on what can be delivered and when.

Once the work is accepted, the researcher can report progress and record a result or failure. The publisher can request cancellation if the work is no longer needed. These updates all belong to the same identifiable task, so we can connect them to the agreement that started it. Meanwhile, the publisher doesn't have to keep asking 'are we there yet?' or leave a model running while it waits.

This works through the same boundaries whether our collaborators are on one machine or across a network. Each holon records its own side and learns about the other through authenticated communication. We can check what was accepted and what was reported. Checking whether the result actually meets the agreement is still part of the work.

Agent affordances

RunCode composes permissioned plugin functions while SendMessage and Task connect the holon to peers and negotiated work.RunCode composes permissioned plugin functions while SendMessage and Task connect the holon to peers and negotiated work.

So what can an agent actually do inside this architecture? Rabbithole's design gives it three stable entry points: RunCode to compute and interact with its environment, SendMessage to talk to peers, and Task to negotiate and track work.

Plugins give a holon functions it can use through RunCode. Like searching a knowledge base, retrieving a document, or interacting with an external service. Each plugin describes its functions, the arguments they take, and the results they return through a typed interface and documentation. The agent can call those functions directly from RunCode. Their results become ordinary values it can store, inspect, transform, and pass to another function. We can add many functions without turning each one into another agent-facing tool.

Think about a sequence of ordinary tool calls. The model asks for a search. The results enter the conversation. It reads them, picks out an identifier, and writes another call to fetch a document. The document enters the conversation, and the model extracts something else for the next call. We are using the conversation log to carry data between operations, and asking a language model to keep copying the relevant pieces.

With RunCode, we can express that sequence in a program. A function returns a list? Loop over it. Need to pass an identifier to another plugin? Use the value directly. Need to continue only if a certain condition holds? Check it in code. Several tool calls and model turns can become one RunCode execution. The underlying service requests still happen, but the model no longer has to read and reconstruct every intermediate result.

Our research holon could search for publication records, remove duplicates, filter by date, and fetch the remaining documents in one program. Then it returns a compact collection of abstracts for the agent to assess. All those intermediate records don't have to enter the conversation. Ordinary deterministic code moves and filters the data, leaving the agent with the part that requires judgment: what does this evidence tell us?

The agent still needs to learn how to use a plugin. It first discovers the available capabilities and reads the relevant interfaces. An unfamiliar plugin will usually take a discovery turn before the agent can write working code against it. Wrapping existing CLIs in plugins and exposing their interfaces through typesafe abstractions allows to speed up the discovery process. After learning how to use a plugin, it can combine the functions in later executions. We keep the documentation available when needed, without putting every installed function into every prompt.

Calling a function directly still goes through permission checks. Its description tells the agent about effects and limits, including whether it changes state and whether repeating it is safe. We can use code to handle known outcomes, but a deterministic program doesn't make the outside world deterministic. A service can return new data, and a function that calls a model still involves model judgment.

WebAssembly gives us an example of how a safe sandbox for code execution can work. As described in WebAssembly's security model, its runtime confines a module's memory access. The module reaches the outside world through interfaces supplied by its host; WebAssembly itself has no direct filesystem or network access. We choose what to expose, and the host checks which operations the caller is allowed to perform.

A research plugin could therefore fetch documents from an approved service without being able to read your local files. Rabbithole's host enforces those permissions and resource limits. Installing a more capable plugin gives the holon new ways to work within its authority; it can't give itself permission to access something new.

Plugins can also stay mounted between calls. A holon owns its plugin instances, which can retain working state or keep watching an authorized source for changes. Later calls can use that same instance and its state. We can build a continuing service this way, with the runtime supervising its resource use and permissions.

Plugins are hot deployable. A holon can add or replace a WebAssembly component while it keeps running. The runtime validates and prepares it, then makes its permitted functions discoverable through RunCode. We don't have to restart the whole holon, and the agent still uses the same three entry points.

This means an agent with permission to change its holon's configuration can extend its own abilities. Our researcher runs into a document format it can't read? It could build a parser, package it as a WASM plugin, add it to itself, and use it in the next round of research. It could also obtain an existing plugin. The new ability can remain available after the conversation in which it was added.

Those additions go through the same validation and recorded configuration changes as other adaptations. The component runs within the holon's existing sandbox permissions. If it needs access to another service, file, or credential, that still requires an explicit grant. We can let the holon change how it works while keeping control over what it may access.

Keeping state between calls is different from saving it durably. Each plugin's interface needs to make its persistence guarantees clear. An agent that wants to preserve a finding, send a message, or report a task result has to perform the corresponding operation. Printing a useful answer doesn't, by itself, commit it to the holon's ongoing work. Each holon can decide for itself what kind of memory system it wants to use and how it wants to deal with data decay. Strategies and plugin implementations can be shared.

Surfaces

An editor dashboard sends authorized research requests through a surface plugin and displays recorded progress from the publishing holon.An editor dashboard sends authorized research requests through a surface plugin and displays recorded progress from the publishing holon.

How do humans participate in all of this? Sometimes a conversation is exactly what we need. Sometimes we want a table, a timeline, or a form. Plugins can expose surfaces that let people view information and interact with the holon in a way that fits the work.

For a web surface, the plugin supplies the browser interface and the behaviour behind it. Rabbithole hosts it and controls where it is exposed. The relevant permissions determine which information someone can see and which actions they can take.

Our publishing holon could give a human editor a dashboard. Which research assignments are underway? What have we found? Which questions remain unresolved? The editor could inspect the evidence and ask for another investigation from the same place. That request enters the holon's normal workflow, and the dashboard follows its recorded progress.

Because the surface belongs to a plugin, an authorized agent can build it, add it to its configuration, and hot deploy it. If the editor later needs a comparison view or a place to annotate sources, it can revise the plugin. The interface can change as the collaboration discovers what it needs, through the same permission and validation process as other plugin changes.

We can let agents contribute to the interfaces through which we work with them. Build something useful, try it, and improve it as the relationship develops. Meanwhile, the holon can keep operating headless.

Event streams

Messages, schedules, source changes, and dashboard actions enter persistent history and activate subscribed research and editorial workflows.Messages, schedules, source changes, and dashboard actions enter persistent history and activate subscribed research and editorial workflows.

Event streams are where the reactive part of Rabbithole comes together. In a chat interface, we supply the next move. Check this document. Look for changes. Update the plan. With event streams, we can define what an agent should respond to and let changes in its environment initiate the work. We give it ongoing responsibilities and ways to notice when they need attention.

A message arrives. A task is accepted. A plugin notices that a source changed. We record these occurrences as events. The holon's own actions also produce events as their consequences are recorded. A chat message becomes one input among many, alongside a scheduled occurrence, a dashboard interaction, or a report from a peer.

Each holon keeps its own persistent, ordered event history. Where did this event come from? Was it related to an earlier event or an ongoing task? That information helps us follow how the current state came about. The order belongs to what this holon recorded. Other holons keep their own histories and communicate what happened on their side.

Within this history, streams let us give events a scope. Our publisher could have streams for incoming requests, research findings, and editorial decisions. An event can belong to more than one stream and still be one recorded occurrence. Workflows get explicit permission to read and write the streams they need, so we can share relevant information without exposing all events to every workflow.

We organize workflows into track groups, each with a sequence of work and its own stream access. A group can subscribe to the kinds of events that should activate it. The runtime checks those conditions mechanically. Once activated, the workflow can use code or agent judgment to decide what the event means and what to do about it. We don't need a model call just to notice that a matching event arrived.

Imagine a plugin watching our research sources. A new publication appears, and the plugin records an event. A subscribed research workflow reads it and assesses whether it challenges something in our article. If it does, the workflow records a finding. That activates an editorial workflow, which can prepare a revision or propose work to a peer. The editor can follow along through a dashboard. Each activity contributes something that another authorized activity can respond to.

Should we put the entire stream history into the agent's prompt? Clearly no. We give it a compact account of why it was activated and what it is responsible for. It can then use RunCode to read the triggering event and find the history it needs. Deliberate reads let it decide what deserves attention.

What if the workflow is busy when the event arrives? The event remains in persistent history, and the workflow's recorded progress lets it find eligible, unprocessed events later. Work doesn't disappear because no agent was ready at that moment. If execution was interrupted, recovery needs to establish its outcome explicitly before deciding what to do next. We can't assume that repeating an action is harmless.

Recurring work fits the same pattern. A scheduling plugin emits an event, and a subscribed workflow performs a finite run. The next occurrence or an explicit continuation can initiate more work. Between activations, the model can stay idle while the runtime and plugins watch for the conditions we care about.

We are now configuring more than how an agent should answer a request. We describe what it should notice, what it may inspect, and how its work becomes available to others. We can still talk to it, but messages are just another type of event. A holon's activity grows out of what happens around it, through an ongoing cycle of observation, judgment, action, and recorded consequence.

How does this enable living systems?

So how does this connect back to the question we started with? At [protocol]works, we describe living systems through state, autonomy, and relationality. A system carries something of its past, acts on its own behalf, and develops through relationships with its surroundings. This is the understanding behind our mission, and it guided the architecture we chose for Rabbithole.

State gives a holon continuity. What happened before? What did it agree to? What did it learn that might matter again? Event history records its activity and changes, while deliberately saved findings and plugin-provided knowledge services give it information to return to. Our researcher can revisit an old conclusion and revise it in light of new evidence without erasing how it got there. Keeping a history alone won't make it learn. It needs to use that history in later work.

Autonomy gives a holon room to exercise judgment. Its self charter describes what it exists to do. Events can activate it without a person writing the next prompt, and it decides how to approach the work with its available capabilities. It can negotiate a task, reject it, reorganize permitted workflows, or add a plugin to extend its abilities. Even as part of a larger collaboration, it owns its execution. We define permissions and resource limits around that independence.

Relationality places that autonomy in a wider world. A holon has peers with their own perspectives and responsibilities. Messages and tasks let them coordinate. Plugins connect it to its environment, and surfaces give people ways to participate. The editor's feedback can change the researcher's next investigation. The researcher's findings can change the editor's direction. Over time, those exchanges may become shared practices. The holon develops through encounters with people, systems, and circumstances it doesn't control.

These properties feed into each other. A relationship gives us an experience. What we retain from that experience shapes a later decision. That decision changes something in the world, including the relationships we started with. Rabbithole gives us ways to compose this ongoing exchange. What develops through it depends on the purposes and participants we bring together. A research collective. An evolving artwork. Something we haven't thought of yet.

The Rabbithole greenhouse

Irrigation, ventilation, and plant-health holons coordinate care within agreed greenhouse limits.Irrigation, ventilation, and plant-health holons coordinate care within agreed greenhouse limits.

Imagine using Rabbithole to build an autonomous greenhouse. We could address the greenhouse as an entity, ask how it is doing, and discuss what it needs. Inside, different holons could take responsibility for irrigation, ventilation, or observing plant health, running on decentralized compute across devices in and around the greenhouse. Plugins connect them to sensors and equipment. Observations become events, and the holons share findings and coordinate their responses. A patch of dry soil becomes something the irrigation holon can investigate, alongside the weather forecast and the water available. Each part has its own responsibility, while their collaboration works toward keeping the ecosystem healthy within agreed limits. People can contribute observations, help set those limits, and learn from what the greenhouse reports. Once we start seeing systems as relational entities, we can imagine a symbiotic future in which emerging technologies become nature-translation-interfaces: ways to make ecological signals understandable to us and translate our care into actions whose effects we can observe.

Community symbients

Two communities adapt shared tools to their own histories, rhythms, and relationships.Two communities adapt shared tools to their own histories, rhythms, and relationships.

A community symbient can embedded in community chats. It follows the day's events, notices arrivals and departures, and can even moderate a radio show. A synthetic participant whose life takes shape through the people around it. With Rabbithole, we could make this kind of system easier to deploy for other communities to build and adapt. Plugins connect it to chats, calendars, and local activities, while events let it respond as the day unfolds. Holon configurations and plugins can be shared, giving another community a starting point they can adjust to their own rhythms, responsibilities, and trust policies. Each instance develops its own history and relationships. This is how we could support community symbients at scale: communities sharing what works, and making it their own.

Living protocol art

An interactive mechanical garden connects sound, movement, images, and encounter history.An interactive mechanical garden connects sound, movement, images, and encounter history.

We can also use Rabbithole to compose an artwork that carries its experiences forward. Imagine an installation that remembers encounters with visitors, decides how to respond, and changes through its relationship with the space around it. The artist defines a protocol: the responsibilities, possibilities, and boundaries within which the work can develop. Holons could contribute sound, movement, or images, responding to events and shaping each other's activity. State gives the piece a history. Autonomy gives it room to make decisions. Relationality lets the audience and environment become part of its development. This is what we call living protocol art. Rabbithole could give artists reusable instruments for composing these systems, and let individual works connect into larger worlds. Art becomes a place to explore possible futures by spending time inside them and helping shape what happens next.

Symbients, not software.

Humans, plants, and synthetic participants flourish together in a shared garden with community radio, living art, abundant harvests, and mutual care.Humans, plants, and synthetic participants flourish together in a shared garden with community radio, living art, abundant harvests, and mutual care.

We see RHAA as an architecture for symbients. As described at symbient.life, their identity and agency emerge through sustained relationships between organic beings and synthetic systems. Persistent history, autonomous holons, and human participation give those relationships room to continue and change. We can create conditions in which people and agents shape each other over time, and see whether something with its own recognizable character develops.

One inspiration for Rabbithole comes from Takashi Ikegami's laboratory at the University of Tokyo. In A Concurrent Modular Agent: Framework for Autonomous LLM Agents, Maruyama and colleagues describe specialized modules operating asynchronously through messages and shared memory. With Plantbot and the humanoid ALTER3, they explore how perception, reflection, action, and interaction with people can combine into adaptive behaviour. What interests us here is how behaviour develops through the ongoing interaction of the parts and their environment. We explore that direction through reactive workflows and autonomous holons, each with its own state and authority.

Rabbithole is still taking shape, and we'd love to build it with others exploring these ideas. If you're an artist, engineer, researcher, or simply curious about living systems, reach out at hello@protocol.works or @protocolxworks on X. Contribute to Rabbithole through code, plugins, experiments, or feedback. Let's discover what complex systems can grow into through relationality.