I Knew the Harness but Not What Was Inside — Learning How Coding Agents Are Built and How They Run Through OpenCode 2 and Hermes#
2026-10-25

A question that started with a news brief#
Last week, in the 2026-10-18 AI news brief, I wrote about OpenCode 2.0. OpenCode is an open source coding agent you use from the terminal. In 2.0, the terminal, web, and desktop apps all connect to a single background server, and it gained Code Mode, which lets the agent chain tools from MCP (Model Context Protocol, the standard for connecting external tools to agents) servers in a small piece of JavaScript, as well as background subagents.
While writing it up, a question came to mind. How is this different from Hermes Agent, which I use every day? I use Hermes, the open source agent from Nous Research, in two places. The PR agent I built runs on Hermes and is connected to Discord, and the Hermes server I use for work is connected to Slack and a web chat that my team shares. A resident server, background work, and subagents are things I already use in Hermes, so I wondered whether the changes in OpenCode 2.0 were heading in the same direction as Hermes or a different one.
But when I tried to compare them, I had no yardstick. I knew the concept of harness engineering. I have written several times on this blog about running agents safely with instruction files, hooks, and verification loops. I also know the main features of coding agents. What I had never checked for myself was what parts a coding agent is made of on the inside, and in what order a single request flows through them. I could put feature lists side by side, but I could not explain where the differences came from or why.
So I changed the order. Before comparing, I decided to get a feel for the basics. I downloaded the latest source code of both projects and read it together with AI, learned the common structure and flow of coding agents, used that map to read each agent, and compared them at the end. This essay is the record of that learning.
The starting point: what a single API call lacks#
If you call an LLM API directly, you learn that it is just like a single function. You send the whole conversation as text, and one next response comes back as text. Three things are missing.
- It has no hands. The model cannot read files or run commands. It can only say “change it like this.”
- It has no memory. The API is stateless. It only seems to remember earlier turns because you resend the whole conversation every time.
- It does not repeat. It answers once and stops. It does not check the result and try again.
Think of a brilliant expert you can only reach by phone. The advice is excellent, but they cannot touch the site, and they forget everything when the call ends. A coding agent is a program that fills these three gaps. Tools fill the missing hands, storage and context assembly fill the missing memory, and a loop fills the missing repetition. I covered the concept of agents in the “Agents and Harnesses” chapter of my book, so here I focus on the parts that fill those gaps and the order in which they run.
The basic makeup of a coding agent: model plus harness#
The program that fills the gaps is called a harness. The word originally means the gear you put on a horse. The horse provides the power; the harness connects that power to the cart and keeps it on course. In a coding agent, the model makes the judgments and the harness connects them to the real world.
The key is a division of roles. The model only asks: “read this file,” “run this command.” This capability is called tool calling. The harness is what receives the request, actually runs it, and shows the result back to the model. Opening the code of both projects, I found the harness could be split into roughly six parts.
| Part | What it does |
|---|---|
| Loop engine | Repeats model calls and tool execution until the job is done |
| Context assembly | Builds what is sent to the model on every call: system instructions, project instruction files, and the conversation so far |
| Toolbox | Provides file reading and editing, shell commands, search, web lookups, and external tools connected through MCP |
| Permission check | Blocks risky requests or asks a human |
| Storage | Records the conversation and progress in a database |
| Model adapter | Converts the harness’s internal format into each provider’s API format, such as Anthropic, OpenAI, or Gemini |
Only after drawing this did I see the line between what I knew and what I didn’t. The harness engineering I had been doing was writing instruction files, adding hooks, and setting permission rules. All of it is configuration applied from outside the harness. I knew an agent follows an AGENTS.md file once you write one, but when and in what order that file is read and handed to the model was the business of the harness’s inside, and I had never looked in there.
The basic flow: the path one request takes#
When a request like “fix the bug that’s making the tests fail” comes in, it flows like this.
User: fix the failing-test bug
Model: let me run the tests
→ result: test 3 fails
Model: let me read foo.py
→ result: file contents
Model: I'll fix line 12
→ result: edit done
Model: let me run the tests again
→ result: all pass
Model: fixed it. The cause was ...
→ no tool request, stopAt each step the model looks at the results so far and decides only the next single action. Every result after an arrow is something the harness actually ran. Repeating this round trip until the goal is reached is the agent loop, and boiled down to code it is about ten lines.
conversation = [system_instructions, user_request]
while True:
response = LLM(conversation, tools) # whole history every time
conversation.append(response)
if response has no tool requests:
reply to the user and stop
for request in response.tool_requests:
if risky: ask a human for permission
result = run(request) # edit files, run commands
conversation.append(result)These ten lines are enough to make an agent run. The trouble starts when you run it for a long time on real projects. Both projects have well over a hundred thousand lines of core code, and when you follow it, a large share turns out to be machinery that guards against the problems this loop runs into in the real world.
| Problem when the loop runs long | Guard |
|---|---|
| The conversation keeps growing and exceeds the model’s context limit | Compaction: replaces older parts with a summary |
It runs commands like rm -rf as is | Permission check: blocks risky requests or asks a human |
| The job is too big to fit in one conversation | Subagents: hand it to a new conversation and get back only the result |
| Progress is lost when the program shuts down | Storage: records each step in a database |
| Long-running commands block the conversation | Background execution: get the result as a notification when it finishes |
That is the map I came away with. Now let me use it to read each agent.
OpenCode 2: a work site that never stops#
OpenCode 2 is written in TypeScript and runs on the Bun runtime. The core of the official announcement is that the server now runs automatically as a single background process on your machine. In the code, running opencode first looks for a server that is already up, and if there isn’t one, starts a server and connects to it. By default the server only accepts connections from inside your own machine, and the terminal, web, and desktop apps are all clients that send HTTP requests to this server and draw the screen from the real-time events they receive.
Placed on the map, the part OpenCode invested in most is the coupling of the loop engine and storage.
- Input is not processed right away; it is first recorded as a row in a database inbox. When a new request arrives mid-run, it either redirects the run (steer) or waits in line (queue).
- At every step, the loop does not keep extending the conversation in memory; it reloads it from the database. The design documents in the repository explicitly say not to build a tool loop that runs only in memory.
- Before execution starts, it first writes a record saying “this session is running.” If the server dies and comes back, it uses that record to resume.
The goal of this design is clear: work should not stop even if you close the window or the server restarts. The background features I wrote about in the brief follow the same idea. When you run shell commands or subagents in the background, you get a response right away, and when they finish, the result arrives in the parent conversation as a message. Background job information is stored too, so it carries over after a restart. The way tools are executed also stood out. While the model’s response is still streaming in, as soon as one tool request is complete, OpenCode starts running it without waiting for the full response.
Checking Code Mode against the code, it matched the official announcement exactly, and it does not change every tool call. Built-in tools such as reading and editing files or the shell all remain ordinary tool calls. Only the tools from MCP servers and plugins are bundled into a single execute tool, which the model calls by chaining them in a small piece of JavaScript. The script runs not in a regular JavaScript engine but in a restricted interpreter OpenCode built itself, and it cannot reach the file system or processes. That said, the fetch available inside it only blocks file URLs, and I could not find a path in the code where it goes through the tool permission check. Its meaning can change depending on which MCP servers you connect, so I am keeping it in mind.
Permissions are decided by a list of rules that state “which action, on which target, handled how,” and when no rule matches, the default is to ask a human. If a human rejects a request and writes a reason, that feedback is passed to the model so it can look for another way.
Hermes: where the features I use were coming from#
Hermes is written in Python. For me, this was the most interesting part, because the behavior I had taken for granted while using it mapped one by one onto parts of the map.
Why it keeps working after the laptop closes: the gateway owns the loop#
At the center of Hermes is a resident process called the gateway. One runs per profile, and it handles every messenger connection, such as Discord, Slack, and Telegram. There were 27 messenger connection plugins alone. A code comment states plainly that the CLI process “never builds an agent, never runs a tool and never writes state.db.” Even when you run hermes in the terminal, the actual conversation runs inside the gateway, and the terminal just receives and shows the result.
When I talk to it from Discord or Slack, the same loop in the same gateway is running. The reason I was able to put Hermes on a cloud server so it keeps working after I close my laptop is also that the gateway is built to stay resident.
Approval requests in Discord or Slack: the last step of the permission check#
The checks Hermes runs before executing a shell command turned out to have more layers than I expected.
- Commands in isolated environments, such as a Docker container without host folders mounted or a cloud sandbox, are not prompted. Only the user’s deny rules apply.
- Fatal patterns, such as deleting system directories, and the user’s deny rules are always blocked.
- In runs nobody is watching, such as cron jobs, commands that match a dangerous pattern are denied by default.
- In a conversation with a human present, commands that do not match a dangerous pattern run as is.
- When a command matches a dangerous pattern, the default setting (smart) has an auxiliary LLM decide first: approve, deny, or hand it to a human.
- Only when it is handed to a human does an approval request reach the channel I use. In Discord or Slack, the request shows up in the chat, and I answer with a button or
/approveor/deny.
To me, an approval request was just a message arriving in a messenger. In fact it is the last step, where only what still cannot be decided after several layers of checks reaches a human.
Why memories and skills pile up: a second loop that runs after the conversation#
This is where Hermes differs most from other coding agents. After the conversation loop ends and the response has been delivered, Hermes checks its counters. By default, memory comes up for review every 10 user turns, and skills every 10 tool iterations. When it is time, it starts an agent forked from the current conversation in the background and has it review “is there anything worth remembering from this conversation?” and “is there a skill to create or fix?” This forked agent can only use the memory and skill tools plus read-only tools. The results are saved to MEMORY.md, USER.md, and skill files, and they go into the system instructions when the next conversation starts.
Cron jobs, which have no human in them, skip this review loop. The code comment gives the reason: one review costs about 30K tokens, and there is nothing to gain when no human is involved. Also, in my remote development essay I wrote that it is good practice for a human to read and approve skills the agent creates, and Hermes already has an option for exactly that (write_approval). When it is on, memory and skill writes are not applied immediately but staged in a review queue. It is off by default.
Other things I confirmed in the code#
- It reads only one kind of project instruction file. It looks for
.hermes.md,AGENTS.md,CLAUDE.md, and.cursorrulesin that order and puts only the first kind it finds into the system instructions. If a repository is shared by several coding tools, it is worth knowing which file actually gets loaded. - The code was ahead of the README. The README says the search over past conversations uses LLM summarization, but the current code does not call an LLM and uses only SQLite full-text search. In fast-moving open source, the code, not the documentation, was the primary source.
Side by side: the same skeleton, different emphasis#
Once I had placed both agents on the map, I could make the comparison I wanted in the first place. First, what they share. In both projects, the skeleton of the loop was the same ten lines from earlier. Open OpenCode’s step-execution function and Hermes’s conversation-loop function, and you find the same loop, even though the languages and design philosophies differ. Both also limit subagent depth to one level by default and return only the result to the parent instead of the subagent’s intermediate steps.
The differences were in the machinery around the loop.
| Item | OpenCode 2 | Hermes |
|---|---|---|
| Identity | Coding agent | General-purpose agent you call from messengers; coding is one of its abilities |
| Where it invested | Uninterrupted execution: database inbox, reloading every step, resuming after restart | An assistant that grows: messenger gateway, review loop after the conversation |
| Tool execution | Runs a request as soon as it is complete mid-stream | After the full response, runs requests that are safe together in parallel |
| When compaction starts | About 85% of the context window | 50% by default; 75% for models with windows under 512K |
| Permission check | Rules and a human | Rules, an auxiliary LLM, and a human |
| Knowledge accumulation | Instruction files and skills managed by people | Memory and skills the agent writes itself |
| Combining external tools | Code Mode (JavaScript) | execute_code (calling tools from a Python script) |
Process structure and run location are different axes#
Since both agents use a resident server, it is tempting to sum it up in one line: “agents that used to run inside the terminal moved to a server.” But that summary mixes two independent axes.
- Process structure: is the harness inside the app that draws the screen (app-embedded), or split out into a separate resident process (daemon)?
- Run location: does that process run on your own machine (local) or on a remote server (remote)?
| Local | Remote | |
|---|---|---|
| App-embedded | A terminal agent run on a laptop | A terminal agent run over SSH on a remote server |
| Daemon | Default setup of OpenCode 2 and Hermes | My Hermes on a cloud server, cloud-hosted coding agents |
With default settings, OpenCode 2 and Hermes are both local daemons. The OpenCode server only accepts connections from inside your machine, and Hermes keeps its state under your home directory (~/.hermes). So the change in OpenCode 2.0 was not a move to remote; it changed the process structure on the same machine. Putting Hermes on a cloud server was a separate choice that changed the run location, and the fact that it keeps working after I close my laptop is the result of the two choices combined.
Nor is a daemon a more advanced stage than an app-embedded harness. A daemon lets work continue after you close the window and lets several screens see the same session, but it comes at a cost. OpenCode 2’s code includes logic to replace the server when the client and server versions differ, contention handling so that only one survives when several processes try to start a server at once, and authentication even for communication within your own machine. There are plenty of situations where an app-embedded harness is simpler and good enough. The two are not stages of progress but design choices with trade-offs.
Wrapping up: I now have a yardstick for comparison#
My original question was “how are OpenCode 2.0 and Hermes different?” Answered with feature lists, they overlap so much, from the resident server and background work to subagents and plugins, that the differences are hard to see. After learning the structure and flow first, the answer changed. The loop is the same; the difference is in what the machinery around the loop is trying to protect. OpenCode 2 protects work from being interrupted, and Hermes protects the agent’s ability to grow the more you use it.
I think I can use this yardstick when I look at new coding agents too. Since the loops are all similar anyway, looking at how each one handles compaction, permissions, subagents, storage, and background execution shows what that agent cares about. It also changes things from the harness engineering side. If you know where the settings you apply from outside land on the inside, you can judge more precisely where to put an instruction file and how to set approval rules.
Let me note the limits too. This time I confirmed internal behavior only from the code and comments, and did not follow actual execution through logs. Next, I plan to pick one conversation on a Hermes I run and follow the order in which the loop runs through the logs. Looking into what memories and skills the review loop has actually created on the two Hermes setups I run also seems like a good starting point for the Hermes operations follow-up I have been putting off.
References#
- OpenCode 2.0 announcement
- anomalyco/opencode (GitHub)
- NousResearch/hermes-agent (GitHub)
- 2026-10-18 AI News Brief
- Key AI Concepts (4): Agents and Harnesses
- Harness Engineering — A Practical Guide to Safe AI Agent Operations
- Hermes Agent — Is It the Luxury Brand of AI Agents? A First Impression
- An Agent That Keeps Working After the Laptop Closes: Remote Development with Lightsail, Hermes, and Discord