Breaking Changes

  • HarnessLink is now proprietary software. Its own code is licensed under the HarnessLink End-User Licence in LICENSE (use with a HarnessLink account; no reverse engineering except where the law allows it, no redistribution, no circumventing limits, policies or licence and integrity checks, no building a competing service), and the Terms of Service say the same in plain language. Code inherited from oh-my-pi and pi stays under the MIT licence with its copyright notices (LICENSES/MIT-oh-my-pi.txt), as do the LiteLLM-derived model prices (LICENSES/MIT-litellm.txt); NOTICE.md lists exactly which files that covers. hnl --license prints all of it, the release tarball is never built without these files, the installers point to them, and the package license fields read SEE LICENSE IN ../../LICENSE (packages/utils, wholly upstream-derived, stays MIT).
  • HarnessLink installs as one compiled binary (ADR-77). Each release builds a minified hnl executable per platform (macOS, Linux incl. musl, Windows; arm64 and x64) instead of a source tree: it installs to ~/.hnl/bin/hnl with the verified release manifest beside it, hnl on PATH links to it, and the background service runs from it. The installer and the automatic updater move an installer-made source install (~/.hnl-src, ~/.superpi-src) onto the binary and remove the old tree (a git checkout is never touched); every reinstall keeps the previous binary as hnl.previous. There is no bun install at install or update time, and no new source tarball is cut — the last one stays served only as the bridge pre-binary updaters update through.

Added

  • hnl doctor (ADR-72). One command that finds and fixes what keeps HarnessLink from working on this machine, and lists each fix: the service (installed, running, on this CLI's build, auto-update failures, release integrity), sign-in (account, org, linked device), capture lists, prompt-history ingest, outboxes (failed items requeued, missing capture triggers reinstalled, a sync now), session uploads, cloud parity, the org policy cache (refreshed when missing, invalid or expired), context/policy hooks and hnl MCP entries (reinstalled), the MCP hub and gateway (service restarted), leftovers from before the rename, and an integrity check of HarnessLink's databases (a damaged one gets a backup-and-recover command; nothing is deleted). --check diagnoses and changes nothing, --only <area>, --json; exit 1 while problems remain. It catches the pattern where memories keep syncing while prompt history and transcripts don't: a harness HarnessLink banks memories from but doesn't capture is added to cloud.harnesses, and a history/sessions/memories category switched off without a record is explained (on by default, so it was switched off on this machine) with the command to turn it back on; an interactive run asks.
  • Opt-outs are dated: turning a sync category off (hnl cloud sync --disable, setup's Customize screen), a harness's capture off (hnl cloud harnesses disable, hnl harness select) or its context hooks off (hnl harness context --remove) records when in cloud.offByChoice in config.yml. hnl doctor shows those as "off by your choice (since <date>)" and never undoes them.
  • The service runs hnl doctor --check every 30 minutes and after a failed sync pass and records ~/.hnl/doctor.json. When something is off, hnl and hnl cloud status show one "needs attention" line with the command to run, and the next harness session starts with one line such as "HarnessLink: Prompt history sync is turned off on this machine while memories still sync; run hnl cloud sync --enable history." (each problem at most once a day).
  • Device health on the dashboard: the service posts a summary of each check to POST /v1/devices/self/health (finding codes, one-line summaries and commands; never paths or prompts; control migration 0051 stores it on devices), and GET /v1/devices/self/health returns the newest history, session and memory the cloud stored from the device (tenant migration 0027 records which device uploaded each session). The cloud flags server.history_stalled on its own when a machine's memories still arrive but none of its prompts have for two days, unless history is off on purpose there. Devices marks such machines "Needs attention: run hnl doctor on this machine" with every problem and its command, and Overview shows a banner naming them.
  • Signed releases (ADR-76). Every release now carries /hnl/manifest.json (version, commit, and the SHA-256 and size of each binary) and /hnl/manifest.json.sig, an Ed25519 signature made with a release key kept in one file outside the repo. install.sh (OpenSSL 3, or perl on a stock Mac), install.ps1 and the automatic updater check the signature and every downloaded file against it, and refuse to install a release that is unsigned, signed by an unknown key, or modified, with a one-line reason. The background service checks a minute after it starts and daily that the running hnl is byte-for-byte the signed release (~/.hnl/integrity.json); hnl doctor reports a modified binary (client.modified, fix: hnl update --force) and sends the result with its health report. The server flags such a device as "Modified client" on your Devices page, on the owner console and, when your organisation's Devices visibility switch is on, on the org's Devices list. Nothing is blocked by the flag; operators can turn on refusal with MODIFIED_CLIENT_POLICY = "refuse" (docs/CLOUD_SETUP.md, "Release signing"). Releasing needs the key: bun scripts/release-sign.ts keygen|embed|check|sign|verify, run by deploy:prod; rotation embeds two public keys during the overlap.
  • Org policy installed system-wide on Team and Enterprise (ADR-74). On a machine signed in to a Team or Enterprise organisation whose policy is on, HarnessLink also writes the policy into each harness's admin-owned settings, above anything in your own: Claude Code's managed-settings.d/50-harnesslink.json (SessionStart, UserPromptSubmit and PreToolUse hooks on the absolute hnl launcher, plus permissions.deny/ask for unconditional explicit tool and command rules), Codex's requirements.toml (the same hooks through a wrapper in hooks.managed_dir, [features] hooks = true, [rules] prefix_rules) and Cursor's enterprise hooks.json (the four policy hooks, fail-closed), plus a root-owned enrollment.json with the org and device ids. No token, signature or instruction text goes in them. hnl cloud setup installs them after sign-in with one administrator prompt; hnl policy managed [status|install|remove] shows, installs or removes them. The service keeps them current, elevating only when a file must change and at most once a day (macOS password dialog; on Linux and Windows the change waits for hnl policy managed install); a waiting change shows in hnl cloud status and the doctor. Leaving the organisation or a plan change removes them the same way; signing out does not. A file at one of these paths that HarnessLink did not write is left alone. When both the managed and your own hooks fire for one event, context is added once and a denial is recorded once. Each device reports its state (PUT /v1/devices/self/managed-settings, control migration 0052: devices.managed_settings, managed_settings_at), and Policies › Coverage lists it per device; GET /v1/org/devices returns managed_settings. The signed policy bundle now carries the org's paid plan (org.plan), and its ETag changes with it. Windows elevation is written but not yet exercised on Windows.
  • Org policy changes apply in seconds, and open sessions are told (ADR-73). The background service holds a WebSocket to the new GET /v1/policy/stream (one hibernating Durable Object per organisation on the server); every policy save on the dashboard, every platform-owner override and every organisation suspension or restore announces the organisation's new version on it, and the service fetches the signed bundle at once, so the next tool call is decided by the new rules (about 10 ms from save to cached on a local server, under 5 s required). The announcement carries only the organisation id and version, never policy, and changes nothing by itself: only a bundle that verifies is cached. The service reconnects with backoff, fetches again whenever the stream comes back, and still checks every minute, so a machine that can't hold the stream open gets changes within a minute. Every open Claude Code, Codex, omp and pi session gets one "HarnessLink policy updated (vN)" block on its next prompt: changed instructions in full, withdrawn ones by name and a line per rule or limit change, nothing when the change doesn't reach that session; what each session was last shown is kept in ~/.hnl/agent/policy-seen/. Org instructions at session start and in the update now say they come from your organisation through HarnessLink and take precedence over project and user instructions. pi's managed extension now adds the org's instructions at session start too.

Fixed

  • Editing local files can no longer switch org policy off. The policy now stays bound to the sign-in in ~/.hnl/cloud.token even when HNL_CLOUD_TOKEN is set, or the file's expires_at is edited (both used to read as "not signed in", so no policy was enforced); a cloud.token that can't be read or parsed blocks tool and model calls until hnl cloud login. On a machine with a managed enrollment (Team and Enterprise managed settings), only a bundle of that organisation verifies, and signing out keeps the cached policy in force instead of turning it off. A cloud.suspended file that exists but can't be read now counts as suspended, like a damaged one.
  • Every plan gate now answers the same way. Organisation invites, teams, shared artifacts, the agent inventory, organisation sharing switches and the organisation digest used to refuse Free and Pro with a bare feature_gated, so the dashboard could not show which plan unlocks them; they now name the feature (shared_org) and the plan (team). Sharing a project's memory checks shared_memory, and GET /v1/org/spend answers 403 feature_gated (member_spend, team) below Team instead of visibility_off. A new contract test calls every gated feature's endpoints on each plan below it and on its plan, asserts every plan limit at its boundary, and fails when a feature is added to the catalog without a row.
  • Free's 30-day history lock could be bypassed from the CLI: hnl cloud pull-sessions and history pulls fetched prompts and transcripts older than the window (and from other machines), which hnl resume could then open. The CLI read planes (GET /v1/sessions/index, GET /v1/sessions/index/:id, GET /v1/sync/history/changes) now apply the same lock as the dashboard: locked sessions are left out and counted, opening one answers 403 history_locked, and locked prompts are not sent. Upgrading makes them available again.

Update HarnessLink and every installed harness to its current release:

hnl update

New machine? Install with:

curl -fsSL https://harnesslink.sh/install.sh | bash

Newer: 17.5.98 · Older: 17.5.96 · All releases · Atom feed

Flag notifications