---
title: "Harness packs"
---

# Harness packs

Publish signed harness packs in Neural Inverse Govern, gate them on evals, assign them to the organisation, teams and projects, and run A/B experiments with staged rollout.

A harness pack is the developer-experience layer the Neural Inverse Editor puts on top of an agent engine (Claude Code, Codex or the Neural Inverse engine): instructions, skills, subagents, tools and MCP servers, context sources, workflow rules, model routing and evals. Govern stores packs, signs every published version, decides which version each organisation, team and project gets, runs experiments and staged rollouts, aggregates outcome metrics and gates promotion on evals.

**Packs are not policy.** Agent policy and other governance are evaluated separately and always win; nothing in a pack can weaken them.

The console service is **Harness**, with the Harness and Experiments pages and Harness usage under Monitor. Routes are under `/api/v1/orgs/{orgID}/harness`.

## Layers

| Layer | Published by | Parent |
|---|---|---|
| Starter (`claude-starter`, `codex-starter`, `neural-inverse-starter`) | Neural Inverse (platform) | None |
| Sector pack (for example `firmware`) | Platform, or an organisation | A starter |
| Company layer | Your organisation | A starter or a sector pack |
| Project layer | A file in the repository, resolved by the Editor only | None |

A layer may **lock** fields (for example `instructions.never_touch`). Govern refuses to publish a lower layer that sets a locked field, and checks locks again whenever a version is assigned.

## Permissions

| Permission | Allows | Built-in roles |
|---|---|---|
| `harness:read` | List packs and versions, read assignments, experiments, metrics and eval runs; receive one's own pack | Owner, Admin, Member |
| `harness:publish` | Create and archive packs, publish versions, change version status | Owner, Admin |
| `harness:assign` | Set and remove assignments | Owner, Admin |
| `harness:experiment` | Start experiments, advance rollouts, promote, roll back, stop | Owner, Admin |
| `harness:eval:submit` | Post eval runs | Only eval runner keys |

`harness:eval:submit` is matched exactly: no role, wildcard or ordinary API key includes it.

## 1. Configure signing (operator)

```bash
openssl genpkey -algorithm ed25519 -out /etc/keystone/harness-signing.pem
chmod 600 /etc/keystone/harness-signing.pem
export NI_AUTH_HARNESS_SIGNING_KEY_FILE=/etc/keystone/harness-signing.pem
export NI_AUTH_HARNESS_SIGNING_KEY_ID=hk-2026-10            # optional
```

| Variable | Purpose |
|---|---|
| `NI_AUTH_HARNESS_SIGNING_KEY_FILE` or `NI_AUTH_HARNESS_SIGNING_KEY_PEM` | Active Ed25519 private key. Without it, packs can be read and assigned but not published (`503 signing_unavailable`). |
| `NI_AUTH_HARNESS_SIGNING_KEY_ID`, `NI_AUTH_HARNESS_SIGNING_KEY_NOT_BEFORE` | Key id and start of validity |
| `NI_AUTH_HARNESS_RETIRED_KEYS_FILE` | Rotated-out public keys, each with `not_after` |
| `NI_AUTH_HARNESS_SEED_DIR` | Platform manifests published at startup |
| `NI_AUTH_HARNESS_ACTIVITY_RETENTION_DAYS` | How long Editor activity events are kept (default 395, at least 91) |

The public keys are at `GET /.well-known/neuralinverse-harness-keys.json`. The Editor verifies every layer with that set and refuses unsigned or tampered packs. Without a seeded starter for an engine, a person with no assignment gets `404 no_harness`.

## 2. Publish

```bash
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/harness/packs" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"slug": "acme-firmware", "kind": "company"}'

curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/harness/packs/$PACK_ID/versions" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/yaml' \
  --data-binary @acme-firmware.yaml
```

- Each version must be a valid `neuralinverse.harness/v1` manifest, greater than every earlier version, with a parent range that matches a published version.
- Sector and company versions must declare at least one eval.
- Secrets are references only: MCP server `env_refs` values must be `vault:<name>`, and any field named like a credential is refused.
- Manifests are limited to 1 MiB, 200 skills, 50 subagents, 50 MCP servers and 100 evals.
- `?draft=true` stores an unsigned draft; publish it later with `PATCH .../versions/{version}` and `{"status": "published"}`.

Published versions never change. Their status can become `deprecated` (still served where assigned) or `withdrawn` (final, never served or assigned).

## 3. Pass the eval gate

A version can be assigned, run in an experiment or promoted only when it passes the promotion gate on the target engine: a passing eval run recorded for your organisation, or, for a platform version, by a platform runner. Starters are exempt.

Eval runs come from a runner (CI or a sandbox) with an eval runner key. Only a platform super administrator creates runner keys:

```bash
curl -sS -X POST "$KEYSTONE_URL/api/v1/harness/platform/runner-keys" \
  -H "Authorization: Bearer $SUPER_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"organization_id": "'$ORG_ID'", "name": "acme-ci", "expires_at": "2027-10-01T00:00:00Z", "reason": "Nightly evals for Acme"}'
```

The runner posts results:

```bash
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/harness/packs/$PACK_ID/versions/1.0.0/eval-runs" \
  -H "Authorization: Bearer $RUNNER_API_KEY" -H 'Content-Type: application/json' \
  -d '{"engine": "claude-code", "model": "claude-sonnet-5-5", "logs_ref": "ci://runs/1842",
       "results": [{"eval_id": "uart-bringup", "passed": true}]}'
```

Every declared eval needs exactly one result; the run passes when all pass. A super administrator can bypass the gate with `override_reason`, which is audited as `harness.eval_gate.overridden`.

## 4. Assign

```bash
curl -sS -X PUT "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/harness/assignments" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "team", "scope_id": "'$TEAM_ID'", "pack_id": "'$PACK_ID'", "version": "1.0.0", "engine": "claude-code"}'
```

`scope` is `org`, `team` or `project` (a repository identifier such as `github.com/acme/fw`). Parent ranges are resolved to exact versions when you assign, so a newer starter changes nothing until you assign again. The most specific assignment wins: project, then team, then organisation. Remove one with `DELETE .../assignments?scope=team&scope_id=<team id>`.

The Editor calls `GET /api/v1/me/harness?project=<repository>&engine=<engine>` at sign-in and every 15 minutes.

## 5. Experiment and roll out

```bash
curl -sS -X POST "$KEYSTONE_URL/api/v1/orgs/$ORG_ID/harness/experiments" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scope": "org", "engine": "claude-code",
       "control": {"pack_id": "'$PACK_ID'", "version": "1.0.0"},
       "candidate": {"pack_id": "'$PACK_ID'", "version": "1.1.0"},
       "candidate_percentage": 5, "bucket_unit": "user", "min_sample": 100}'
```

Each person (or project, with `bucket_unit: project`) lands in a sticky bucket; buckets below the candidate percentage get the candidate. Both arms must pass the eval gate.

| Action | Route |
|---|---|
| Raise the rollout (it can only go up) | `POST .../experiments/{id}/rollout` with `{"candidate_percentage": 25}` |
| Promote the candidate | `POST .../experiments/{id}/promote` |
| Roll back to the control | `POST .../experiments/{id}/rollback` |
| Stop without a decision | `POST .../experiments/{id}/stop` |
| Results per arm | `GET .../experiments/{id}/results` |

Results show metrics and deltas only once both arms reach `min_sample`. `GET .../packs/{packID}/versions/{version}/metrics` aggregates sessions, suggestions kept, edited and discarded, tasks completed, median duration, tokens and cost. Editor activity events carry identifiers, counts and outcomes only, so no code, prompt or completion is stored.

## Audit

Pack, version, assignment, experiment, eval run and runner key changes are audited inside the transaction of the change (`harness.pack.created`, `harness.version.published`, `harness.assignment.set`, `harness.experiment.promoted`, `harness.eval_run.recorded` and others). On the hosted service, custom packs and experiments depend on the plan (`403 not_entitled`); self-hosted includes everything.
