{"slug": "self-improvement-harness-based-on-pi-pi-harness-rsi", "title": "Self Improvement Harness based on pi. pi-harness-rsi", "summary": "A new pi extension, pi-harness-rsi, implements a harness self-improvement loop based on the RSI method from Harness² (arXiv:2610.14224), enforcing context economy through a single choke point in src/context.ts, src/guard.ts and src/ledger.ts so that no artifact entering an LLM context is silently truncated. The extension runs a snapshot → probe ×k → propose INV/DOM ×2k → probe ×2k → compose ×k → probe ×k → select → adopt → reflect cycle, invoked via /rsi commands such as start, probe, status, watch, adopt and revert, with adoption gated behind human confirmation and every state-changing command printing exactly one next command to type.", "body_md": "A **pi extension** for harness self-improvement (RSI, after Harness² / arXiv:2610.14224)\nwith **enforced context economy**: every artifact that may enter an LLM context is\nbudgeted by one choke point, and nothing is ever silently truncated.\n\nDesign: budgets, the editable surface, the safety model and a full example\nworkflow are documented below — and enforced in code (`src/context.ts`,\n`src/guard.ts`, `src/ledger.ts`) with an executable test suite, so every\ninvariant here is executable rather than aspirational.\nThe loop: `snapshot → probe ×k → propose INV/DOM ×2k → probe ×2k → compose ×k → probe ×k → select → adopt → reflect`.\n\n```\npi -e /path/to/pi-harness-rsi/index.ts          # one-off\npi install /path/to/pi-harness-rsi              # registers in settings\n```\n\nOne command, verb first. The verb may be omitted: then the whole line is a\n`start` task.\n\n```\n/rsi <verb> [flags] <args>\n```\n\n| Verb | What it does | \n|---|---|\n| *(none)* | **State view** — the in-flight run if there is one, otherwise the latest ledger and the exact command to type next. Never calls a model. | \n| `start <task>` | Full loop: probe → propose → probe → compose → probe → select → adopt → reflect. Flags: `--k <n>``--domain <name>``--mode parallel\\|sequential``--yes``--force` | \n| `probe <task>` | Reference executions + digests only (no editor, no edits, no adoption). Flags: `--k <n>``--domain <name>` | \n| `status [runId]` | The in-flight run, else the latest ledger: identity `R=4k, P=3k` , USD, fallbacks, diversity, budget usage. | \n| `watch [--once] [--lines <n>]` | Live view of the in-flight run: stage, elapsed, USD, latest notes — polls until the run closes. `--once` prints a single snapshot instead. | \n| `stop` | Interrupt the in-flight run before its normal end (see *Interruption* below). | \n| `adopt [label] [--run <id>] [--yes] [--force]` | Human gate: apply a composed surface, snapshot the library, reflect. | \n| `revert <domain>/<slug> [--force]` | Restore the previous adopted surface for one library entry, under the same guards as adoption. Must resolve, else it errors. | \n| `library [domain]` | Domains, entries, `INDEX.md` ,`EXPERIENCE.md` . | \n| `help` | The table above, in the terminal. | \n\n`/rsi` with no arguments is the entry point: it prints where things stand and\nwhat to type next.\n\n**The three commands you actually type** — everything else is for when you want\nto look:\n\n```\n/rsi probe fix the flaky session test                        # 1. what does the harness do today? (no edits)\n/rsi status                                                  #    read the digests, then...\n/rsi start --domain acme run the verifier after every edit    # 2. learn the habit (asks before adopting)\n/rsi                                                          # 3. what next? one exact command\n```\n\nEvery command that changes state ends by printing exactly one command to type\nnext, so the workflow is discoverable from the terminal (`/rsi` alone) and from\nthe end of any run.\n\n**Argument rules** (they are what keep the surface predictable):\n\n- The first word is a verb **only** if it is one of the lowercase verbs above.\nAnything else is task text, so`/rsi fix the flaky test` means`start` .\n- **Flags come before the task.** Everything from the first task word onward is\ntask text verbatim —`/rsi start --k 2 fix --verbose` has the task`fix --verbose` . (Verbs that take discrete arguments —`adopt` ,`status` ,`revert` — accept flags anywhere:`/rsi adopt final0 --run <id> --yes` .)\n- `--flag=value` works too, and`--` ends flag parsing when the task itself\nstarts with a dash:`/rsi start -- --verbose is not a flag` .\n- An unknown flag, a value flag with no value, or a value on a boolean is an error with the usage line — never a silent reinterpretation of the task.\n\nAdoption and reflection also maintain the library's health: a new lesson whose\ncue matches an older one **supersedes** it (the old entry stays on disk for\naudit, but stops entering missions), and every rendered lesson carries its\n`adopted:` date so the editor can weigh staleness.\n\nHeadless (`pi --print \"/rsi …\"`) mirrors every report to stdout; adoption stays\ngated — without `--yes` a headless run ends as `outcome: pending` and prints\nthe `/rsi adopt …` command to run next. There is no input loop to keep\nresponsive, so headless runs **await** the loop instead of detaching it: the\nprocess exits when the run has actually finished, not while it is still\nstarting.\n\nA `running` ledger whose owner pid (recorded in the ledger, same host) is no\nlonger alive is reported as orphaned — `/rsi status` says so and `/rsi watch`\ndoes not present it as in flight, so a crashed or killed run cannot masquerade\nas a live one.\n\nOnly one run can be in flight per project: `start`, `probe` and `adopt` refuse\nwhile another run is active (in this session or in another process — see the\nrun lock); `watch` inspects it and `stop` interrupts it.\n\n**Prerequisites:** pi ≥ 1.3, bun, the extension loaded (see *Load it*), and a\nverifier you trust — the whole loop is evidence-driven, so point `rsi.verifier`\nat the command that tells you whether the project is healthy:\n\n```\n// ~/.config/pi/settings.json  (or the project's .pi/settings.json)\n{ \"rsi\": { \"verifier\": \"bun test\", \"maxUsd\": 2 } }\n```\n\n**First run — look before you leap.** Start with a probe: it executes the *same\ntask* k times against the current surface in a throwaway worktree and writes\ndigests (errors, tool timeline, rework, verifier output). No editor, no edits:\n\n```\n/rsi probe fix the flaky session test\n```\n\nRead `/rsi status`. The digests under `.pi/harness/runs/<id>/digests/` tell you\nwhere the harness bleeds: repeated identical calls, failing checks, paths never\nread. That is the input the improvement loop will act on.\n\n**Then run the loop.** The task is not “the change you want” — it is the\ncapability you want the harness to acquire (it is what the reference executions\nmeasure):\n\n```\n/rsi start --domain acme-api run the verifier before every edit and re-read files after editing\n```\n\nWhat happens, stage by stage:\n\n1. `snapshot` — the surface (`AGENTS.md` +`.pi/skills` +`.pi/prompts` ) is read;\na git worktree is created under`.pi/harness/worktrees/` .\n2. `probe ×k` — k solver runs of the task; digests + session JSONL kept as evidence.\n3. `propose ×2k` — two*independent* editor sessions (INV lever, DOM lever) each\nreply with a`<edits>` delta over the surface; validator feedback is retried\nonce verbatim, then it falls back to the current surface.\n4. `probe ×2k` +`compose ×k` +`probe ×k` — each candidate is executed, a third\neditor session merges the best of both, the composed surface is executed again.\n5. `select` (k>1) — an anonymised judge (`c0` ,`c1` , …) picks from deliverables\nand digests only; labels and “why mine is better” text never reach it.\n6. `adopt` —**the human gate** : pi shows the diff and asks. Confirm to update\nthe live tree and write the library snapshot; decline and nothing changes\n(the candidate stays in`surfaces/` for later`/rsi adopt` ).\n7. `reflect` — one`EXPERIENCE.md` entry (cue / lesson / evidence / confidence,\ncaps enforced) so the next run in this domain starts smarter.\n\n**Iterate.** Repeat `/rsi start` with a new task in the same domain; each run\nfeeds on the previous library (`INDEX.md` + top lessons enter the propose\nmission, still budget-capped). Check or undo with:\n\n```\n/rsi status                     # latest run + the exact next command\n/rsi library acme-api           # domains, INDEX.md, EXPERIENCE.md\n/rsi adopt final0 --run <id>    # apply a candidate you left pending\n/rsi revert acme-api/<slug>     # restore the previous adopted surface\n```\n\n**Headless / scripted:**\n\n```\npi --print \"/rsi probe --domain acme-api $TASK\"          # evidence only, silent adoption impossible\npi --print \"/rsi start --domain acme-api --yes $TASK\"    # explicit opt-out of the gate\n```\n\n**Cost & knobs:** a k=1 run is 4 solver probes + 3 editor sessions (7 child-`pi`\ncalls, each capped by `stageTimeoutMs`); with small models that is typically a\nfew tens of cents, and the run aborts fail-closed at `maxUsd` (default $2).\nBecause a free model spends `$0` and still takes time, a run is also capped by a\nwall clock: `maxRunMinutes` (default **5**, `PI_RSI_MAX_MINUTES` to override)\nends the run with `outcome: aborted_timeout`, killing the in-flight stage rather\nthan waiting for the next one. `--k 2` doubles both sides of the identity\n(R=8, P=6) and buys a real selection stage. `PI_RSI_FAKE=1` exercises the entire\nloop with **zero** model calls.\n\nScenario: `acme-api` (TypeScript). The agent keeps editing without running the\ntests and re-reads nothing, so it repeats the same failing check three times per\nsession. You want the *harness* to learn the habit, not a one-off reminder.\n\n``` bash\n$ cd ~/code/acme-api && pi\n\n▸ /rsi probe add a rate-limit header to the login endpoint\n  … 1 reference run, digest in .pi/harness/runs/20261018-121500-add-a-rate-limit/digests/ref0.md\n\n▸ /rsi status\n  run 20261018-121500-add-a-rate-limit  task: add a rate-limit header to the login endpoint\n  ledger: R 1 rollouts (probe-only run — R=4k/P=3k identity does not apply)\n  usd: 0.0412 / max 2 (setting)\n  outcome: probed\n  notes:\n    - ref0: # digest: ref0 (model=…, 96s, turns=11) — 1 deliverable file(s), verifier=yes\n\n▸ cat .pi/harness/runs/20261018-121500-add-a-rate-limit/digests/ref0.md\n  # digest: ref0 (model=…, 96s, turns=11)\n  tools: bash×4 (2 err) edit×2 read×5\n  errors:\n    1. bash exit=1 @step=3  window: \"FAIL src/auth.test.ts …\"\n  deliverable: src/auth.ts | git-diff: +41 −12\n  verifier: bun test exit=1\n  first_divergence: @step=3 hypothesis: bash failed first — the rest of the run reacts to it\n  rework: 2 consecutive identical grep calls\n\n  → the digest shows the real defect: 2 identical greps, 2 failing checks, no re-read.\n\n▸ /rsi start --domain acme-api run the verifier after every edit and re-read files before the next edit\n  rsi: profile=small k=1 mode=parallel fake=0 maxUsd=2 maxRun=5m confirmAdopt=1\n  [rsi] final0: # digest: final0 (model=…, 88s, turns=9) — diff 2 file(s) changed\n  rsi: adopt final0?\n      adopt final0: added 2, removed 0, modified 1, always-on Δ +312 chars, surface Δ +1 104 chars\n        + .pi/skills/acme-api-notes/SKILL.md\n        + .pi/skills/acme-api-notes/scripts/check.sh\n        ~ AGENTS.md\n      The live tree is updated and a library snapshot is written.\n  ✔ Yes\n  rsi: adopted final0 · USD 0.0873 · R 4/4 P 3/3 identity OK\n  /rsi library acme-api\n\n▸ git status\n  M AGENTS.md\n  ?? .pi/skills/acme-api-notes/    # review and commit these like any other change\n  ?? .pi/harness/library/          # your learned experience — version it\n                                    # (runs/ and worktrees/ are self-ignored)\n\n▸ /rsi library acme-api\n  --- INDEX.md ---\n  - **add-a-rate-limit-header** — checks failing after an edit → re-read the edited file and run the verifier before the next edit (2026-10-18)\n```\n\nNext week, `/rsi start --domain acme-api <new task>` loads that lesson into the\npropose mission automatically (cue match), and the new reference digests sit next to the\nold ones — so whether rework actually went down is measurable, not vibes. If a\nrun makes things worse:\n\n```\n▸ /rsi revert acme-api/add-a-rate-limit-header\n  rsi: restored previous surface for acme-api/add-a-rate-limit-header (AGENTS.md)\n```\n\n(The pre-adoption surface comes back and the skill files it did not have are\nremoved again — `previous/` in the library is what makes this possible.)\n\nEverything is auditable: each run’s missions, digests, session evidence, candidate\nsurfaces and the ledger (`R=4k, P=3k`, USD, fallbacks, budgets) live under\n`.pi/harness/runs/<id>/` — nothing happened that the ledger cannot account for.\n\n- `AGENTS.md` (always-on, char-budgeted per profile)\n- `.pi/prompts/<name>.md`\n- `.pi/skills/<name>/{SKILL.md, scripts/**}`\n\nBecause `scripts/**` is *executable capability* mounted into every probe\nsandbox, candidates are **script-scanned** (fetch-and-execute, `sudo`, credential\npaths, reverse shells, env exfil — fail-closed like the tripwires), and the\nadoption gate shows the content of every added/modified script (bounded excerpt\n\n- hash), not just its path.\n\nNever editable: `tests/**`, `**/*.test.ts`, `.github/**`, `package.json`, lockfiles\n(`rsi.forbiddenGlobs`), plus grader-gaming tripwire phrases\n(`pass_rate`, `delete the failing test`, …) scanned in every candidate.\n\nThe extension manages most of this itself: on every run it writes\n`.pi/harness/.gitignore` containing `worktrees/`, `runs/` and `run.lock`\n(idempotent, it never touches `.gitignore` at the repo root or\n`.git/info/exclude`), so the ephemeral artifacts below never show up in\n`git status`.\n\n**Commit these** — they are the harness's durable state, and without them a\nfresh clone starts from zero:\n\n| Path | Why | \n|---|---|\n| `AGENTS.md` ,`.pi/skills/` ,`.pi/prompts/` | the adopted surface — the habits the harness actually learned | \n| `.pi/harness/library/` | `INDEX.md` ,`EXPERIENCE.md` ,`surface/` ,`previous/` ,`history/` — the experience that seeds the next run and makes`/rsi revert` work | \n| `.pi/settings.json` | the `rsi` config (verifier, budget, profile) so runs are reproducible across machines | \n| `.pi/harness/.gitignore` | optional to commit (it is regenerated anyway); committing it documents the intent | \n\n**Never commit** — regenerated and/or self-contained:\n\n| Path | Why | \n|---|---|\n| `.pi/harness/runs/` | per-run evidence: session JSONL, rollouts, digests, candidate surfaces; large, contains verbatim transcripts and duplicated source | \n| `.pi/harness/worktrees/` | throwaway working copies of the whole repo, one per reference execution | \n| `.pi/harness/run.lock` | cross-process run lock (live pid + run id); regenerated, never useful in history | \n\nIf you prefer a single rule at the repo root instead of the generated file:\n\n```\n.pi/harness/runs/\n.pi/harness/worktrees/\n.pi/harness/run.lock\n```\n\n(Do **not** ignore `.pi/harness/` or `.pi/harness/library/` wholesale — that\nwould silently drop the learned experience you are supposed to version).\n\n| Artifact | Budget | \n|---|---|\n| `AGENTS.md` | 6 000 chars | \n| trajectory digest | 6 000 chars, fixed section shape, drops lowest-priority sections *whole* | \n| propose mission | 16 000 chars | \n| compose mission | 24 000 chars | \n| select mission / reflect mission | 16 000 / 8 000 chars | \n| `<notes>` | 600 chars | \n| skill description / skill body warn | 300 / 4 000 chars | \n\n`context.assemble()` is the single choke point: mandatory parts **raise**\n(`BudgetError`) instead of truncating; optional parts are dropped whole with a\n`## dropped-over-budget` notice so the editor gets a pointer, never a slice.\nProfile `large` doubles these (set `rsi.profile`).\n\n```\n{\n  \"rsi\": {\n    \"profile\": \"small\",          // or \"large\" (doubles every budget below)\n    \"k\": 1,                      // references per domain index (R=4k, P=3k)\n    \"mode\": \"parallel\",          // or \"sequential\"\n    \"editorModel\": null,         // child-pi model overrides (null = inherit the session's model)\n    \"solverModel\": null,\n    \"thinking\": null,            // child-pi --thinking level, when set\n    \"tools\": \"read,bash,edit,write,grep,find,ls\",   // solver tool allowlist\n    \"editorTools\": \"read,grep,find,ls\",             // editor is answer-only\n    \"verifier\": null,            // e.g. \"bun test\" — evidence line, never a score\n    \"confirmAdopt\": true,        // keep the P6 human gate on\n    \"requireClean\": true,        // refuse to start on a dirty tree (unless --force)\n    \"maxUsd\": 2,\n    \"maxRunMinutes\": 5,          // wall-clock cap for the whole run (free models spend $0 but still take time)\n    \"maxParallel\": 2,\n    \"libraryDir\": \".pi/harness/library\",   // absolute or repo-relative\n    \"worktreeRoot\": \".pi/harness/worktrees\",\n    \"keepRuns\": 20,              // run dirs kept under .pi/harness/runs (pending adoptions are never pruned)\n    \"stageTimeoutMs\": 600000,\n    \"budgets\": {},               // per-key overrides on the active profile\n    \"forbiddenGlobs\": []         // replaces defaults when non-empty\n  }\n}\n```\n\nEnvironment overrides: `PI_RSI_MAX_USD`, `PI_RSI_MAX_MINUTES`, `PI_RSI_PI_BIN`\n(child binary), `PI_RSI_FAKE=1` + `PI_RSI_FAKE_SCENARIO=valid|invalid-first|always-invalid`\n(deterministic loop, **zero model calls**).\n\n- **P6** : adoption only through the confirm gate (`/rsi adopt --yes` to opt out\nexplicitly); a dirty working tree refuses adoption unless`--force` .\n- **Revert is guarded too** :`/rsi revert <domain>/<slug>` applies the snapshot\ninside the same transaction as adoption — dirty-tree check (`--force` to\noverride), cross-process run lock, re-validation, and restore-on-throw — so a\nrestore can no longer clobber uncommitted work or race an adoption.\n- **Fail closed** : invalid editor output → one retry carrying the verbatim\nvalidator error → fallback to the current surface (counted in the ledger).\n- **Fail open** only at selection (judge unavailable → pick`c0` , recorded),\nbecause refusing selection cannot corrupt a grader.\n- **Interruption** :`/rsi stop` interrupts the run before its normal end. In\nthe session that owns the run, the current child-`pi` call — rollout*or* verifier — is SIGTERMed (both run async and are killed as a process tree); from\nanother session (headless runs included) it writes a`STOP` sentinel into the\nrun dir. Either way the loop checks the stop signal at every stage boundary\nand closes with outcome`aborted` — nothing half-applied, evidence and\ncandidates stay under the run dir, and no adoption happens.`/rsi watch` shows the live stage, spend and notes (it tails until the run closes;`--once` prints a snapshot), with a ledger-backed disk view for runs started in another\nsession.\n- **Budget check granularity** :`rsi.maxUsd` is evaluated at stage boundaries,\nnot mid-rollout, so a stage that already launched its parallel children can\novershoot the cap by one stage's spend before the run aborts fail-closed.\nCrashed children that never report provider cost count as $0 — keep`maxUsd` a\nreal ceiling, not the exact spend. The wall-clock cap (`rsi.maxRunMinutes` ) is\nthe budget that binds when spend is $0: its timer aborts the run signal, so\nthe in-flight child is killed immediately rather than at the next boundary.\n- **Retention** : each new run prunes run dirs beyond`rsi.keepRuns` — but only\nruns that are old*and* decided; anything awaiting`/rsi adopt` (and the\nin-flight run) is never deleted, so the raw evidence a lesson came from\nstays repairable.`/rsi probe` goes through the same bookkeeping, so it also\nprunes stale runs/sandboxes even though it edits nothing in the surface.\n- **One run per project (cross-process lock)** : the in-process registry only\nsees this`pi` session, so every run also takes`.pi/harness/run.lock` (pid + run id + host). A second session — or a headless`pi --print` — refuses\nto start while a live lock exists, and`/rsi adopt` takes the same lock before\ntouching the tree. A lock whose pid is dead, is older than 12 h, or is\nunreadable is ignored (fail open), so a crashed run never wedges the project.\n- **Delta-only validation** :`readSurface` snapshots what is already in the\ntree; candidates are validated as`base ⊕ delta` and`validate(base, candidate)` reports only the errors/warnings the proposal*adds* — a hostile script or\ntripwire phrase that already exists in the host tree is inherited, not\nre-litigated on every proposal. The tripwire boundary is about what a\nproposal adds or rewrites, not a full audit of the host project.\n- **Matched conditions** : clean git trees probe a detached worktree at`HEAD` ;\na dirty tree (only reachable with`--force` ) or a non-git project probes a\nbyte-faithful copy of the live tree instead, so rollouts always measure the\nsurface that was snapshotted rather than a stale checkout.\n- **Sandbox boundary (be precise)** : a probe sandbox isolates the*working\nfiles* , not the process. A git-worktree sandbox shares the host repository’s`.git` , so a solver with`bash` can touch refs/config/hooks; a copy sandbox\ncopies the tree, including untracked files (e.g.`.env` ). Run untrusted tasks\nin a container/VM, or point the project at a throwaway clone.\n- **Digest truncation is reported** : when a digest has to drop sections to fit\nits budget, the run records a`digest … capped` note instead of hiding it.\n- Every run writes `.pi/harness/runs/<id>/` :`ledger.json` ,`report.json` ,`status.txt` , missions, digests, evidence (sessions + rollouts), candidate\nsurfaces,`selection.json` . Library:`.pi/harness/library/<domain>/<slug>/` with`EXPERIENCE.md` ,`INDEX.md` ,`surface/` ,`previous/` and`history/` (previous snapshots archived on each new adoption;`/rsi revert` restores the\nmost recent`previous/` ).\n\n```\nbun install          # dev deps (types only)\nbun test             # full suite, includes fake loops for k=1 and k=2 (PI_RSI_FAKE=1)\nbun run typecheck    # tsc --noEmit, strict + noUncheckedIndexedAccess\nbun run lint         # Biome: lint + format check (bun run format to fix)\nPI_RSI_FAKE=1 pi --offline -ne -e ./index.ts --print \"/rsi start --domain demo <task>\"   # zero-cost e2e\n```\n\n`src/args.ts` is the whole command surface: `VERBS`, per-verb `FLAGS`, the\nparser and the `HELP` table. Adding a verb means one entry in `VERBS`, one in\n`FLAGS`, one in `HELP`, and one `case` in `index.ts` (whose `default:` branch\ntypes `verb` as `never`, so a missing `case` is a compile error, not a command\nthat silently does nothing); `test/args.test.ts` asserts `HELP` covers every\nverb, so a missing row fails the suite.\n\nLayout: `index.ts` (command dispatch: one `/rsi`, verb-first) · `src/args.ts`\n(verb + flag parsing, help text) · `src/loop.ts` (orchestration) · `src/watch.ts`\n(live run registry: `/rsi watch`, `/rsi stop`) · `src/lock.ts` (cross-process\nrun lock) · `src/context.ts` (budget choke point) · `src/surface.ts` (whitelist +\nvalidation + gate review) · `src/guard.ts` (tripwires/script scan/gates) ·\n`src/trajectory.ts` (digests) · `src/editor.ts` (missions, reply parsing) ·\n`src/selection.ts` · `src/library.ts` (lessons, supersession) · `src/ledger.ts` ·\n`src/probe.ts` · `src/proc.ts` (interruptible child processes: rollouts + verifier) · `src/retention.ts` (run pruning) · `src/runs.ts` (run discovery) ·\n`src/worktree.ts` · `src/fake.ts`.", "url": "https://wpnews.pro/news/self-improvement-harness-based-on-pi-pi-harness-rsi", "canonical_source": "https://github.com/grigio/pi-harness-rsi", "published_at": "2026-10-19 11:54:51+00:00", "updated_at": "2026-10-19 12:16:05.289826+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "large-language-models", "ai-safety"], "entities": ["pi", "pi-harness-rsi", "Harness²", "arXiv:2610.14224"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/self-improvement-harness-based-on-pi-pi-harness-rsi", "markdown": "https://wpnews.pro/news/self-improvement-harness-based-on-pi-pi-harness-rsi.md", "text": "https://wpnews.pro/news/self-improvement-harness-based-on-pi-pi-harness-rsi.txt", "jsonld": "https://wpnews.pro/news/self-improvement-harness-based-on-pi-pi-harness-rsi.jsonld"}}