In the previous article, we discussed the theory of Agent = Model + Harness. In this article, we'll build one from scratch.
Goal
Build a minimal but usable Agent Harness in one afternoon, validating the following concepts:
- Multi-provider support (Claude / GPT-4 / extensible)
- Agent Loop (query → stream → tool-call → loop)
- AGENTS.md automatic context injection
- MEMORY.md cross-session persistent memory
- Permission boundaries (path whitelist, dangerous command interception)
- Lifecycle hooks (PreToolUse / PostToolUse)
Final product: 7 Python modules, ~300 lines of core code, supporting both CLI and programmatic usage.
→ github.com/greasebig/harness-poc
Architecture
harness/
├── agent.py # Core Agent Loop — the heartbeat of the entire system
├── context.py # AGENTS.md auto-discovery & system prompt construction
├── memory.py # MEMORY.md cross-session persistent memory
├── permissions.py # Path whitelist + dangerous command interception
├── hooks.py # PreToolUse / PostToolUse lifecycle
├── providers.py # Anthropic + OpenAI + extensible
└── tools.py # read_file, write_file, list_dir, shell
Core: Agent Loop
The soul of the entire system in just 30 lines:
while not done:
response = self.provider.chat(self.messages, self.tools.schemas())
if response.stop_reason == "tool_use":
for tc in response.tool_calls:
# Permission check → Hook → Execute → Result
result = self._execute_tool(tc.name, tc.input)
self.messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tc.id,
"content": result,
}],
})
else:
final_text = response.text
done = True
The model decides "what to do." The Harness handles "how to do it" — permission validation, hook triggers, result formatting. Interlocked, never stopping.
Five Design Decisions
1. Context ≤ 60 Lines
def _load_agents_md(self) -> str:
# ...
if len(lines) > 60:
lines = lines[:57] + ["# ... (truncated for focus)"]
return "\n".join(lines)
Give the Agent a map, not an encyclopedia. AGENTS.md files exceeding 60 lines are automatically truncated.
2. Every Tool Call Goes Through Permission Check
def _execute_tool(self, name, params):
if not self.perm.allow(name, params):
return "[BLOCKED] Permission denied."
self.hooks.fire("pre_tool", name=name, params=params)
result = self.tools.execute(name, params)
self.hooks.fire("post_tool", name=name, params=params, result=result)
return result
Permission → Hook → Execute → Hook. No shortcuts.
3. Memory is Explicit
The Agent must actively use the [MEMORY: ...] tag to write to long-term memory. Non-intrusive, doesn't pollute context.
# Agent writes in response:
# I notice this project uses pytest. [MEMORY: This project uses pytest for testing]
# Harness auto-extracts and writes to MEMORY.md:
# - [2026-05-24 20:30] This project uses pytest for testing
4. Providers are Plugins
Adding a new provider takes just 30 lines of code:
class MyProvider(BaseProvider):
default_model = "my-model"
def chat(self, messages, tools):
# Your API call here
return ProviderResponse(text=..., tool_calls=..., ...)
PROVIDERS["my_provider"] = MyProvider
5. Hooks are Optional
Without registering hooks, the system runs normally. With hooks, you gain logging, auditing, and verification capabilities.
agent.hooks.register("pre_tool", lambda **kw: print(f"🔧 {kw['name']}"))
agent.hooks.register("post_tool", lambda **kw: print(f"✅ done"))
Demo
$ python -m harness "List files and create hello.txt"
[harness] 🤖 anthropic/claude-sonnet-4-5-20250514 — 4 tools loaded
[harness] 💾 Saved 1 memory entries to MEMORY.md
Here's what I did:
1. Listed files: AGENTS.md, README.md, harness/, examples/
2. Created hello.txt with "Hello from harness!"
Three Examples
| Example | Demonstrates |
|---|---|
01_basic_task.py | Basic task: list files + write file |
02_hooks.py | Hook system: log every tool call |
03_memory.py | Cross-session memory: Agent remembers and recalls |
Why Build This POC?
The previous article cited LangChain's experimental data: the same model, with Harness optimization, achieved a 26% accuracy improvement.
This POC is the runnable version of that experiment. It proves:
- Harness isn't complex — core logic is only 300 lines of Python
- Harness is extensible — new providers, new tools, new hooks are all plug-and-play
- Harness compounds — every time the Agent makes a mistake, you add a rule, and it becomes more reliable
Next steps:
- Add self-verification loop (auto-run lint + test)
- Support sub-Agent dispatch (context firewall)
- MCP (Model Context Protocol) integration
Fork it, break it, build on it.