The Pain: 30+ files referencing each other. Change one script, break five gates. And the whole thing only works if the agent remembers to load the engine — forget once, and every gate is a paper tiger.
What You'll Learn: Why prompt-injected rules beat code-level enforcement, how SOUL.md + .hermes.md replace a 30-file engine, and the 2-file + 2-script system that survives session switches and model changes.
"If I have to remember to run the engine, the engine shouldn't exist."
In the previous article we built the 4 AI pipelines, covering the full chain from content generation to anomaly monitoring. The architecture was running — but the codebase was growing.
After adding persistent state, a quality system, and ops monitoring, my agent engineering stack naturally bloated. It's not that the code was badly written — it's that more features means more files. Three months in, the stack had ballooned to 30+ files: engine scripts, registries, validation scripts, config mapping files… They referenced each other, depended on each other; change one, and you had to regression-test five.
It wasn't "lightweight" anymore. It wasn't "physical" anymore.
So last month I made a decision: full rewrite. Of the 30+ files in the five-gate system, I deleted 28 and kept 2 files + 2 scripts.
This article is the complete postmortem of that slimming — not deletion, refactoring.

System slimming: 30 files → 2 files + 2 scripts
1. The Problem: A 30-File Enforcement System Is Its Own Baggage
After building persistent state, the quality system, and ops monitoring, I naturally started thinking about a deeper question: how do I make sure the agent follows the rules every single time?
My first answer to that question was the "three locks" — entry gate, session isolation, and pre-checks:
| Lock | Interception point | What it does |
|---|---|---|
| EntryGate | entry | complex tasks that aren't decomposed are rejected |
| Session isolation | path | multiple sessions never overwrite each other |
| Pre-checks | quality | actually reads files, compares state |
Later I added gate #4 (terminal-state check) and gate #5 (anomaly alerting + persistence), and it became the five-gate system.
The five gates worked well — violation rate dropped to zero, anomalies auto-alerted, cross-session persistence worked. But it had one fatal flaw: the agent had to remember to run it.
The five gates were implemented as a standalone Python engine — loop_engine.py. Before every execution, the agent had to load the engine, register rules, initialize state, run the step gates, run the terminal check. If the agent forgot to load the engine — the engine never even started — then the five gates were just words on paper.
Worse, the engine itself was 30+ files. The structure looked roughly like this:
hermes-harness/
├── loop_engine.py # main engine (200 lines)
├── skill_registry.json # skill registry
├── violations.json # violation persistence
├── gates/ # 6 gate files
├── scripts/ # 7 validation scripts
├── config/ # 4 config mapping files
└── STATE.md # state file
30+ files referencing each other. Change one script, affect 5 gates. Every refactor meant flipping through the whole directory.
The five gates solved one problem and created another.
2. The Core Insight: Physical Enforcement Lives in the Prompt, Not the Code
Before the rewrite, I asked myself one key question: what is the essence of the five gates?
They're a pile of ifs and raises — intercepting the agent's illegal behavior at the code layer. But code interception has a precondition: the agent must be running inside the code's execution environment.
If the agent switches sessions, switches models, or simply never calls loop_engine.py, all the physical enforcement evaporates.
Real physical enforcement doesn't live in code. It lives in the prompt. Because the prompt is injected automatically on every conversation — the agent doesn't have to load it. An agent can forget to run the engine, but it can't forget to see the prompt — the instructions in the prompt are in effect from the very first turn.
That insight drove the design of the entire new system:
Old system:
prompt → rules → agent remembers to load engine → code enforces
↓
if the agent forgets → enforcement dead
New system:
SOUL.md iron rules (prompt injection) → rules in front of the agent → auto-enforced
↓
the agent doesn't need to remember anything
So the new system is just 2 files and 2 scripts. No engine needed.
3. The New System: 2 Files + 2 Scripts

From Declarative Rules to Physical Enforcement
File 1: SOUL.md (system-level iron rules, 243 lines)
SOUL.md is the system-level prompt that the Hermes Agent auto-injects into every session. It holds 17 iron rules, from "never fabricate data" to "must execute the task in the first turn."
The point isn't the number of rules — it's how it gets loaded. It isn't something the agent actively reads; it's auto-injected by Hermes's session-initialization mechanism. Before the agent says a single word, SOUL.md is already sitting in the context.
How SOUL.md loads — the agent doesn't need to remember anything:
$ cat ~/.hermes/SOUL.md | head -10
# SOUL.md — System Operating Universal Law
# This file holds the system-level iron rules for the Hermes Agent.
# Auto-injected into every new session. Cannot be skipped or overridden.
# Violating any rule = the system refuses to execute.
## Iron Rule 1: Facts First
# Never fabricate any data, code, execution result, or API response.
## Iron Rule 2: No Step Gating
# Execute tasks directly; do not wait for "step confirmation."
The rules that took 30 files to enforce are now written into the prompt — lighter, more physical, and impossible for the agent to bypass.
File 2: .hermes.md (project-level SOP, 127 lines)
SOUL.md governs the whole system; .hermes.md governs a single project's SOP.
.hermes.md example (project-level constraints):
rules:
- name: maker-checker-separation
rule: "The same agent cannot act as both Maker and Checker"
severity: BLOCKER
- name: commit-protection
rule: "Never push directly to the main branch"
severity: ERROR
SOUL.md + .hermes.md replace 5 files from the old system:
-
skill_registry.json(mapping of 10 skills) -
allowed_transitions.json+forbidden_transitions.json(step whitelist/blacklist) -
role_configurations.json(role config) - 3
check_*.pypre-check scripts
5 files replaced by 2.
Script 1: check_completion.py (terminal-state check)
This is the only retained gate #4 — the terminal-state checker. But I converted it from a Python class (which needed engine instantiation) into 3 CLI commands:
$ check_completion.py create task-42
✅ Task task-42 created
$ check_completion.py status task-42
Task: task-42
Status: running
Execution success: None
Push status: None
$ check_completion.py done task-42
❌ Terminal check failed:
- push_status is not PUSHED
- push_receipt is empty
The core logic is just three checks:
def cmd_done(task_id):
# Checks three conditions:
# 1. The task actually executed successfully
# 2. The code was committed and pushed
# 3. A push receipt exists
if not task.get("execution_success"):
violations.append("execution_success is not True")
if task.get("push_status") not in ("PUSHED", "PUSHED_WITH_CHANGES"):
violations.append("push_status is not PUSHED")
if not task.get("push_receipt"):
violations.append("push_receipt is empty")
if violations:
# Print the failure reasons, return a non-zero exit code
sys.exit(1)
# All passed → mark completed
task["status"] = "completed"
The old system's TerminalStateChecker was a class that needed instantiation and was invoked by the engine. The new system is 3 pure CLI commands — runnable from any terminal, no import engine, no environment loading.
Script 2: send_alert.py (anomaly alerting)
The old gate #5 was an anomaly engine with 5 rules — it needed a background process and timed scanning. The new system is 2 CLI commands:
$ send_alert.py check task-42
🚨 [WARNING] Task task-42 has been running for 25 minutes, over the timeout threshold
🚨 [ERROR] Task task-42 executed successfully but no checker was dispatched
$ send_alert.py scan
📊 Scanned 3 active tasks: 1 anomaly found
- task-42: timeout (25m > 20m limit)
From a scheduled background process → on-demand CLI commands. This isn't weaker capability; it's removing complexity. The agent calling send_alert.py check at task end is no worse than background polling — but it eliminates a resident process.
4. Before & After
| Dimension | Old five gates | New system | Change |
|---|---|---|---|
| File count | 30+ | 2 files + 2 scripts | −90% |
| Engine | 200-line core | none | fully removed |
| Rule registration | JSON registry | SOUL.md iron rules | merged |
| Step gating | code + config files | not needed | removed |
| Terminal check | class instantiation | CLI commands | simplified |
| Anomaly alerting | background process | CLI commands | simplified |
| Persistence | 2 files | 1 file | merged |
| Startup | agent remembers to load | auto-injected | zero effort |
The key numbers: 28 files deleted, 0 features lost.
This isn't feature removal — it's translation: rules that lived in code get written back into the prompt; classes that needed engine instantiation become pure CLI commands.
The whole workflow collapses to 3 steps:
- Step 1: Startup — confirm the time + read STATE.md
$ date && cat STATE.md
- Step 2: Execute the task — the rules are in SOUL.md and .hermes.md
# (the agent executes per the iron rules; no engine to load)
- Step 3: Finish — terminal check
$ check_completion.py done task-42
5. Pitfalls I Hit
Pit 1: Don't Romanticize "Automation"
The old anomaly engine scanned every 5 minutes — sounded smart. But after three months of running, only 23% of anomalies were caught by the polling. The other 77% were already caught by the agent calling send_alert.py check itself after finishing a task.
The complexity of background polling exceeded the problem it solved. Delete the polling; keep the manual trigger.
Pit 2: CLI Commands Beat Classes
The old TerminalStateChecker was wrapped in a class:
checker = TerminalStateChecker()
result = checker.verify(task_file)
Looks elegant — but the agent has to know TerminalStateChecker exists to instantiate it. If the agent forgets the import, the check never runs.
With a CLI command:
check_completion.py done task-42
The agent only needs to remember the command name, not understand a Python class. When the command fails it prints the error directly — there's no "forgot to load it" failure mode.
Pit 3: Don't Trust the Agent's Claim of "I've Got the Rules"
I made a classic mistake — putting a rule at the top of SOUL.md and assuming the agent would follow it every turn. By the 5th turn of a conversation, the agent started inventing its own rules: "based on our earlier discussion…", "as confirmed in the previous step…" — bypassing the iron rules sitting right in the prompt.
The fix: physical verification must exist. SOUL.md defines the rules; check_completion.py done does the physical verification. Two rails: rules in the prompt, verification in code. The prompt can be "forgotten," but a CLI command's output can't be tampered with by the agent.
6. Closing: Light Is Physical
Looking back at every "system" I've built for my agent — from the original three-layer physical enforcement to the 30 files to today's 2 files + 2 scripts — the strongest one was also the simplest.
Not because I got lazier. Because I finally understood something: the core of physical enforcement isn't code complexity — it's the minimal path the agent can't skip.
A 30-file engine? The agent can forget to run it. Two files and two scripts? The agent can't forget even if it wants to — SOUL.md is auto-injected into the prompt, and check_completion.py sits in the shell, callable at any moment.
Guess which of the two systems is the real "physical enforcement"?
Series so far: the previous articles covered the full path from persistent state to quality systems to ops monitoring to pipeline architecture. This one slimmed the system from 30 files to a 2-file + 2-script setup.
Next article: Writing Code Isn't Enough — Self-Attack Testing Is the Hardest Test. After the new system went live, I ran a round of self-attack tests — simulated all 5 attack methods — and found an unexpected breakthrough…
About the author: Wu Ji (无记) — AI & digitalization practitioner focused on Agent engineering, Loop Engineering, and digital transformation. Practical, hands-on tutorials — follow along and it just works.
Top comments (0)