
We run two AI agents inside one notes vault. Claude Code works on a laptop and on a small cloud server. Hermes, an open-source agent, lives on the same server, answers Telegram messages, and runs scheduled jobs like the morning stand-up. Both read and write the same markdown files.
Last week a post by @beamnxw on harness engineering made the rounds. Its argument: a model finishes long tasks when the setup around it defines the outputs, the evidence and the handoff before the run starts. We audited our own setup against it and found a problem we hadn't noticed. Our two agents were following two different rulebooks.
Why bother with a harness
The models were rarely the problem. The failures we could name came from the setup around them:
- The stand-up agent wrote a line about a conference prize that appeared nowhere in its input. Nobody checked, so it went out.
- A deploy run from a fresh copy of a repo used
rsync --deleteand removed files that only existed on the live server. - An agent cleaning up “orphaned” folders deleted 63 GB of video footage that belonged to live projects.
Each of these had a written rule against it somewhere. Written rules are suggestions. The agent reads them, mostly follows them, and on a bad day doesn't.
A harness is everything around the model that turns suggestions into structure: the instructions it loads, the tools it may call, the checks that run before it finishes, and the notes it leaves for the next session. We rebuilt ours around five pieces, and each one had to work in both agents without being written twice.
1. One instruction file, imported everywhere
Claude Code reads CLAUDE.md. Hermes reads AGENTS.md, and it loads only one project file per session, so it never sees CLAUDE.md when AGENTS.md exists. We had both files, and over a few months they drifted. Claude never saw the rule that every build needs a dev log. Hermes never saw the rule about formatting text you'll paste into an email.
The fix is boring and it works. AGENTS.md is now the only place rules live. CLAUDE.md shrank to a few lines ending in @AGENTS.md, which Claude Code treats as an import. Rules that only matter in one folder, like the facts about our app's deployment, moved into an AGENTS.md inside that folder, with a one-line CLAUDE.md next to it. Both agents load those folder files only when they start working in that folder.
One honest note: this did not shrink what the agents load at startup. It grew. Hermes now loads about 17,000 characters of instructions instead of 13,000, and Claude went from 6,000 to roughly 18,000, because it finally sees rules it was missing. We traded tokens for consistency, and we'd make that trade again.
2. One guard script, two agents
Both agents support hooks: small programs that run before a tool call and can block it. What surprised us is how close the two formats are. Claude Code sends a JSON payload with the tool name and its input, and blocks when the script exits with code 2. Hermes sends nearly the same payload and accepts a Claude-style {"decision": "block", "reason": "..."} reply. So one Python script, guard.py, protects both. It checks which agent called it and answers in that agent's format.

It blocks a short list, and every item maps to something that went wrong or nearly did:
- Printing or copying
.envand credential files, because keys should never land in a transcript. Loading them withsource .envstill works. rsync --deletewithout a dry run first: the deploy that wiped live files.- Deleting the video project's blob folder: the 63 GB loss.
git push --force, because several sessions share one repo.- Paths owned by other system users. Some keys belong to a separate user on purpose.
The guard splits a shell command on ;, && and pipes, and judges each piece by its first word. That is why set -a; . ./.env; set +a; python3 gen.py | head passes while cat .env doesn't. It has 74 tests covering both payload formats.
It is not a sandbox. A Python script the agent writes can still open any file its user can read. Hooks catch the agent's own tool calls, and the real protection for anything valuable is a separate system user with its own permissions. We'd rather say that plainly than oversell a regex.
The guard went live mid-build and immediately blocked our own setup step, an echo that wrote a test value into a .env file. That's the correct behavior, and it was a nice moment.
3. A reviewer that only reads
A writer is a poor judge of its own work. So we added a fact-check skill: give it an output file and the sources it was written from, and it returns a table with one row per factual claim, marked verified, incorrect or unresolved, each with the source line it checked. It never edits anything.
In Claude Code it runs as a subagent restricted to read and search tools. Hermes can't restrict a sub-task's tools (children inherit the parent's), so there the same skill runs in-session with read-only as an instruction. The morning stand-up now runs it before saving and drops any bullet it can't trace back to its input.

We tested it on the design document for this very system. Of 41 claims, 37 checked out, three couldn't be confirmed from the sources we allowed, and one was wrong. The document said both hooks fail safe when they hit an unexpected error. The reviewer read the code and found that the guard didn't: a payload in an unexpected shape would crash it. We fixed it and added a test. A reviewer that finds a real bug in its first run has earned its place.
4. A handoff note every agent can read
Long work spans sessions, machines and agents. Each project's dev log now starts with the same six-line block:
## Resume
- **Task:** what this project is doing right now
- **Outputs:** the paths that hold the current work
- **Completed:** finished stages and the checks that passed
- **Decisions:** confirmed choices, each with where it was decided
- **Open issues:** missing evidence, blockers, open questions
- **Next action:** one concrete step the next agent can start onTo resume, any agent reads the block, opens the files it names, and starts on the next action. Because the format is fixed, a script can read it too: the morning stand-up now lists every project's next action and open issues without anyone writing a summary.
5. Define “done” before you start
Both agents now have a /goal command that keeps working until a judge model decides the goal is met. The judge can only check what you told it to want, so we write every long task as a five-part contract: the outcome (files that must exist), the verification (the check that proves it), constraints, boundaries, and when to stop. Hermes uses these same five fields natively. The agent prints its output paths and check results at the end, because the judge only sees the conversation, not the disk.
Scheduled jobs get the short version. Each prompt ends with a “Done when” line and an instruction to reply BLOCKED: <reason> and stop if something fails. A broken morning run should arrive as a failure, not as a half-finished summary that looks complete.

One more hook enforces our oldest rule. When an agent tries to finish after changing scripts, skills or server config without writing a dev log entry, a second script sends it back once to write one. It nudges a single time, so a false alarm costs one extra turn.
How to set this up yourself
You need a repo or folder that more than one agent works in. Then:
- Pick one canonical instruction file. Use
AGENTS.md, since most agents read it. MakeCLAUDE.mdcontain only@AGENTS.mdplus anything Claude-specific. Move folder-only rules into that folder's ownAGENTS.md. - Write down what has actually gone wrong. Your guard rules should come from incidents, not imagination. Five rules you understand beat fifty you don't.
- Write one guard script. Read JSON from stdin, take
tool_nameandtool_input, decide. For Claude Code, block with exit code 2 and the reason on stderr. For Hermes, print{"decision": "block", "reason": "..."}. Wrap everything so any error exits 0, then write tests for both formats. - Wire it into each agent. Claude Code: a
PreToolUsehook in.claude/settings.json. Hermes: ahooks: pre_tool_callentry in itsconfig.yaml, plus the exact command string in~/.hermes/shell-hooks-allowlist.json. Hermes skips unapproved hooks in background runs. - Share skills from one folder. Put them in
.agents/skills/. Symlink.claude/skillsto it, and list it underskills.external_dirsin Hermes. - Add a read-only reviewer to whatever output you read without checking.
- Add the Resume block and the five-part contract to your instruction file, and test each piece live by asking the agent to do the forbidden thing.
Instructions for your agent
If you'd rather hand this to your own agent, paste the block below into a session in your repo. It's written for an agent to follow.
Set up a shared agent harness in this repository so Claude Code and Hermes follow the same rules.
1. Instructions: make AGENTS.md the single source of rules. Merge any rules that exist
only in CLAUDE.md into AGENTS.md. Replace CLAUDE.md with a short header and the line
"@AGENTS.md". For rules that apply to one folder only, create <folder>/AGENTS.md and
<folder>/CLAUDE.md containing "@AGENTS.md". Show me the diff before deleting anything.
2. Guard: create .agents/hooks/guard.py. Read a JSON payload from stdin. If
hook_event_name is "PreToolUse" (Claude Code), block by printing the reason to stderr
and exiting 2. If it is "pre_tool_call" (Hermes), block by printing
{"decision": "block", "reason": "..."} and exiting 0; otherwise print {}.
Shell tools are Bash / terminal (input key: command). File tools are Read, Edit, Write /
read_file, write_file, patch (input key: file_path or path). Ask me which incidents to
guard against; start with: printing .env files, rsync --delete without -n, and
git push --force. Split shell commands on ; && || | and judge each segment by its
first word. Any exception must exit 0. Write tests for both payload formats and run them.
3. Wiring: add a PreToolUse hook running the guard to .claude/settings.json. Add
hooks.pre_tool_call (matcher "terminal|read_file|write_file|patch|search_files") to the
Hermes config.yaml, and add the exact command string to
~/.hermes/shell-hooks-allowlist.json. Run: hermes hooks doctor
4. Skills: create .agents/skills/. Symlink .claude/skills to it. Add it to
skills.external_dirs in the Hermes config.
5. Reviewer: create .agents/skills/fact-check/SKILL.md. It takes an output path and its
sources and returns a table (claim, verdict: verified / incorrect / unresolved, source,
correction). It never edits files. For Claude Code, add .claude/agents/fact-checker.md
with tools: Read, Grep, Glob, WebFetch, WebSearch.
6. Handoff and done: add to AGENTS.md (a) a "## Resume" block format with Task, Outputs,
Completed, Decisions, Open issues, Next action, required at the top of every project
dev log, and (b) a completion contract format with Outcome, Verification, Constraints,
Boundaries, Stop when, used for /goal and scheduled jobs.
7. Test live: in a new session of each agent, try to cat a dummy .env file and run
rsync --delete on two scratch folders. Both must be blocked. Report the results.What we'd do differently
We'd start with the guard and the reviewer, because they caught real problems on day one. The single instruction file is the least exciting piece and the one we'd least want to give up. The stand-up's new fact-check step runs for the first time tomorrow morning, on a small and fast model, and we don't know yet whether it's strong enough to catch its own mistakes. We'll find out.
Argo Writer Agent