# How HN: Harness Router

> Source: <https://protocol-lattice.github.io/harness-router/>
> Published: 2026-09-30 10:50:15+00:00

**A decision layer embedded into the harness tool-selection loop.**

Harness Router intercepts the host harness at its native control point, evaluates the available tools, and routes the next tool with cache, Jev, and optional MCTS.

[Website](https://harness-router.vercel.app/)
  ·
  [Architecture](#architecture)
  ·
  [Integrations](#integrations)
  ·
  [MCP](#mcp)
  ·
  [Python API](#python-api)

Agent harnesses already have a tool-selection loop:

```
goal
  │
  ▼
model reasoning
  │
  ▼
tool selection
  │
  ▼
tool execution
  │
  ▼
result
  │
  └──────────────► next iteration
```

Harness Router inserts a decision layer **inside that loop**:

```
model proposes / harness reaches tool-selection point
                         │
                         ▼
                 ┌─────────────────┐
                 │ Harness Router  │
                 │                 │
                 │ cache → Jev     │
                 │        → MCTS   │
                 └────────┬────────┘
                          │
                    routing decision
                          │
                          ▼
                    next tool call
```

The host harness still owns the agent.

**Harness Router owns the tool-selection decision.**

It does not replace the harness’s:

This separation is intentional.

Every routing request follows the cheapest applicable path first:

```
                 routing request
                       │
                       ▼
                ┌─────────────┐
                │ Route cache │
                └──────┬──────┘
                       │ miss
                       ▼
                ┌─────────────┐
                │     Jev     │
                │ fast route  │
                └──────┬──────┘
                       │
                 ambiguous /
              downstream-dependent
                       │
                       ▼
                ┌─────────────┐
                │    MCTS     │
                │   bounded   │
                │ local search│
                └──────┬──────┘
                       │
                       ▼
                 routing decision
```

Repeated compact decisions can be served without invoking a decision provider.

Jev is the normal decision path for tool-selection choices.

MCTS is an optional escalation path for decisions where the best immediate action depends on possible downstream state.

MCTS operates against a side-effect-free simulator. Real tools are not executed during search.

Harness Router is framework-agnostic, but integrations use the **native control point available in each host**.

The control point determines how directly Router can influence the next tool.

Codex uses:

`SessionStart` for tool discovery,`PreToolUse` for routing,

```
Codex
  │
  ▼
tool proposal
  │
  ▼
PreToolUse
  │
  ▼
Harness Router
  │
  ├── cache
  ├── Jev
  └── optional MCTS
  │
  ├── same / fallback / error ──► allow
  │
  └── different confident tool ─► deny
                                      │
                                      ▼
                                  re-plan
                                      │
                                      ▼
                                  next call
```

Install:

```
curl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \
  | python3 - --provider codex
```

Claude Code uses:

```
Claude Code
    │
    ▼
tool proposal
    │
    ▼
PreToolUse
    │
    ▼
Harness Router
    │
    ├── cache → Jev → optional MCTS
    │
    ├── same / fallback ──────► normal flow
    │
    └── different tool ───────► deny + re-plan
                                  │
                                  ▼
                              next call
```

The hook does not execute tools or rewrite their arguments.

See [the Claude integration guide](.claude/README.md).

```
curl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \
  | python3 - --provider claude
```

ohmypi exposes a stronger control point through its active tool surface.

The integration can call `setActiveTools()` before the next provider request:

```
agent turn
    │
    ▼
before_agent_start
    │
    ▼
Harness Router
    │
    ├── cache
    ├── Jev
    └── optional MCTS
    │
    ▼
setActiveTools([selected])
    │
    ▼
next provider request
    │
    ▼
model selects from controlled tools
    │
    ▼
tool execution
    │
    ▼
next routing decision
```

The active tool set is restored on fallback, timeout, low confidence, malformed output, or router failure.

See [the ohmypi integration guide](.omp/README.md).

```
curl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \
  | python3 - --provider ohmypi
```

Antigravity uses its native `PreToolUse` hook.

```
Antigravity
    │
    ▼
tool proposal
    │
    ▼
PreToolUse
    │
    ▼
live tool inventory
    │
    ▼
Harness Router
    │
    ├── cache → Jev → optional MCTS
    │
    ├── same / fallback ──────► allow
    │
    └── different tool ───────► deny + re-plan
```

The adapter uses the live conversation tool inventory and does not invent a fallback catalog when discovery fails.

See [the Antigravity integration guide](.antigravity/README.md).

```
curl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \
  | python3 - --provider antigravity
```

DeepSeek Harness reaches Router through its supported Codex hook bridge.

```
DeepSeek Harness
    │
    ▼
tool proposal
    │
    ▼
tools/pre-execute
    │
    ▼
dsh-hooks-codex
    │
    ▼
Harness Router
    │
    ├── same / fallback / error ──► allow
    │
    └── different tool ───────────► deny + re-plan
```

The command-hook bridge does not provide a faithful live tool registry, so this integration requires a supplied catalog and fails open when a valid routing context is unavailable.

```
python3 hooks/deepseek/install.py
dsh --patch .dsh/harness-router.patch.yml
```

See [the DeepSeek integration guide](https://protocol-lattice.github.io/harness-router/hooks/deepseek/).

Harness Router is also available as a native MCP server.

Start it with:

```
harness-router-mcp
```

Available tools:

| Tool | Purpose | 
|---|---|
| `route` | Fast next-tool routing | 
| `route_mcts` | Bounded multi-step routing | 

Example:

```
[mcp_servers.harness-router]
command = "harness-router-mcp"
```

MCP provides an explicit routing interface for hosts that do not use automatic hook integration.

Requires **Python 3.11+**.

```
uv tool install --force --with 'mcp>=2,<3' \
  'git+https://github.com/Protocol-Lattice/harness-router.git@main'
```

Set the OpenRouter key:

```
export OPENROUTER_API_KEY="your-key"
```

Default decision model:

```
typesafe/jev-1.13
```

Route a tool-selection decision directly:

```
har route \
  --goal "Fix the failing parser test" \
  --observation "Failure points to src/parser.py" \
  --tools-json '[
    {
      "name": "read_file",
      "description": "Read a repository file",
      "category": "inspect",
      "risk": "low"
    },
    {
      "name": "search_code",
      "description": "Search repository source",
      "category": "inspect",
      "risk": "low"
    },
    {
      "name": "run_tests",
      "description": "Run tests",
      "category": "verify",
      "risk": "low"
    }
  ]'
```

Example result:

```
{
  "tool": "read_file",
  "confidence": 0.93,
  "fallback": false
}
python
import asyncio

from harness_router import (
    HarnessState,
    JevToolRouter,
    OpenRouterConfig,
    OpenRouterJevProvider,
    RiskLevel,
    RoutingConfig,
    ToolDescriptor,
)

async def main():
    provider = OpenRouterJevProvider.from_config(OpenRouterConfig())
    router = JevToolRouter(provider, RoutingConfig())

    tools = [
        ToolDescriptor(
            name="read_file",
            description="Read a repository file",
            category="inspect",
            risk=RiskLevel.LOW,
        ),
        ToolDescriptor(
            name="search_code",
            description="Search repository source",
            category="inspect",
            risk=RiskLevel.LOW,
        ),
    ]

    try:
        decision = await router.route(
            HarnessState(
                goal="Fix the failing parser test",
                observation="Failure points to src/parser.py",
            ),
            tools,
        )

        print(decision.tool)
        print(decision.confidence)
    finally:
        await provider.aclose()

asyncio.run(main())
```

Use `route_mcts` when the immediate choice depends on possible downstream outcomes.

```
current state
     │
     ▼
policy prior
     │
     ▼
local simulator
     │
     ├── state
     ├── candidate tools
     ├── transition
     ├── reward
     └── terminal condition
     │
     ▼
best first action
mcts = MCTSToolRouter(
    simulator,
    policy_router=router,
    config=MCTSConfig(
        simulations=64,
        max_depth=4,
        max_policy_evaluations=1,
    ),
)
```

The simulator should model possible transitions rather than execute real tools.

Harness Router is designed for **known alternatives at a tool-selection point**.

Typical decisions:

Keep these in the main planner:

The intended division is:

```
                 harness / planner
                       │
                 goal + state
                       │
                       ▼
                Harness Router
                       │
                    next tool
                       │
                       ▼
                 argument generation
                       │
                       ▼
                   permissions
                       │
                       ▼
                    execution
                       │
                       ▼
                     result
                       │
                       └────────► next iteration
```

Routing is particularly useful when many tools overlap semantically.

Hierarchical routing can reduce the decision surface:

```
                 current state
                      │
                      ▼
                 category route
                      │
        ┌─────────────┼─────────────┐
        ▼             ▼             ▼
     inspect        mutate        verify
        │             │             │
   read/search    write/patch    test/lint
```

Common categories include:

`inspect` · `mutate` · `execute` · `verify` · `git` · `browser` · `memory` · `network` · `finish`

Routing is not authorization.

A high-confidence routing decision means:

This is the strongest candidate from the supplied tool set.

It does not mean:

This action is authorized.

The host harness remains responsible for:

Harness Router does not bypass those controls.

Where the host integration supports it, routing failures fail open: if discovery, the router, or the decision provider is unavailable, the original host behavior continues.

For agents without automatic hook integration:

```
skills/harness-router/SKILL.md
```

Skill mode is explicit:

```
agent
  │
  ▼
invoke routing skill
  │
  ▼
Harness Router
  │
  ▼
next tool
```

Use hooks when routing should be embedded in the host lifecycle. Use the skill when routing should be invoked selectively.

Harness Router is useful when:

If there are only a few obvious tools, routing can add unnecessary overhead.

Evaluate the **whole agent loop**, not router latency in isolation:

```
git clone https://github.com/Protocol-Lattice/harness-router.git
cd harness-router
python -m pip install -e ".[dev]"
```

Run checks:

```
pytest
ruff check .
mypy
```

Harness Router is **alpha**.

The project focuses on one boundary in an agentic system:

```
current harness state
        │
        ▼
  tool-selection point
        │
        ▼
  Harness Router
        │
        ▼
     next tool
```

The host harness remains in control of the rest of the agent lifecycle.

**A decision layer embedded into the harness tool-selection loop.**

  Cache → Jev → MCTS

MIT.
