{"slug": "how-hn-harness-router", "title": "How HN: Harness Router", "summary": "Protocol-Lattice released Harness Router, a decision layer that intercepts an agent harness's tool-selection loop and routes the next tool call through a route cache, a fast path called Jev, and optional bounded MCTS search. Harness Router is framework-agnostic and integrates via each host's native control point, using SessionStart for tool discovery and PreToolUse for routing in Codex and PreToolUse in Claude Code, where a different confident tool proposal is denied to trigger re-planning. MCTS runs against a side-effect-free simulator so real tools are not executed during search, and the hook does not execute tools or rewrite their arguments.", "body_md": "**A decision layer embedded into the harness tool-selection loop.**\n\nHarness 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.\n\n[Website](https://harness-router.vercel.app/)\n  ·\n  [Architecture](#architecture)\n  ·\n  [Integrations](#integrations)\n  ·\n  [MCP](#mcp)\n  ·\n  [Python API](#python-api)\n\nAgent harnesses already have a tool-selection loop:\n\n```\ngoal\n  │\n  ▼\nmodel reasoning\n  │\n  ▼\ntool selection\n  │\n  ▼\ntool execution\n  │\n  ▼\nresult\n  │\n  └──────────────► next iteration\n```\n\nHarness Router inserts a decision layer **inside that loop**:\n\n```\nmodel proposes / harness reaches tool-selection point\n                         │\n                         ▼\n                 ┌─────────────────┐\n                 │ Harness Router  │\n                 │                 │\n                 │ cache → Jev     │\n                 │        → MCTS   │\n                 └────────┬────────┘\n                          │\n                    routing decision\n                          │\n                          ▼\n                    next tool call\n```\n\nThe host harness still owns the agent.\n\n**Harness Router owns the tool-selection decision.**\n\nIt does not replace the harness’s:\n\nThis separation is intentional.\n\nEvery routing request follows the cheapest applicable path first:\n\n```\n                 routing request\n                       │\n                       ▼\n                ┌─────────────┐\n                │ Route cache │\n                └──────┬──────┘\n                       │ miss\n                       ▼\n                ┌─────────────┐\n                │     Jev     │\n                │ fast route  │\n                └──────┬──────┘\n                       │\n                 ambiguous /\n              downstream-dependent\n                       │\n                       ▼\n                ┌─────────────┐\n                │    MCTS     │\n                │   bounded   │\n                │ local search│\n                └──────┬──────┘\n                       │\n                       ▼\n                 routing decision\n```\n\nRepeated compact decisions can be served without invoking a decision provider.\n\nJev is the normal decision path for tool-selection choices.\n\nMCTS is an optional escalation path for decisions where the best immediate action depends on possible downstream state.\n\nMCTS operates against a side-effect-free simulator. Real tools are not executed during search.\n\nHarness Router is framework-agnostic, but integrations use the **native control point available in each host**.\n\nThe control point determines how directly Router can influence the next tool.\n\nCodex uses:\n\n`SessionStart` for tool discovery,`PreToolUse` for routing,\n\n```\nCodex\n  │\n  ▼\ntool proposal\n  │\n  ▼\nPreToolUse\n  │\n  ▼\nHarness Router\n  │\n  ├── cache\n  ├── Jev\n  └── optional MCTS\n  │\n  ├── same / fallback / error ──► allow\n  │\n  └── different confident tool ─► deny\n                                      │\n                                      ▼\n                                  re-plan\n                                      │\n                                      ▼\n                                  next call\n```\n\nInstall:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \\\n  | python3 - --provider codex\n```\n\nClaude Code uses:\n\n```\nClaude Code\n    │\n    ▼\ntool proposal\n    │\n    ▼\nPreToolUse\n    │\n    ▼\nHarness Router\n    │\n    ├── cache → Jev → optional MCTS\n    │\n    ├── same / fallback ──────► normal flow\n    │\n    └── different tool ───────► deny + re-plan\n                                  │\n                                  ▼\n                              next call\n```\n\nThe hook does not execute tools or rewrite their arguments.\n\nSee [the Claude integration guide](.claude/README.md).\n\n```\ncurl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \\\n  | python3 - --provider claude\n```\n\nohmypi exposes a stronger control point through its active tool surface.\n\nThe integration can call `setActiveTools()` before the next provider request:\n\n```\nagent turn\n    │\n    ▼\nbefore_agent_start\n    │\n    ▼\nHarness Router\n    │\n    ├── cache\n    ├── Jev\n    └── optional MCTS\n    │\n    ▼\nsetActiveTools([selected])\n    │\n    ▼\nnext provider request\n    │\n    ▼\nmodel selects from controlled tools\n    │\n    ▼\ntool execution\n    │\n    ▼\nnext routing decision\n```\n\nThe active tool set is restored on fallback, timeout, low confidence, malformed output, or router failure.\n\nSee [the ohmypi integration guide](.omp/README.md).\n\n```\ncurl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \\\n  | python3 - --provider ohmypi\n```\n\nAntigravity uses its native `PreToolUse` hook.\n\n```\nAntigravity\n    │\n    ▼\ntool proposal\n    │\n    ▼\nPreToolUse\n    │\n    ▼\nlive tool inventory\n    │\n    ▼\nHarness Router\n    │\n    ├── cache → Jev → optional MCTS\n    │\n    ├── same / fallback ──────► allow\n    │\n    └── different tool ───────► deny + re-plan\n```\n\nThe adapter uses the live conversation tool inventory and does not invent a fallback catalog when discovery fails.\n\nSee [the Antigravity integration guide](.antigravity/README.md).\n\n```\ncurl -fsSL https://raw.githubusercontent.com/Protocol-Lattice/harness-router/main/scripts/install_hook.py \\\n  | python3 - --provider antigravity\n```\n\nDeepSeek Harness reaches Router through its supported Codex hook bridge.\n\n```\nDeepSeek Harness\n    │\n    ▼\ntool proposal\n    │\n    ▼\ntools/pre-execute\n    │\n    ▼\ndsh-hooks-codex\n    │\n    ▼\nHarness Router\n    │\n    ├── same / fallback / error ──► allow\n    │\n    └── different tool ───────────► deny + re-plan\n```\n\nThe 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.\n\n```\npython3 hooks/deepseek/install.py\ndsh --patch .dsh/harness-router.patch.yml\n```\n\nSee [the DeepSeek integration guide](https://protocol-lattice.github.io/harness-router/hooks/deepseek/).\n\nHarness Router is also available as a native MCP server.\n\nStart it with:\n\n```\nharness-router-mcp\n```\n\nAvailable tools:\n\n| Tool | Purpose | \n|---|---|\n| `route` | Fast next-tool routing | \n| `route_mcts` | Bounded multi-step routing | \n\nExample:\n\n```\n[mcp_servers.harness-router]\ncommand = \"harness-router-mcp\"\n```\n\nMCP provides an explicit routing interface for hosts that do not use automatic hook integration.\n\nRequires **Python 3.11+**.\n\n```\nuv tool install --force --with 'mcp>=2,<3' \\\n  'git+https://github.com/Protocol-Lattice/harness-router.git@main'\n```\n\nSet the OpenRouter key:\n\n```\nexport OPENROUTER_API_KEY=\"your-key\"\n```\n\nDefault decision model:\n\n```\ntypesafe/jev-1.13\n```\n\nRoute a tool-selection decision directly:\n\n```\nhar route \\\n  --goal \"Fix the failing parser test\" \\\n  --observation \"Failure points to src/parser.py\" \\\n  --tools-json '[\n    {\n      \"name\": \"read_file\",\n      \"description\": \"Read a repository file\",\n      \"category\": \"inspect\",\n      \"risk\": \"low\"\n    },\n    {\n      \"name\": \"search_code\",\n      \"description\": \"Search repository source\",\n      \"category\": \"inspect\",\n      \"risk\": \"low\"\n    },\n    {\n      \"name\": \"run_tests\",\n      \"description\": \"Run tests\",\n      \"category\": \"verify\",\n      \"risk\": \"low\"\n    }\n  ]'\n```\n\nExample result:\n\n```\n{\n  \"tool\": \"read_file\",\n  \"confidence\": 0.93,\n  \"fallback\": false\n}\npython\nimport asyncio\n\nfrom harness_router import (\n    HarnessState,\n    JevToolRouter,\n    OpenRouterConfig,\n    OpenRouterJevProvider,\n    RiskLevel,\n    RoutingConfig,\n    ToolDescriptor,\n)\n\nasync def main():\n    provider = OpenRouterJevProvider.from_config(OpenRouterConfig())\n    router = JevToolRouter(provider, RoutingConfig())\n\n    tools = [\n        ToolDescriptor(\n            name=\"read_file\",\n            description=\"Read a repository file\",\n            category=\"inspect\",\n            risk=RiskLevel.LOW,\n        ),\n        ToolDescriptor(\n            name=\"search_code\",\n            description=\"Search repository source\",\n            category=\"inspect\",\n            risk=RiskLevel.LOW,\n        ),\n    ]\n\n    try:\n        decision = await router.route(\n            HarnessState(\n                goal=\"Fix the failing parser test\",\n                observation=\"Failure points to src/parser.py\",\n            ),\n            tools,\n        )\n\n        print(decision.tool)\n        print(decision.confidence)\n    finally:\n        await provider.aclose()\n\nasyncio.run(main())\n```\n\nUse `route_mcts` when the immediate choice depends on possible downstream outcomes.\n\n```\ncurrent state\n     │\n     ▼\npolicy prior\n     │\n     ▼\nlocal simulator\n     │\n     ├── state\n     ├── candidate tools\n     ├── transition\n     ├── reward\n     └── terminal condition\n     │\n     ▼\nbest first action\nmcts = MCTSToolRouter(\n    simulator,\n    policy_router=router,\n    config=MCTSConfig(\n        simulations=64,\n        max_depth=4,\n        max_policy_evaluations=1,\n    ),\n)\n```\n\nThe simulator should model possible transitions rather than execute real tools.\n\nHarness Router is designed for **known alternatives at a tool-selection point**.\n\nTypical decisions:\n\nKeep these in the main planner:\n\nThe intended division is:\n\n```\n                 harness / planner\n                       │\n                 goal + state\n                       │\n                       ▼\n                Harness Router\n                       │\n                    next tool\n                       │\n                       ▼\n                 argument generation\n                       │\n                       ▼\n                   permissions\n                       │\n                       ▼\n                    execution\n                       │\n                       ▼\n                     result\n                       │\n                       └────────► next iteration\n```\n\nRouting is particularly useful when many tools overlap semantically.\n\nHierarchical routing can reduce the decision surface:\n\n```\n                 current state\n                      │\n                      ▼\n                 category route\n                      │\n        ┌─────────────┼─────────────┐\n        ▼             ▼             ▼\n     inspect        mutate        verify\n        │             │             │\n   read/search    write/patch    test/lint\n```\n\nCommon categories include:\n\n`inspect` · `mutate` · `execute` · `verify` · `git` · `browser` · `memory` · `network` · `finish`\n\nRouting is not authorization.\n\nA high-confidence routing decision means:\n\nThis is the strongest candidate from the supplied tool set.\n\nIt does not mean:\n\nThis action is authorized.\n\nThe host harness remains responsible for:\n\nHarness Router does not bypass those controls.\n\nWhere the host integration supports it, routing failures fail open: if discovery, the router, or the decision provider is unavailable, the original host behavior continues.\n\nFor agents without automatic hook integration:\n\n```\nskills/harness-router/SKILL.md\n```\n\nSkill mode is explicit:\n\n```\nagent\n  │\n  ▼\ninvoke routing skill\n  │\n  ▼\nHarness Router\n  │\n  ▼\nnext tool\n```\n\nUse hooks when routing should be embedded in the host lifecycle. Use the skill when routing should be invoked selectively.\n\nHarness Router is useful when:\n\nIf there are only a few obvious tools, routing can add unnecessary overhead.\n\nEvaluate the **whole agent loop**, not router latency in isolation:\n\n```\ngit clone https://github.com/Protocol-Lattice/harness-router.git\ncd harness-router\npython -m pip install -e \".[dev]\"\n```\n\nRun checks:\n\n```\npytest\nruff check .\nmypy\n```\n\nHarness Router is **alpha**.\n\nThe project focuses on one boundary in an agentic system:\n\n```\ncurrent harness state\n        │\n        ▼\n  tool-selection point\n        │\n        ▼\n  Harness Router\n        │\n        ▼\n     next tool\n```\n\nThe host harness remains in control of the rest of the agent lifecycle.\n\n**A decision layer embedded into the harness tool-selection loop.**\n\n  Cache → Jev → MCTS\n\nMIT.", "url": "https://wpnews.pro/news/how-hn-harness-router", "canonical_source": "https://protocol-lattice.github.io/harness-router/", "published_at": "2026-09-30 10:50:15+00:00", "updated_at": "2026-09-30 11:20:10.213859+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["Protocol-Lattice", "Harness Router", "Codex", "Claude Code", "Jev", "MCTS"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-hn-harness-router", "markdown": "https://wpnews.pro/news/how-hn-harness-router.md", "text": "https://wpnews.pro/news/how-hn-harness-router.txt", "jsonld": "https://wpnews.pro/news/how-hn-harness-router.jsonld"}}