A minimal agent harness based on H = (E, T, C, S, L, V)
English | 中文
NanoHarness is a minimal Python framework for building tool-augmented LLM agents. It implements the six-component governance model from the Agent Harness Survey:
| Component | Responsibility | |
|---|---|---|
| E | Execution Loop | Think → Act → Observe cycle, termination, error recovery |
| T | Tool Registry | Typed tool catalog, routing, schema validation |
| C | Context Manager | Context window composition and compaction |
| S | State Store | Cross-turn persistence and crash recovery |
| L | Lifecycle Hooks | Cross-cutting instrumentation: logging, policy, auth |
| V | Evaluation | Trajectory recording, mid-loop early-stop detection, independent goal verification |
The kernel provides only these six interfaces and one orchestration engine. Everything else — which LLM to call, how to manage memory, whether to enforce permissions — is determined by the application.
┌─────────────────────────────────────────────────────────────────┐
│ NanoHarness Kernel │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ E: NanoEngine │ │
│ │ │ │
│ │ ON_START ──► Think ──► Act ──► Observe ──► ON_STEP │ │
│ │ │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ │ │
│ │ LLMProtocol T: Tools C: Context │ │
│ │ V: Eval │ │
│ │ should_stop? ──► STOP │ │
│ │ │ │
│ │ ON_END ◄── V: Report + evaluate_success │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Interfaces: BaseToolRegistry BaseContextManager │
│ BaseStateStore BaseHookManager │
│ BaseEvaluator LLMProtocol │
└─────────────────────────────────────────────────────────────────┘
│
constructor injection
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Application Layer │
│ │
│ LLM adapter · memory strategy · permission policy │
│ tool assembly · prompt templates · UI / output │
│ │
│ Wiring: main.py or per-project builder │
└─────────────────────────────────────────────────────────────────┘
Design principle: The engine has zero knowledge of prompts, memory, permissions, or I/O. All behavior is injected. This makes the kernel safe to share across different agent applications.
nanoharness/
core/ # Kernel: interfaces + engine
schema.py # ToolCall, LLMResponse, AgentMessage, StepResult, StopSignal, EvaluationResult
base.py # ETCSLV ABCs, LLMProtocol, HookStage
engine.py # NanoEngine (with mid-loop evaluation)
prompt.py # PromptManager (YAML template loader)
components/ # Minimal ETCSLV implementations
tools/ # T: DictToolRegistry, ScriptToolRegistry
context/ # C: SimpleContextManager
state/ # S: JsonStateStore
hooks/ # L: SimpleHookManager
evaluator/ # V: TraceEvaluator (with should_stop + evaluate_success)
utils/ # get_logger, count_tokens
configs/
prompts.yaml # Prompt templates
scripts/ # Shell-script tools (auto-discovered, 27 tools)
examples/
coding_agent/ # Full-featured coding agent reference (434 tests)
tests/ # 74 kernel tests
git clone https://github.com/HabitGraylight/NanoHarness.git
cd NanoHarness
pip install -e .The kernel has no required external dependencies. LLM clients and other integrations are installed by each application as needed.
# Run the minimal example
python main.py
# Run the coding agent
cd examples/coding_agent && python main.pyNanoEngine.run(query)
│
├─ L.trigger(ON_TASK_START)
├─ C.add_message(user)
│
└─ loop until terminated or max_steps:
│
├─ Think: E → LLM.chat(C.get_full_context(), T.get_schemas())
├─ L.trigger(ON_THOUGHT_READY)
│
├─ Act: for each tool_call:
│ optional permission gate → T.call(name, args)
│ C.add_message(observation)
│
├─ S.save_state()
├─ V.log_step()
├─ V.should_stop()? ──► early break if stuck/spinning
└─ L.trigger(ON_STEP_END)
├─ V.get_report() (includes evaluate_success verdict)
└─ L.trigger(ON_TASK_END)
No memory, no prompt rendering, no permission logic inside the engine. All of that flows through injected components and hooks.
Tools satisfy BaseToolRegistry with two methods: get_tool_schemas() and call(name, args).
Two built-in registries:
- DictToolRegistry — register Python functions via
@tooldecorator. JSON Schema is inferred from type hints. - ScriptToolRegistry — auto-discovers
.shfiles in a directory. Parameters are declared via@paramcomment headers and passed as environment variables.
Registries compose via merge().
Adding a new tool does not require touching any Python code — drop a shell script with the right headers into configs/scripts/ and it is automatically available to the agent.
The kernel defines interfaces. Applications provide concrete behavior:
LLM — implement LLMProtocol:
def chat(self, messages, tools=None) -> LLMResponse: ...Custom components — subclass any Base* ABC and inject into NanoEngine.
See examples/coding_agent/ for a reference that wires together a custom LLM adapter, memory strategy, permission pipeline, subagent delegation, skill loading, and evaluation — all built on top of the kernel without modifying it.
# Kernel tests (74)
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/ -v
# Coding agent tests (434: 291 UT + 143 ST)
cd examples/coding_agent
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/ -vTotal: 508 tests. No external dependencies required for kernel tests.
- Streaming LLM output
- Async engine mode
- Multi-agent orchestration
- Context compaction strategies
- Observability integration (OpenTelemetry / LangFuse)
- Harness Completeness Matrix — automated ETCSLV coverage reporting
Agents with tool access can cause real damage. Production deployments should implement permission gates, sandbox execution, and prompt injection defenses. See the coding agent example for a reference permission pipeline.
The theoretical foundation of this project is based on the Agent Harness Survey.
@software{nanoharness2026,
title = {NanoHarness: A Minimal Agent Harness Based on H=(E,T,C,S,L,V)},
author = {Habit},
year = {2026},
url = {https://github.com/HabitGraylight/NanoHarness},
license = {MIT}
}Theoretical foundation:
@article{meng2026agentharness,
title = {Agent Harness for Large Language Model Agents: A Survey},
author = {Meng, Qianyu and Wang, Yanan and Chen, Liyi and others},
year = {2026},
url = {https://www.preprints.org/manuscript/202604.0428/v2}
}MIT
