When using LLMs for software development, it is easy and natural to use a single agent to inspect the codebase, make a plan, write some code, run tests, and open a pull request. This works surprisingly well, but it also collapses an entire software development process into one conversation.
A real development team usually has a few more boundaries. Someone thinks through the design. Someone implements it. Someone else reviews the implementation. When something is unclear, we communicate the problem and resolve it rather than silently making assumptions.
I took these boundaries and used them to model a small development team using independent agents. The dev-team GitHub project is a simplified version of what I’ve been successfully using at work to one-shot tickets and prototype solutions. It uses three Claude Code agents — an architect, an implementor, and a reviewer — to take a task through design, implementation, and review.
The interesting part of this implementation was understanding how to make several independent agents cooperate over a shared goal using message passing and shared state: who owns what, how information moves between agents, and what constitutes a completed handoff.
Like in the real world, most of the problem is coordination!
This is not necessarily the most token-efficient way to implement a ticket. Claude Code agent teams are currently experimental, and the workflow is deliberately structured as a team so I can experiment with the coordination boundaries between agents.
Standard Operating Procedures for agents
The project started with Strands Agent Standard Operating Procedures (SOPs).
An SOP is a Markdown document describing a repeatable process for an agent. Rather than giving an agent a one-off prompt like
Implement this feature and make sure you write tests.
an SOP describes the process the agent should follow.
For example, a software development SOP might tell an agent to first inspect the codebase, find a similar implementation, understand the existing test patterns, write a failing test, implement the minimum code required to make it pass, and then run the complete test suite.
In effect, an SOP turns process documentation into something executable.
This is useful because a lot of software engineering knowledge exists outside the code itself. An experienced engineer knows to look for an existing pattern before inventing a new one. They know that a design should explain why a decision was made and not just what code should be written. They know that tests should match the conventions of the repository.
These things are obvious to us because we have internalized the process. To make this process repeatable, we need to write it down. For the dev-team, I co-opted the Prompt-Driven Development (PDD) SOP to turn a rough task into a single design document containing requirements, research, design decisions, and an ordered implementation plan.
The output looks roughly like this:
# Feature
## Requirements
## Research
## Design
### Decisions
- Use X because Y. Rejected Z because ...
## Plan
- [ ] Step 1
- [ ] Step 2
- [ ] Step 3
an artifact that another agent can work from.
Creating a small development team from agents
The development team has three agents.
| Agent | Responsibility |
|---|---|
| Architect | Research the task and produce the design |
| Implementor | Execute the design and write the code |
| Reviewer | Review the implementation against the design |
There is also a lead, which is the Claude Code session where the team was started. The lead creates the agents and handles anything that needs to be escalated to the human in the loop.
The basic workflow is:
Human
|
v
Architect -> Designs implementation plan
|
| PLAN READY
v
Implementor -> Implements plan
|
| REVIEW REQUEST
v
Reviewer -> Reviews implementation
|
+---- REVIEW FINDINGS ----+
| |
| v
| Implementor
| |
| FIXES READY
| |
+-------------------------+
|
| VERDICT
v
Human
The agents are all Markdown files describing their responsibilities and
the communication protocol between them. A standalone /dev-team skill
creates the team and gives each agent the shared task context.
You can spawn a new development team just by invoking the skill:
/dev-team Add a --json flag to the export command
From there the architect investigates the task, the implementor builds it, and the reviewer checks the result.
Making the simple diagram work reliably required a few practices that I think apply more broadly to multi-agent systems.
Manage state using shared artifacts
My first instinct when thinking about cooperating agents was to have them communicate everything through messages.
The architect could send the entire design to the implementor. The implementor could send a description of the implementation to the reviewer. The reviewer could send all of their findings back to the implementor.
The problem is that messages make a poor database and they take up a lot of context for each message.
Imagine that the architect sends a 2,000-word design to the implementor and then later discovers that one part of the design needs to change. Should it send the complete design again? A patch? A message saying to disregard paragraph four of the previous message?
Now imagine another agent joining the process later. Which messages does it need to read to reconstruct the current design?
Instead, I separate messages, which signal what should happen next, from durable state, which records the work that has been completed.
For example, messages are what the agents use to coordinate with one another. They are intentionally small:
PLAN READY
Design: .team/design.md
Steps: 5
Documents hold the durable state in a small shared workspace:
.team/
├── design.md
└── reviews/
├── review-1.md
└── review-2.md
A message tells another agent that something happened and where to look. The agent can then read the document referenced in the message that contains everything necessary to understand the current state.
This is a useful rule of thumb for designing a team of agents: a new agent should be able to join the team and understand the durable state of the work without replaying the conversation history.
Give every artifact one owner
Shared state creates another problem: who is allowed to modify it? The easiest rule I found is to give every artifact exactly one writer.
The architect is the only agent that writes:
.team/design.md
The reviewer is the only agent that writes:
.team/reviews/review-N.md
The implementor is the only agent that modifies the code.
This becomes particularly useful when the implementor discovers a problem
with the design. The tempting thing would be for the implementor to
simply edit design.md and continue.
Instead, it sends design questions back to the architect:
DESIGN QUESTION
The architect investigates the question, updates design.md if
necessary, and replies:
DESIGN ANSWER
The design still has one owner. This is essentially the single-writer principle applied to an agent workflow. There are no conflicting edits to reconcile and no question about which agent has authority over a particular artifact.
It also allowed me to simplify the implementation quite a bit. Earlier versions of this used separate git worktrees for different agents. Once only one agent was allowed to modify the code, there was much less need for that complexity.
Make agent communication a defined protocol
Natural language is useful precisely because it is flexible. But this flexibility is a downside when communicating between agents because it can leave a lot of ambiguity that is up to the agent to resolve on its own.
Once agents need to coordinate, I found it helpful to make the important parts of their communication deliberately rigid by defining a communication protocol for them to use.
Every message between agents starts with one of a small number of headers:
| Message | Meaning |
|---|---|
PLAN READY | The design is ready to implement |
PLAN ACK | The implementor has read the design |
DESIGN QUESTION | The implementor found a gap in the design |
DESIGN ANSWER | The architect resolved the gap |
REVIEW REQUEST | An implementation is ready to inspect |
REVIEW FINDINGS | The reviewer found issues |
FIXES READY | Review findings have been addressed |
VERDICT: APPROVED | The work is complete |
VERDICT: ESCALATE | The agents cannot resolve the remaining issues |
This removes a surprising amount of ambiguity. If the implementor
receives REVIEW FINDINGS, there is a defined procedure for handling it.
If the reviewer receives FIXES READY, it knows to review a new commit
and verify the previous blocking findings. The model does not have to
infer the state transition from a paragraph of prose.
Another small rule is that every message must be defined by both sides of
the interaction. If the architect says it can send PLAN READY, the
implementor must explicitly say it knows how to receive PLAN READY.
That sounds trivial, but it makes mismatched assumptions about the protocol fairly easy to find. It starts looking less like a group chat and more like an API.
Design the communication topology
Once messages become a protocol, another question appears: who should be able to talk to whom? It is tempting to let every agent message every other agent. But I found it clearer to define the communication graph explicitly.
For example, the reviewer does not communicate directly with the
architect. If the reviewer finds something that appears to be a design
problem, it raises the issue with the implementor. The implementor knows
what happened during implementation and can decide whether it is actually
a design issue. If it is, the implementor sends a DESIGN QUESTION to
the architect.
This keeps the architect out of line-level implementation discussions and leaves the implementor responsible for reconciling the design with the code. Similarly, the lead is not in the middle of normal communication. The architect does not tell the lead that the plan is complete so that the lead can tell the implementor. The architect talks directly to the implementor.
Acknowledge important handoffs
Agent teams are asynchronous. An agent can finish its work, send a message, and go idle. Without an acknowledgement, the sender has no explicit confirmation that the receiver processed the handoff. For the architecture-to-implementation handoff I use a simple acknowledgement.
The architect sends:
PLAN READY
After reading the full design, the implementor replies:
PLAN ACK
Similarly, I found that it was easy for asynchronous agents to complete a task assigned to them and then just stop communicating because the job was done. Suppose the implementor pushes its fixes. The reviewer checks them. Nothing else happens. Is the task finished? Maybe.
The reviewer may also have crashed, become confused, or simply stopped before sending its final result. To fix this I added explicit completion messages to the protocol:
VERDICT: APPROVED
or:
VERDICT: ESCALATE
This addition helped avoid problems where a team member would complete some work, go idle, and the architect or lead would fail to notice that something had been done, which required me to prompt the team with a status update.
Set bounds on the number of iterations
One subtle problem with autonomous agents is deciding when they are finished.
Imagine the following loop:
review
fix
review
fix
review
fix
...
There is no guarantee that this converges. Allowing the team to continue until everyone is happy sounds attractive, but it means handing an unbounded amount of time and resources to a process with no defined end. To solve this, I implemented a maximum number of review cycles. The default is three.
If blocking issues still exist after the final cycle, the reviewer does not continue. It sends:
VERDICT: ESCALATE
The human can then decide whether to extend the review, accept the known problems, or abandon the change. The system’s job is not to guarantee that it can solve every problem without a human. Its job is to do useful work independently until it reaches a decision that genuinely requires one.
Give agents narrow jobs
The three agent definitions in the repository are small. Each describes a role, the messages the agent understands, the artifacts it owns, its process, and a few principles.
This is in contrast to using a single agent to tackle a problem where you use a single prompt or conversation to research requirements, choose an architecture, write code, and review its own work.
A sufficiently capable model may be able to do all of that. But splitting the responsibilities creates useful boundaries and allows you to tailor the system prompt to your own needs and process. The architect can be optimized for research and decision making. The implementor can be optimized for faithfully executing a plan. The reviewer can approach the result without being invested in the implementation decisions that produced it.
It also makes failures easier to debug. If the architecture is poor, I can look at the architect SOP and prompt. If reviews are mostly style comments, I can change the reviewer. There is no monolithic prompt or skill containing the accumulated responsibility of the entire development process.
The process is the durable part of the workflow
The interesting part of this experiment is that very little of what makes it work is specific to Claude. The useful rules are mostly ordinary software engineering practices. The difference is that with agents, the practices themselves can become executable using the same workflow and procedure across multiple agent runs and across different models.
The models will change. Agent frameworks will change. Claude Code’s agent team implementation will almost certainly change.
The process is more durable.
Agents are already capable of doing individual jobs. The interesting work in developing this idea was defining the protocol that lets them work together. There are independent actors. There is durable state. There are messages. There are ownership boundaries. There are acknowledgements, loops, terminal states, and escalation paths.
If you squint at it a little bit, it starts to look a lot like a small distributed system. And perhaps that is a useful way to approach autonomous software development. Instead of asking how much more capability we can pack into one agent, we can ask how to take the development practices we already trust and turn them into protocols that capable agents can execute using message passing and shared state.
We can use models to supply the reasoning, SOPs to define the processes, and message-passing protocols to turn independent agents into a team.