HarnessRouter Community Edition: the self-hosted, Apache-2.0 edition of the unified interface for agent harnesses. Run Codex, Claude Code, Hermes, and more through one API, with sessions, streaming, files, cancellation, and failure handling. Implements the Unified Harness Protocol (UHP), an open standard. Your keys, your infrastructure.
Enterprises that want to run LLM‑driven “harnesses” (e.g., Codex, Claude Code, Hermes) on‑premise must stitch together separate model APIs, session handling, streaming, file storage and cancellation logic. Doing this manually leads to duplicated code, fragile integrations and opaque failure handling.
What This Does
HarnessRouter CE provides a single self‑hosted HTTP endpoint that implements the Unified Harness Protocol (UHP).
The gateway package (gateway/app.py) runs a Flask API that orchestrates requests, stores media in gateway/media_catalog.json, and persists workspace state via SQLite (gateway/control_sqlite.py).
The runner (runner/server.py) launches the actual harness processes, loads installed kits from the persistent volume (/data), and streams results back to the gateway.
The protocol folder (protocol/conformance/uhp_conformance/cli.py and related modules) supplies a CLI that validates an installation against the UHP spec and produces JSON reports (protocol/conformance/reports/*.json).
A React/Next.js UI (ui/) offers a browser console that talks to the gateway, displays sessions, and lets users add provider keys, all styled with Tailwind.
How It Is Wired
Entry point – gateway/app.py creates the Flask app (create_app()) and registers routes defined in gateway/backing.py.
Request flow – An incoming HTTP request hits a route in backing.py, which constructs a RequestContext (protocol/conformance/uhp_conformance/context.py) and calls gateway/control_store.py to record the job in the SQLite DB (gateway/control_sqlite.py).
Job dispatch – gateway/backing.py invokes the runner via a local HTTP call to runner/server.py (the runner runs in a separate container defined in docker-compose.yml). The runner loads the requested kit from the shared volume (/data/kits) and executes it, streaming partial responses through Server‑Sent Events.
Result handling – The runner posts the final payload back to the gateway’s /response endpoint; gateway/media_plane.py writes any attached files to gateway/media_catalog.json. Errors are wrapped by gateway/tests/test_response_error_envelope.py utilities and returned as standardized UHP error envelopes.
Protocol conformance – The CLI (protocol/conformance/uhp_conformance/cli.py) imports checks.py and registry.py to run the full UHP test suite; results are written to protocol/conformance/reports/*.json. This path is the only place where the repository touches the external conformance spec.
The gateway is the widest‑impact component (it touches the DB, file store, and external runner). The runner is isolated to a single container, lowering blast radius for code changes. No circular imports were observed; the call graph is a clear star topology centered on gateway/app.py.
How To Use It
# Clone the repo
git clone https://github.com/moses-y/harnessrouter
cd harnessrouter
# Build and start all services (gateway, runner, UI) with Docker Compose
docker compose up -d
# The UI is reachable at http://127.0.0.1:3000
# First‑time login uses the default credentials shown in the container logs.
# After login, add an LLM provider key via the Settings page.
If you prefer the pre‑built image rather than building locally, the README’s quick‑start works as‑is:
Environment variables are optional; the container generates a default admin user on first start. For custom configuration, edit .env.example and mount it as --env-file .env.
Real‑World Use
A SaaS platform that needs to run proprietary code‑generation models behind a firewall can replace its ad‑hoc orchestration layer with HarnessRouter. The platform’s backend calls POST /v1/harnesses/run on the gateway; the gateway queues the job, the runner executes the model in an isolated container, and the result streams back to the platform’s existing WebSocket layer without any code changes.
Code Health & Issues
Low – Test coverage present (gateway/tests/, protocol/conformance/tests/).
Low – CI pipeline (.github/workflows/tests.yml) runs lint, unit tests and Docker build on every push.
Low – License file (LICENSE) and contributor guidelines (CODE_OF_CONDUCT.md, CONTRIBUTING.md) are included.
No secret files or hard‑coded keys found in the repository.
No obvious dead code; each top‑level package (gateway, runner, protocol, ui) has a clear responsibility.
The Bottom Line
HarnessRouter CE delivers a well‑structured, Docker‑first self‑hosted gateway that abstracts away the plumbing required to run multiple LLM harnesses under a single, UHP‑compliant API. It is suitable for teams that need on‑prem control, have existing CI/CD pipelines, and are comfortable with Docker‑compose deployments. Users seeking a fully managed cloud service will find the package overly low‑level, but for on‑prem integration it provides a solid, test‑backed foundation.
What the analyser found
Deployment readiness
7/7
✓Container image
✓CI pipeline
✓Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
210 files
TSX40
Python33
TypeScript28
CSS26
Markdown20
JSON9
ReactNext.jsFlaskTailwindDocker
Module dependencies
The 10 most depended-upon modules of 110, from static import analysis. Red outline marks a module in an import cycle.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
critical1
high3
medium6
low2
Upgrade the pinned dependency carrying a critical advisorycritical13 occurrences
pyjwt@2.10.1 CVE-2025-45768 (critical) +12 more
The manifest pins the exact version the advisory covers, so this is not a warning about drift: the vulnerable code is what installs, and a critical rating means it is reachable over the network without credentials or user interaction.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Pin third-party GitHub Actions to a commit SHAhigh3 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Upgrade the pinned dependency carrying a published advisoryhigh95 occurrences
The pinned version is the one that installs, so a published advisory against it describes this deployment rather than a hypothetical one, and the advisory is also a public description of how to exploit it.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Commit a lockfile beside the manifesthigh
protocol/conformance/pyproject.toml
manifest with no lockfile in scope
An unlocked range means the artifact you tested and the artifact you ship can contain different transitive code, so a malicious patch release reaches production with no diff.
Fix: Run the package manager once and commit the generated lockfile.
Declare least-privilege permissions for GITHUB_TOKENmedium3 occurrences
.github/workflows/release.yml
3 workflow(s) declare no permissions, 2 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
4 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium2 occurrences
Dockerfile
node:22-slim, python:3.12-slim
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Give the outbound request a timeoutmedium
runner/server.py
1 outbound call(s) with no timeout
requests has no default timeout, so a peer that accepts the connection and then says nothing holds the worker forever, and with a small worker pool a single slow dependency takes the whole service down without anything appearing to fail.
Fix: Pass timeout= on every call, or use a session with one configured.
Set persist-credentials: false on checkoutmedium
.github/workflows/tests.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Set timeout-minutes on the workflow jobslow3 occurrences
.github/workflows/release.yml
3 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Add the repository convention files this project lackslow3 occurrences
missing .editorconfig, .gitattributes, a formatter config
Without them one contributor's editor writes tabs into a Python file, a shell script commits with CRLF and fails in the container, and a notebook diff is unreviewable.
Fix: Add .editorconfig, .gitattributes with text=auto eol=lf, and a formatter config.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.