Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
515e8bc
Merge pull request #4 from knowhycodata/feat/llmtr-provider
richard-epsilla Aug 19, 2026
d7512c7
feat(backends): Pi, the fourth harness — and MCP for it via the open-…
richard-epsilla Aug 20, 2026
07bf58d
fix(entrypoint): pi joins the availability probe, not just the install
richard-epsilla Aug 20, 2026
0908b16
fix(selfhost): API keys could not authenticate anything — the gate at…
richard-epsilla Aug 20, 2026
50bba02
fix(selfhost): a bearer request is judged by its key, and discovery n…
richard-epsilla Aug 20, 2026
cb00aa8
fix(selfhost): the proxy's response-header allow-list was eating UHP-…
richard-epsilla Aug 20, 2026
0039f2e
fix(ui): pi's own words in the base picker, pi in the base filter, AG…
richard-epsilla Aug 20, 2026
ba4b71f
feat(pi): the frontier set, each model probed through the pi CLI befo…
richard-epsilla Aug 20, 2026
dd531bf
fix(pi): a text-only channel must not be fed the session's images on …
richard-epsilla Aug 20, 2026
26b3ef0
fix(pi): a non-prefix text revision must not paint the answer twice
richard-epsilla Aug 20, 2026
d65e35e
feat(ui): built-ins hold their catalog order — Codex, Claude Code, He…
richard-epsilla Aug 20, 2026
6d44ad6
Merge pull request #5 from HarnessRouter/feat/pi-backend
richard-epsilla Aug 20, 2026
c03a4b4
docs(conformance): the 0.5.0 report — full class, 52/52, run against …
richard-epsilla Aug 20, 2026
f52db59
docs(conformance): the release is 0.7.0, so the report is too
richard-epsilla Aug 20, 2026
9265c2e
feat(backends): DeepSeek Harness, the fifth — official SDK transport,…
richard-epsilla Aug 20, 2026
6da98d5
fix(dsh): cross-process resume — the one seam upstream left open, use…
richard-epsilla Aug 20, 2026
1bdb4ac
fix(dsh): a mute runtime must not read as a hang, and absent MCP must…
richard-epsilla Aug 20, 2026
f198d5e
feat(dsh): multi-family — the wider families ride pi's LLM library, a…
richard-epsilla Aug 20, 2026
21dc2f2
fix(dsh): the anthropic-messages route base must not carry /v1 — the …
richard-epsilla Aug 20, 2026
3e0a9f9
Merge pull request #6 from HarnessRouter/feat/dsh-backend
richard-epsilla Aug 20, 2026
91eedca
docs(conformance): the 0.8.0 report — full class, 52/52, through a De…
richard-epsilla Aug 20, 2026
a8b6905
docs(readme): the first screenshot shows all five built-in harnesses
richard-epsilla Aug 20, 2026
d21b424
docs(readme): rename the first screenshot so every cache lets go of t…
richard-epsilla Aug 20, 2026
48e7af5
Merge main: keep its CONTRIBUTING spine, fold in this PR's additions
Aug 20, 2026
c1af391
Merge pull request #1 from HarnessRouter/codex/add-contributing-gover…
richard-readily Aug 20, 2026
c79c9e6
conformance(report): the JSON verdict honours 'a skip is never a pass…
Aug 20, 2026
f883d4c
ci: run every test suite on every PR — until now nothing did
Aug 20, 2026
08ba83e
ci: an empty console suite is not a failing one
Aug 20, 2026
58fcc18
fix(dsh): retry a reasoning_effort rejection once without it, then st…
Aug 20, 2026
fe3850f
fix(gateway): a failed Response's error object now matches the spec's…
Aug 21, 2026
35a96f8
ci: the gateway job needs jsonschema — the envelope test validates ag…
Aug 21, 2026
22b8ab7
fix(hermes): a loopback relay repairs translator-fatal message shapes…
Aug 21, 2026
77045d4
fix(workspaces): the registry joins its records on the server — two l…
richard-epsilla Aug 21, 2026
8a99a7b
Merge pull request #13 from HarnessRouter/conformance-hardening
richard-epsilla Aug 21, 2026
2573d7f
Merge pull request #14 from HarnessRouter/fix/selfhost-workspaces
richard-epsilla Aug 21, 2026
c0565d2
test(media): scope the no-poll assertions to the job under test
richard-epsilla Aug 21, 2026
cacac15
Merge pull request #15 from HarnessRouter/fix/media-test-isolation
richard-epsilla Aug 21, 2026
a7f0571
feat(runner): the workspace contract — every turn tells the agent whe…
richard-epsilla Aug 21, 2026
8b907c0
fix(runner): adopt deliverables stranded at the workspace root — the …
richard-epsilla Aug 21, 2026
11b390e
fix(runner): the wrong folder must not exist — the session workspace …
richard-epsilla Aug 21, 2026
3ae3ed9
fix(runner): the session write-wall — an agent process may write its …
Aug 23, 2026
b678a45
test(media): isolate the job store per test, and make provider task i…
richard-epsilla Aug 23, 2026
c6b324f
docs(uhp-site): add "The naming of UHP" background page
Aug 23, 2026
1de0a39
fix(selfhost): shared scratch stays writable — the wall walled off Li…
richard-epsilla Aug 23, 2026
48b060f
Merge pull request #17 from HarnessRouter/docs/uhp-naming-background
richard-epsilla Aug 23, 2026
dcf8a90
fix(hermes): vision is a capability of the instance, not a property o…
richard-epsilla Aug 23, 2026
e70d2c4
docs(conformance): full class, 52/52, on the write-wall + vision buil…
richard-epsilla Aug 23, 2026
ab69218
Merge branch 'fix/session-write-wall': the session write-wall, scratc…
richard-epsilla Aug 23, 2026
13615e1
ci: the runner job installs httpx — its suite drives the app through …
richard-epsilla Aug 23, 2026
2cccc8c
ci: a workflow that opens pull requests as github-actions[bot]
richard-epsilla Aug 23, 2026
d8cc4be
Merge pull request #18 from HarnessRouter/ci/runner-testclient-deps
richard-epsilla Aug 23, 2026
b05780e
docs: how changes land on main, and the bot-opened pull request
richard-epsilla Aug 23, 2026
5c2cbed
ci: start checks
richard-epsilla Aug 23, 2026
eedc47d
ci: start checks
richard-epsilla Aug 23, 2026
2513540
ci: start checks
richard-epsilla Aug 23, 2026
c20bdb7
Merge pull request #19 from HarnessRouter/docs/pr-workflow-note
richard-epsilla Aug 23, 2026
f6656a3
docs(readme): the container starts as root and drops privileges itsel…
richard-epsilla Aug 23, 2026
59494f6
ci: start checks
richard-epsilla Aug 23, 2026
ecc667a
Merge pull request #20 from HarnessRouter/docs/0.8.2-privilege-note
richard-epsilla Aug 23, 2026
5243e6a
fix(selfhost): share links work — the resolver no longer needs a host…
richard-epsilla Aug 23, 2026
abafdf3
ci: start checks
richard-epsilla Aug 23, 2026
1658521
feat(runner): support --plugin-dir and stdio MCP servers for Claude C…
aug2uag Aug 23, 2026
7d9f1fc
Merge pull request #21 from HarnessRouter/fix/selfhost-share-links
richard-epsilla Aug 23, 2026
faee7f1
Merge branch 'main' into feat/claude-plugin-dir
aug2uag Aug 23, 2026
95b96d7
Merge pull request #22 from aug2uag/feat/claude-plugin-dir
richard-epsilla Aug 23, 2026
d44d256
feat(gateway): upload a local harness to a hosted workspace
richard-epsilla Aug 23, 2026
921337d
feat(console): upload a harness to the cloud
richard-epsilla Aug 23, 2026
4c49c79
fix(console): the upload chip's effect runs above every early return
richard-epsilla Aug 23, 2026
3dd73a0
docs(conformance): full class, 52/52, on the cloud-upload build (rele…
richard-epsilla Aug 23, 2026
da49afb
ci: start checks
richard-epsilla Aug 23, 2026
77408f0
fix(console): select mode tells the truth when only built-ins are listed
richard-epsilla Aug 23, 2026
5316c8a
fix(console): the upload action lives on the floating footer, and but…
richard-epsilla Aug 23, 2026
6f0318d
fix(console): the selection bar is fixed to the viewport, and the dia…
richard-epsilla Aug 23, 2026
4e1e6bb
fix(gateway,console): an upload lands wherever the key points, and th…
richard-epsilla Aug 23, 2026
427d27f
feat(console,gateway): stored cloud workspaces with a picker, and no …
richard-epsilla Aug 23, 2026
dc08325
fix(console): selection has no mode
richard-epsilla Aug 23, 2026
077b26a
fix(gateway): a stored destination heals its label once the cloud can…
richard-epsilla Aug 23, 2026
ae14b8b
fix(gateway,console): a revoked cloud key says so
richard-epsilla Aug 23, 2026
9a27cd1
fix(console): Add is the verb
richard-epsilla Aug 23, 2026
5595312
fix(console): the upload chip is its own line in the harness cell
richard-epsilla Aug 23, 2026
99bdf32
fix(console): the chip's label is not a block
richard-epsilla Aug 23, 2026
77ea276
fix(console): the dialog says only what it knows, and trims what the …
richard-epsilla Aug 23, 2026
86b593a
fix(runner): a skill keeps its name, and the doc says where a skill's…
richard-epsilla Aug 23, 2026
136568a
fix(runner): the agent doc gives each skill's folder, absolutely
richard-epsilla Aug 23, 2026
b2eaf59
site: per-page meta descriptions, self-canonicals, and llms.txt
Aug 23, 2026
be45c7d
Merge pull request #26 from HarnessRouter/seo/uhp-per-page-descriptions
kuanzema Aug 23, 2026
e79cb08
fix(runner): skills go where each CLI's own loader looks
richard-epsilla Aug 23, 2026
77177f5
site: root-absolute internal links — fixes 34 broken chapter links on…
kuanzema Aug 23, 2026
1c5fc09
Merge remote-tracking branch 'origin/main' into feat/cloud-upload
richard-epsilla Aug 24, 2026
6990540
Merge pull request #24 from HarnessRouter/feat/cloud-upload
richard-epsilla Aug 24, 2026
3174d51
fix: the discovery document reports the version this build is
richard-epsilla Aug 24, 2026
a8f8edc
ci: start checks
richard-epsilla Aug 24, 2026
db78b95
Merge pull request #28 from HarnessRouter/fix/version-in-discovery
richard-epsilla Aug 24, 2026
cb502be
feat(runner): opencode backend — argv, normalizer, MCP and skills
richard-epsilla Aug 25, 2026
ffc959e
feat(runner,docker): opencode hard tool permissions + install path
richard-epsilla Aug 25, 2026
41a2bcc
fix(runner): opencode argv needs --pure and --thinking
richard-epsilla Aug 25, 2026
63a5fa5
feat(gateway): register the opencode base so it is routable
richard-epsilla Aug 25, 2026
e9715c8
fix(docker): opencode installs by default, and the backend docs were …
richard-epsilla Aug 25, 2026
a56977f
fix(ui): opencode was missing from the console built-in list
richard-epsilla Aug 25, 2026
caa1b7c
fix(ui): add opencode to the backend union
richard-epsilla Aug 25, 2026
548d3c8
fix: opencode reads AGENTS.md, and the console base lists had drifted
richard-epsilla Aug 25, 2026
52f1c6a
chore: trigger checks on #29
richard-epsilla Aug 25, 2026
1392602
feat: opencode serves the full multi-family model set
richard-epsilla Aug 25, 2026
7cb9659
fix(runner): opencode config must match the RELEASED schema, not the …
richard-epsilla Aug 25, 2026
ff22f72
fix(runner): opencode turns must synthesize a result at end of stream
richard-epsilla Aug 25, 2026
d39eade
fix(ui): deleting an integration must prune every map that names it
richard-epsilla Aug 25, 2026
6a18866
fix: Delete on a model mapping was a no-op, and a JSX escape rendered…
richard-epsilla Aug 25, 2026
c846851
fix(runner,gateway): stale PWD broke every opencode turn; built-in ha…
richard-epsilla Aug 25, 2026
03be642
fix(runner): opencode.json must not be a deliverable
richard-epsilla Aug 25, 2026
091e423
feat(ui): real OpenCode logo
richard-epsilla Aug 25, 2026
362535a
fix(gateway): a turn orphaned by a restart settles in minutes, not si…
richard-epsilla Aug 25, 2026
d428000
fix(gateway): settle the response record with its session, and never …
richard-epsilla Aug 25, 2026
4d77ddf
fix(runner,gateway): the collection cursor — checkpointed must never …
richard-epsilla Aug 26, 2026
f0d5661
Merge pull request #29 from HarnessRouter/feat/opencode-backend
richard-epsilla Aug 26, 2026
f118626
site: version-addressable spec URLs, TOC scroll-spy, and repo→web bui…
Aug 26, 2026
cfd2f8a
Merge branch 'main' into site/implementations-and-search
kuanzema Aug 26, 2026
856d62c
Merge pull request #32 from HarnessRouter/site/implementations-and-se…
kuanzema Aug 26, 2026
72429e6
docs: add Devin backend design spec
chenillen Aug 26, 2026
3b96dc5
docs: align Devin backend spec with existing backend patterns
chenillen Aug 26, 2026
b809a8e
docs: use official Devin install script in backend spec
chenillen Aug 26, 2026
5f8d2a2
docs: add Devin backend implementation plan
chenillen Aug 26, 2026
8211e3f
feat(runner): add Devin ACP normalizer and tests
chenillen Aug 26, 2026
2e6dfad
feat(runner): add Devin ACP driver and fake server tests
chenillen Aug 26, 2026
7a822da
feat(runner): wire Devin backend into registry and dispatch
chenillen Aug 26, 2026
3c4dc0d
fix(runner): include devin in agent-doc and tools-disabled paths
chenillen Aug 26, 2026
9e85896
feat(gateway): add devin harness base, model catalog, and provider
chenillen Aug 26, 2026
d9d3268
fix(gateway): include devin in bare-model fallback set
chenillen Aug 26, 2026
b14f75e
feat(ui,docker): add Devin harness entry, logo, and runtime CLI insta…
chenillen Aug 26, 2026
3c3c983
fix(ui): silence tsconfig deprecation warnings and update Devin logo
chenillen Aug 26, 2026
16cc8c5
fix(runner): call ACP authenticate before devin sessions
chenillen Aug 26, 2026
ed12972
refactor(runner): rename devin credential env from WINDSURF_API_KEY t…
chenillen Aug 26, 2026
56be132
fix(docker,ui): build and install Devin in container
chenillen Aug 26, 2026
3565ff0
gateway: dynamically load Devin ACP models from server.codeium.com
chenillen Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/open-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Open a pull request as github-actions[bot].
#
# Main requires a pull request, one approval, and green checks, with no bypass for anyone. A
# pull request cannot be approved by its own author, so an operator who works alone (or through
# an agent using their account) could never merge. This workflow lets the agent open the PR
# under the bot identity instead; the operator is then a reviewer, not the author, and approves
# like anyone else.
#
# Trigger it with the branch to open, e.g.
# gh workflow run open-pr.yml -f branch=fix/x -f title="..." -f body="..."
#
# A PR opened with GITHUB_TOKEN does not start the checks workflow by itself (GitHub's guard
# against recursive runs). The next push to the branch does, and the agent makes one; an empty
# commit is enough.
name: open-pr
on:
workflow_dispatch:
inputs:
branch:
description: head branch to open against main
required: true
title:
description: pull request title
required: true
body:
description: pull request body (markdown)
required: false
default: ""
permissions:
contents: read
pull-requests: write
jobs:
open:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ inputs.branch }}
TITLE: ${{ inputs.title }}
BODY: ${{ inputs.body }}
run: |
existing=$(gh pr list --head "$BRANCH" --state open --json number -q '.[0].number')
if [ -n "$existing" ]; then echo "PR #$existing already open for $BRANCH"; exit 0; fi
gh pr create --base main --head "$BRANCH" --title "$TITLE" --body "$BODY"
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ jobs:
build-args: |
HR_SKILLS_REF=${{ needs.plan.outputs.skills }}
HR_KITS_REF=${{ needs.plan.outputs.kits }}
HR_VERSION=${{ needs.plan.outputs.version }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
Expand All @@ -163,7 +164,7 @@ jobs:
docker logs smoke 2>&1 | tail -30
LINE=$(docker logs smoke 2>&1 | grep "backends available" | tail -1 || true)
echo "$LINE"
for b in claude codex hermes; do
for b in claude codex hermes pi dsh; do
echo "$LINE" | grep -q "$b" || { echo "::error::$b did not install on ${{ matrix.arch }}"; exit 1; }
done
docker exec smoke ls /opt/harnessrouter/kits
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Run every test suite the repository carries, on every pull request and every push to main.
#
# This exists because until now nothing did: `release.yml` gates what ships, but the tests in
# gateway/tests, runner/tests, protocol/conformance/tests and ui only ran when someone remembered
# to run them — which is how a green-looking PR carries a red suite. CONTRIBUTING.md's
# "Development checks" section tells contributors which commands to run; this workflow runs the
# same commands, so the two cannot drift apart without this file changing.
#
# One job per suite, because they fail for unrelated reasons and a contributor should see WHICH
# area broke without reading logs: a gateway failure is not a console failure. No job depends on
# another; they all run in parallel and each is a required signal on its own.
name: tests

on:
pull_request:
push:
branches: [main]

jobs:
gateway:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: gateway/requirements.txt
# The media tests build real fixtures (a one-second mp4, an mp3 with a readable duration)
# and gate themselves on `have_ffmpeg()` — without ffmpeg they silently skip, and a media
# regression would ride a green run. Installing it makes them actually run here.
- name: Install ffmpeg for the media fixtures
run: sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg
# jsonschema is test-only: test_response_error_envelope.py validates failed Responses
# against the spec schema. It stays out of requirements.txt so it never ships in the image.
- run: pip install -r gateway/requirements.txt pytest jsonschema
- run: python -m pytest gateway/tests -q

runner:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: runner/requirements.txt
# httpx: the runner suite drives the app through FastAPI's TestClient, which needs it. The
# runner itself does not, so it stays out of requirements.txt and lives with pytest here.
- run: pip install -r runner/requirements.txt pytest httpx
- run: python -m pytest runner/tests -q

conformance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: protocol/conformance/pyproject.toml
- run: pip install -e protocol/conformance pytest
- run: python -m pytest protocol/conformance/tests -q

console:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ui
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: ui/package-lock.json
# Mirrors the Dockerfile's install: .npmrc carries legacy-peer-deps, without which npm
# refuses the tree (several deps still declare React 18 peers while the app runs 19).
- run: npm ci --no-audit --no-fund || npm install --no-audit --no-fund
- run: npm run type-check
# The console has no jest tests yet, and bare `jest` exits 1 on "No tests found" — which
# would read as a test failure when the truth is an empty suite. The flag makes an empty
# suite pass and a failing test still fail; the first real test file makes it moot.
- run: npm test -- --passWithNoTests
- run: npm run build

# The unifiedharnessprotocol.org site is generated from protocol/ markdown by
# protocol/site/build.py and deployed by Vercel. Nothing gated it until now — a broken generator,
# a dead cross-link, a missing #anchor, or a description-manifest violation would reach the
# production deploy with a green PR. This job runs the SAME build.sh the Vercel deploy runs, from
# the same working directory (protocol/), so the gate and the deploy cannot diverge; build.py
# self-checks links, anchors, versions and metadata, so the build failing is the signal.
site:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Read the interpreter from the same .python-version the Vercel build reads, so CI and the
# deploy can never run different Pythons.
- uses: actions/setup-python@v5
with:
python-version-file: protocol/.python-version
cache: pip
cache-dependency-path: protocol/site/requirements.txt
- name: Build the spec site exactly as the deploy does
working-directory: protocol
run: bash site/build.sh
# The single host config gates the deploy; a malformed vercel.json, or one whose build no
# longer points at build.sh, would break publishing silently. Assert it stays valid and wired.
- name: The host config stays valid and wired to build.sh
run: |
python3 -c "import json; c=json.load(open('protocol/vercel.json')); assert c['buildCommand']=='bash site/build.sh', 'buildCommand drifted from build.sh'; assert c['outputDirectory']=='site/dist', 'outputDirectory drifted'"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ __pycache__/
protocol/site/dist/

protocol/site/dist/
*.egg-info/
66 changes: 62 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Contributions are welcome, and so are questions.

This project is two connected open-source layers: the Unified Harness Protocol (UHP), an open
standard for how products work with agent harnesses, and this repository, a working implementation
of it. Both take contributions — the specification, the conformance suite, harness support, the
implementation, and the documentation.

## Where things go

- Proposals and bug reports: GitHub Issues.
Expand All @@ -10,14 +15,67 @@ Contributions are welcome, and so are questions.

## Substantial changes: describe before you build

For anything beyond a small fix, open an issue first. Describe the problem you are solving, the change you propose, and its expected impact, before writing the implementation. Once a maintainer agrees on the direction, either you or a maintainer implements it.
For anything beyond a small fix, open an issue first. Describe the problem you are solving, the
change you propose, and its expected impact, before writing the implementation. Once a maintainer
agrees on the direction, either you or a maintainer implements it.

Small, obvious fixes such as typos or a clear bug with an equally clear fix can go straight to a pull request.
Small, obvious fixes such as typos or a clear bug with an equally clear fix can go straight to a
pull request.

## Changes to the protocol

The Unified Harness Protocol has a stricter process, because a specification, a reference implementation, and a conformance suite have to stay in step. If your change touches the protocol, follow [protocol/GOVERNANCE.md](protocol/GOVERNANCE.md): open a UHP Enhancement Proposal (UEP) as an issue labelled `uep` with Problem, Proposal, Compatibility, and Alternatives. Maintainers respond within 10 working days. An accepted UEP ships as one pull request that updates the specification, the schema, the reference implementation, a conformance test, and the changelog together.
The Unified Harness Protocol has a stricter process, because a specification, a reference
implementation, and a conformance suite have to stay in step. If your change touches the protocol,
follow [protocol/GOVERNANCE.md](protocol/GOVERNANCE.md): open a UHP Enhancement Proposal (UEP) as
an issue labelled `uep` with Problem, Proposal, Compatibility, and Alternatives. Maintainers
respond within 10 working days. An accepted UEP ships as one pull request that updates the
specification, the schema, the reference implementation, a conformance test, and the changelog
together.

## Pull requests

Keep each pull request to one problem. Before requesting review: link the issue when one exists;
add or update tests for behavior changes; update the documentation when the public API, protocol,
configuration, or user workflow changes; call out compatibility, security, or licensing
implications; and check that no credentials, generated dependencies, or unrelated files ride
along.

## Development checks

Run the checks that match the area you changed.

For the console:

```bash
cd ui
npm ci
npm run type-check
npm test
npm run build
```

For the gateway:

```bash
python -m pytest gateway/tests
```

For container-level changes, build the image and walk the documented self-hosted flow before
requesting review.

## License

By contributing, you agree that your contribution is licensed under the Apache License 2.0, the same license as this repository.
By contributing, you agree that your contribution is licensed under the Apache License 2.0, the
same license as this repository.

## How changes land on main

`main` takes pull requests only: one approval, every check green, no bypass for anyone, admins
included. A pull request cannot be approved by its own author, so an agent working through a
maintainer's account opens its pull requests with the `open-pr` workflow instead, which creates
them as `github-actions[bot]`; the maintainer then reviews and approves like any other change.

```bash
gh workflow run open-pr.yml -f branch=<branch> -f title="<title>" -f body="<body>"
git commit --allow-empty -m "ci: start checks" && git push # a bot-opened PR needs one push to run CI
```
28 changes: 20 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
# They land in the data volume, so the cost is paid once per volume rather than once per start.
# Choose backends at RUN time:
#
# docker run -e HR_BACKENDS=claude,codex,hermes ... # default: all three
# docker run -e HR_BACKENDS=claude ... # lean
# docker run -e HR_BACKENDS=claude,codex,hermes,pi,dsh,opencode ... # the default
# docker run -e HR_BACKENDS=opencode ... # lean
#
# WITH_BROWSER is still a build arg because Chromium and its system libraries genuinely belong
# in the image layer.
Expand All @@ -27,7 +27,7 @@
# This is the SAME console the hosted product runs. Surfaces with no self-hosted backend
# (billing, marketplace, analytics, sign-in) are hidden by the edition flag rather than removed,
# so the two stay one codebase. See ui/src/lib/edition.ts.
FROM node:20-slim AS ui
FROM node:22-slim AS ui
WORKDIR /ui
# git: the UI depends on the ReifyUI component library straight from its repository.
RUN apt-get update -y && apt-get install -y --no-install-recommends git ca-certificates \
Expand Down Expand Up @@ -84,8 +84,10 @@ RUN set -eux; \
&& rm -rf /var/lib/apt/lists/*; \
fi

# Node is needed for the UI server and for the npm-based agent CLIs.
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
# Node is needed for the UI server and for the npm-based agent CLIs. 22, not 20: pi's
# engine floor is >=22.19, and node 20 has been end-of-life since April 2026 anyway —
# the other CLIs (claude >=18, codex >=20) run unchanged on 22.
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* /root/.npm

Expand Down Expand Up @@ -139,6 +141,13 @@ RUN chmod +x /tmp/install-skills.sh \
# Starter Kits: a Harness plus an app, both baked in. Same pull-and-pin shape as the skills
# bundle above, and the same rule: pass a commit sha to get the newest catalogue, because that
# is what changes the layer's inputs.
# What this build IS, for the discovery document and anything else that reports a version. The
# release workflow passes the tag; a local build says so honestly rather than claiming a number.
# Without this the gateway fell back to a literal that had been six releases stale, so every
# install told clients it was 0.3.0 (found by the 0.9.0 release sanity run).
ARG HR_VERSION=dev
ENV HR_VERSION=${HR_VERSION}

ARG WITH_STARTER_KITS=1
ARG HR_KITS_REPO=https://github.com/HarnessRouter/starter-kit.git
ARG HR_KITS_REF=main
Expand All @@ -160,8 +169,12 @@ COPY --from=ui /ui/.next/standalone /app/ui/
COPY --from=ui /ui/.next/static /app/ui/.next/static
COPY --from=ui /ui/public /app/ui/public

# The agent CLIs refuse to run as root (they gate their own permission bypass on it), so the
# runtime user is unprivileged and owns the workspace and data volume.
# `agent` is the PRODUCT's user: the gateway and the console run as it and it owns the data
# volume. The container itself starts as root and the entrypoint drops privileges per process,
# because the runner needs root for one thing: every agent CLI runs as its own per-session uid,
# which owns its session directory and nothing else (the write-wall; see docker/entrypoint.sh).
# The CLIs refuse to run as root anyway (they gate their own permission bypass on it), and they
# never do.
#
# The `rm -rf /home/agent/.npm` is not tidying. HOME is already /home/agent while the kit apps are
# built above, and those builds run as root — so npm leaves ~1600 root-owned files in the cache
Expand All @@ -179,7 +192,6 @@ RUN useradd -m -u 10001 agent \
&& mkdir -p /data \
&& chown -R agent:agent /data /app /home/agent \
&& chmod -R a+rX /opt/harnessrouter/skills /opt/harnessrouter/kits
USER agent

EXPOSE 3000
VOLUME ["/data"]
Expand Down
Loading