Skip to content

HabitGraylight/NanoHarness

Repository files navigation

NanoHarness

Python 3.10+ License: MIT Tests ETCSLV

NanoHarness

A minimal agent harness based on H = (E, T, C, S, L, V)

English | 中文


What

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.


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                       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.


Structure

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

Quick Start

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.py

Engine Loop

NanoEngine.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

Tools satisfy BaseToolRegistry with two methods: get_tool_schemas() and call(name, args).

Two built-in registries:

  • DictToolRegistry — register Python functions via @tool decorator. JSON Schema is inferred from type hints.
  • ScriptToolRegistry — auto-discovers .sh files in a directory. Parameters are declared via @param comment 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.


Extending

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.


Testing

# 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/ -v

Total: 508 tests. No external dependencies required for kernel tests.


Roadmap

  • Streaming LLM output
  • Async engine mode
  • Multi-agent orchestration
  • Context compaction strategies
  • Observability integration (OpenTelemetry / LangFuse)
  • Harness Completeness Matrix — automated ETCSLV coverage reporting

Security

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.


Acknowledgments

The theoretical foundation of this project is based on the Agent Harness Survey.


Citation

@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}
}

License

MIT

About

A minimal, composable AI agent harness framework in Python.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages