AgentRite - roles and sandbox
I've been trying to automate my software development process as much as possible. The more I play around, the more I feel like I'm the limiting factor in the equation.
So I started making a set of tools to give up control over the steps while keeping control over the outcome:
- A description of the process
- A set of roles agents can assume
- A sandbox where agents can run freely, but in a controlled environment
And I'm planning on adding an orchestration layer to it as well.
I'm trying to model the roles the way I believe human teams should work. For example, the reviewer comes from a different model family, just as you'd ask a colleague to review your code rather than only doing it yourself. The process defines lots of written output, to improve traceability of decisions and to guide the agents. The goal is to create reliable software more quickly, without compromising on quality.
Until I see this working on heavy-load systems, with actual users who can wreak havoc, I'll be skeptical and I won't claim anything about the success of this project. But I want to give it an honest shot, because I do believe we should stop clinging to the way software development used to be.
I turned it into a standalone project so I can plug it into everything I work on. Allow me to introduce AgentRite: a rite for agents, agents done right, agents that write... code. And lots of Markdown.
At a glance
Four roles, each producing a file the next one reads: spec, plan, code, review.
This is how I currently use AgentRite myself:
- Use Claude Code with Opus 5.5 as the
specifierrole and refine a feature (or bug or task) together with it - Read the
spec.mdthat comes out of this, ask for a few clarifications or corrections if necessary, and approve it - Run
./sandbox.sh 0007, where0007is the change number the first step generated. This cuts the branch and starts the sandbox - Run Claude Code with Opus 5.5 as the
plannerrole and ask it to create a plan for feature 0007 - Run Claude Code with Sonnet 5.5 or OpenCode with Qwen 3.8 27b as the
implementerrole and ask it to implement the plan - Run OpenCode with Sol 6.1 as the
reviewerrole and ask it to review the code - Reopen Claude Code (
claude -c) and ask the implementer to check the review and see which changes need to be made - Repeat steps 6 and 7 until no more issues arise
- Exit the sandbox. This will merge back the branch from the sandbox (which was a separate clone!). At this
point it is up to you how to handle the rest. You can create a pull request and check the changes yourself, or you
can simply mark the change
implementedand merge it back to master
Want to try it out yourself? Jump straight to Getting started.
How it started
When I started the Nora Reit project, I used Claude Code in plan mode and always had it persist the plan inside the repository. This seemed like a good pattern to not lose context on how and why things were done. Somewhere along the way I started to think about how the software I was building with the help of LLMs could be made more reliable. I was also playing around with local LLMs, which, despite being pretty good, are nowhere near as good as the frontier models. I had been planning on trying out Superpowers for a while, but I wasn't sure if it would fit in the pattern I was already using. After some research I found other options like OpenSpec, Spec Kit, and BMAD. But in the end I decided to simply create my own set of roles that built on what I was already doing and made integration with the sandbox smooth.
The roles and artifacts
I ended up with four roles: the specifier, the planner, the implementer, and the reviewer. These roles are configured in
the .claude/agents/ and .opencode/agents/ directories so that they can be used as agents in both Claude Code and
OpenCode. I usually just reference them like @specifier.
In addition to the roles, which define what each agent can do, there are also some artifacts involved in the process.
The process.md is part of AgentRite itself. The spec.md, plan.md, and record.md are artifacts that are created
by the agents in a directory called changes/, one set per change (a "change unit", or "unit" for short). These act in
two ways:
- They are a way for the agents to communicate with each other
- They are a form of issue tracking, since they contain all the information required to execute the work. The
record.mdalso contains a status and a type (feature, bug, or task)
And finally there is a .reviews/ directory in which review remarks end up, which is not checked in.
process.md
This artifact lives in the AgentRite package itself. It describes how work is done: the changes process, the commit
conventions, the code standards, where the roles are defined, etc. It contains a lot of the boilerplate you would
copy-paste into your AGENTS.md for each new project.
record.md
This artifact records the steps that happened during the process, including status changes and outcomes. It has a dual purpose: to make it possible to trace how the change came to be, on top of the git log, and to provide the agents with a log of what has already happened. It's a way to communicate between agents. But decisions made by "the human" are also logged. The status and type sit at the top, in frontmatter. All roles are required to add their entries to this document.
The specifier and spec.md
The specifier does one half of what Claude Code's plan mode already did very well: it creates a spec from which a concrete plan can be derived later on.
Its output is a spec.md in the changes/ directory. This is the contract for one change. It states what must be
true when the work is done, as a list of requirements. It doesn't say how to get there, so it contains no file paths or
tasks. Decisions that were made and options that were discarded are recorded here as well.
In my original setup this was part of the planner. But as I used the sandbox more often, I noticed it makes more sense to already have a spec ready that does not contain how things should be done. That way the plan can be made in the sandbox and can be part of an automatic orchestrator in the future. It also lets me write multiple specs without having to worry that some of the plans might depend on each other.
The planner and plan.md
The planner does the other half of what Claude Code's plan mode already did well: it turns the spec into a concrete plan.
Its output is a plan.md in the changes/ directory. It maps the work required for one change. It includes technical
details on which files need to change and how the implementation should be approached. It is an ordered, tickable list
of steps that satisfies the spec. This document states how the work will be done. Nothing in here decides what
should be true. It is written once the spec has been approved and implementation is about to start, against the code as
it is at that moment.
The implementer
The implementer executes the plan. It needs to take the spec into account to make sure nothing gets left out. If the
plan and spec don't match, it pushes back and returns the plan to the planner. It also logs decisions and actions in
record.md. After a review, the implementer is responsible for addressing the remarks.
The reviewer
The reviewer is responsible for checking the implementation against the spec and the plan. It needs to report back any issues it finds.
For each review, it produces a report that is stored in the git-ignored .reviews/ directory. The report is a form of
communication between the reviewer and the implementer. The directory with reviews is also mounted directly into the
sandbox, so that these documents survive between sandbox runs and are available on the host.
Truth be told, at the time of writing the review step is not perfect, and my reviewers are often rather extreme. Besides finding actual issues, they also love harping on timestamps in the record that are not entirely correct or states of the change that do not line up with the progress that has been made. But while a human implementer might get annoyed with this behavior, the implementer agent usually just says: "That's right. I'll fix it immediately!"
Harness and LLM choice
One of the important aspects, and the main reason I decided on creating the implementer and reviewer roles, was that a review should happen independently of the implementation. Before the age of AI, we didn’t rely on reviewing our own code; someone else reviewed it So it makes sense that if model A writes the code, model B should review it. I believe that in an ideal situation the reviewer even comes from a different model family than the implementer.
Besides splitting implementation and review across model families, it also makes sense to choose the right model for each task. Specifying and planning require thinking along with "the human". The implementer should be good at writing code, but also good at simply following the plan. You don't want the implementer to think too much, because that might lead to bad, unchecked decisions or taking too long to finish. And the reviewer, in turn, should be "smart" enough to find holes in the implementation.
I ended up with the following:
- Specifier: Claude Code with Opus or Fable
- Planner: Claude Code with Opus or Fable
- Implementer: Claude Code with Sonnet (or Opus for complex tasks) OR OpenCode with my local Qwen
- Reviewer: OpenCode with Sol
The sandbox
Why?
When I first started using OpenCode with a local Qwen, I noticed it doing creepy things a few times:
- Running
rm -rf: luckily just on a temp file it had created itself, but seeing that command pass by still freaked me out a little - Looking at other projects without being told to do so
And who knows what else it might do! I tried to battle this by locking down my OpenCode config file, but I never got this working the way I wanted it to. I always ended up having to do a lot of "allow always (during this session)".
I figured it would be a lot easier to not have to care: run the agents in a containerized environment (the sandbox) and let them go full YOLO mode.
What it is
The sandbox lets you run Claude Code and OpenCode unattended, with permission prompts off, while limiting what a misbehaving or misled agent can touch and where it can send things. This saves you from babysitting your agent and from Enter fatigue.
It is over a thousand lines of shell, Compose, and Node, with tests, that turn "Docker with two CLIs" into a default-deny environment. The design is the same as that of Docker Sandboxes and Anthropic's reference devcontainer: allowlist the network, hide the host.
What it limits
- Network: all outbound traffic is denied except HTTPS to a hostname allowlist (some model providers, the npm registry, AgentRite's own release host, plus what the project adds). The check is on the hostname in the TLS handshake, not on IP addresses, so another site sharing an address with an allowed one is still refused. One port on the host stays open for a local model server
- Your machine: the agent sees a clone of the unit's branch, the
.reviews/of your project, a per-project log directory for the harnesses, your git identity, and your Claude Code status line script. It sees nothing else of your home: no SSH keys, no cloud credentials, no other repositories. Extra directories are mounted only when you name them - The process: it runs as a non-root user with no capabilities, on a read-only root filesystem, with memory, CPU, and process limits
- Your repository: the agents work on a clone of the unit's branch, made before the sandbox starts. When it exits, the only thing that comes back is the commits of that branch. The review reports will be in your host repository as well, since they get mounted directly
- Proof at start-up: the sandbox refuses to start unless it has shown that a blocked host is blocked and the allowed hosts are reachable, so a broken firewall cannot pass silently
What it makes convenient
- One command:
./sandbox.sh 0007from the project root builds the image if needed and drops you into the sandbox, on a fresh clone of that unit's branch../sandbox.sh --in-placedoes the same on your checkout as it is - Files and commits stay yours: the agent runs under your uid, so files it writes are owned by you, and commits carry your git identity
- Logins persist across runs: a home volume keeps your Claude Code login, while the OpenCode logins are kept in the per-project log directory on the host, together with the harness logs
- Same harnesses as on the host: Claude Code and OpenCode versions are passed from the host by default
- Per-project settings in one small file: names, extra allowed hosts, resource limits, and optionally a Chromium for Playwright tests
- Work lands by itself: when the sandbox exits, your branch is fast-forwarded to the agent's commits. If your branch
moved in the meantime, they wait under
sandbox/<branch>for you to merge - Nothing is lost: a clone with uncommitted work is kept and resumed by the same command, even after a crash
- Units side by side: each unit has its own clone and sandbox, so two agents can work on two units at once, and two runs on one branch are refused
What it does not do
- It does not vet what the agent commits. A unit run works on a clone, so a git hook, a line in
.git/config, or a file innode_modulesstays in the clone and never reaches your repository. But the commits do come back, and a script in a commit runs on your host once you check it out and run it. The difference is that it is in a diff you can read first- If you run with
--in-place, the actual repository is mounted and the risk of a script running on your host is increased
- If you run with
- It is not airtight on exfiltration. DNS is open, and the allowed hosts are themselves channels. It stops the casual case, not a targeted one
- It does not protect the harness logins from the agent, only from leaving through unlisted hosts
- It costs web access. Search works under Claude Code, but opening arbitrary pages is blocked unless the host is listed. Specifying (and sometimes planning as well) therefore has to be done outside the sandbox. The sandbox is mainly for the development/review loop
- It is a container, not a VM. A kernel-level escape is out of its scope
- A new clone starts bare: it has no
node_modules, so the first command inside isnpm ciif you need node modules or want to useagentriteinside the sandbox - The sandbox's own settings come from your checkout, not from the clone. A unit that changes the allowlist runs without that change until you check it out
Quirks
The sandbox also comes with a few quirks.
Multiple clients
I explicitly disallowed starting multiple containers for the same change unit. But if you want to have
multiple harnesses on the same branch next to each other (e.g. an implementer and a reviewer), you need another way into
the sandbox. Since the sandbox is just Docker, that is possible. To make it convenient, I added an extra parameter to
the script: ./sandbox.sh --connect 0007.
Sandbox clone vs. host
Often the plan requires me to do something manually at the end: do a release, deploy the change, or test something that is too sensitive. But because the sandbox uses a fresh clone of the repository and does not have remote access, there is no easy way to get the changes out of the sandbox. When you exit the sandbox, the changes are merged in. But that closes the harnesses running in the sandbox, although their sessions can be continued afterward. To work around this, you could manually fetch the commits from the sandbox clone and merge them into your own repository. Or you could exit the sandbox and start it again later if you still need it.
Harness Telemetry
The containerized setup didn't work by default with my Harness Telemetry project. Each project's sandbox kept the harnesses' state in a Docker volume of its own, so my telemetry agent on the host never saw the Claude Code and OpenCode logs written there.
And the solutions weren't straightforward.
Running the telemetry agent inside each project's sandbox would mean giving the sandbox my database credentials and network access to my Raspberry Pi. And obviously I don't want to force the telemetry agent into AgentRite.
Mounting the host's Claude Code and OpenCode directories into the sandbox would be possible, but it would break the
isolation: the sandbox agent could read the conversations of every project on the host, and write there too. Claude
Code keeps its memory files in that same directory, so the sandbox agent could even leave instructions for the agent
running outside the sandbox. On top of that, the telemetry couldn't tell which project a session belongs to, because
inside the sandbox every project is mounted at /work. Changing that path would break every project that uses
AgentRite, which might not be worth it.
I settled on having AgentRite mount a per-project directory on the host for the harnesses' logs, and on extending the Harness Telemetry project to read more than one such directory. There might be other use cases for that as well, so it was worth adding as a feature.
State of the project
Some honest caveats about the current state of AgentRite:
- The implementer almost always fixes everything that's mentioned in the review. I'm not sure yet if this is a good or a bad thing
- Sometimes the review loop keeps going on forever. This usually means you have to step in and make a decision on how to handle whatever is causing this loop. It helps to ask the implementer to explain what is going on and use your systems-thinking mindset to find a way out
- It is still a lot of manual fumbling. I would really like to automate most of what is happening in the sandbox (see below)
The future of AgentRite
Ever since I introduced the roles, I have been using two or three harnesses at the same time for the same feature: an implementer, a reviewer, and often the planner still open as well. Because I regularly have to wait for a process to finish, I started multitasking and handling features in multiple projects in parallel. This quickly left me short of screen real estate, since I like to split-view two harnesses next to each other on one screen and I tend to keep my main screen for IntelliJ and a browser. At some point, I suddenly realized this sounded like Steve Yegge's stage 6 developer. I managed to partly fix the space problem by using tmux with colors per project and alerts set up, but it still feels like juggling a lot of balls.
It is somewhat addictive to be building so much stuff at once. But it is also very messy. As soon as I exceed four open terminals, it gets hard to wrap my head around what is going on in each. And once I open my browser or IntelliJ, I lose track of two of the four terminals, since I'm using a dual-monitor setup.
Early in the development of AgentRite I decided I would extend it with an orchestrator. I saw a lot of developers complaining on Reddit, X, and Threads about how they turned into machines that just press Enter in terminal windows. That's stupid: running in a sandbox with permissions set up in such a way that nothing can be broken (presumably) should allow for full automation. You become the bottleneck at that point. In an ideal world most of the process is fully automated:
- The human creates a spec
- The orchestrator picks it up
- The planner creates a plan
- The implementer executes the plan
- The reviewer checks the implementation and produces a review report
- The implementer checks the review report and makes any necessary changes
- Steps 5 and 6 loop for a while until either there are no more remarks or something somehow decides we're done
- The human reviews the final product and decides whether to deploy it or not
All kinds of extra steps could be wired into this along the way:
- Reviewing the spec and/or plan and having a feedback loop on that
- Creating multiple implementations and choosing the best one
- Making smarter decisions about which review remarks to act on
- ...
Without realizing it, I ended up working toward my own orchestrator, like the one Steve Yegge describes in stage 8. I read that post of his a while ago. I even referenced it in my first post. But somewhere along the line I lost track of it. Starting several ventures at once and needing better tooling brought me to this point: in need of an orchestrator that I am going to build myself.
I should probably check out Gas City by now. But I am too busy reinventing my own wheel. Sorry, Steve.
So expect a follow-up post about the orchestrator I am building, or about improvements to AgentRite!
Disclaimer
Has this been tested in heavy-load production systems? No. Do I promise it will hold up? Absolutely not. But I have been using it for my own pet projects, and I'm planning to keep building on it and using it there, in the near future at least. You're free to try it out and let me know how it goes!
Getting started
You can find the repository on GitHub and the package on npm.
To get started, all you need to do is install AgentRite via npm and initialize it in your project:
npm install --save-dev agentritenpx agentrite init claude opencode(assuming both harnesses)initmight print some instructions if it can't install all files (because they already exist)
After that you are good to go: the roles are available in Claude Code and OpenCode, and the sandbox is ready to use!
