`src/openultrasast/improve/memory.py` (deterministic proposal rules). None of it is in the reconciler. The
reconciler gains only `mark_done` (below). It is 465 lines today, and `tests/test_plane_reconciler.py:759-761`
keeps it under 500.
## Components and Interfaces
### 1. Fate of each HarnessX capability (Requirement 1)
| Capability | Where today | Fate | What the user loses |
| --- | --- | --- | --- |
| LLM hunter pool (`HxScanOrchestrator`) | `hunter_harness.py:49-185`; gated in `cli.py:400-456` (`hx_hunter` at :405, orchestrator at :438-448, degradation at :452-455) | **Retired.**`run_hunter_pool` becomes the only standard-mode hunter pool. LLM hunting of a repository stays available in two places: the MAP-stage tool hunter in `ousast scan --mode standard/deep` with `[models] hunter` (`cli.py:631-640`, not HarnessX), and plane `verify` via `ousast plane run plane/runs/validation-46.yaml` | `hx-hunter:<path>:<line>` findings from an anthropic/openai/litellm agent (`hunter_harness.py:155`). No run of this path is recorded under `benchmarks/`, so no measured TP is lost. |
| LLM judge | `verify_judge.py:22-63`; gated in `cli.py:406, 499-511` | **Retired.**`verification.verify_findings` is called directly and `verify_judge.py` is deleted (it is only the dispatcher and the judge). The plane's LLM verification is `verify` + `agree` (+ `vc`/`final` tie-break) | Per-finding `plausible/unsupported` verdicts that set `ACCEPTED/REJECTED` (`verify_judge.py:83-88`). Unmeasured. `[models] judge` (model-grounded-detection, `model/endpoint.py:195`) is a different capability and stays. |
| LLM fusion panels | `fusion.py:194-326` (`_fuse_with_llm_panels`, `_run_llm_panels`); gated in `cli.py:519-541` | **Retired.** The deterministic panels (`_panel_a`, `_panel_b`, `_reconcile`, `fusion.py:101-167`) become the only path. `fuse_findings_dispatch` is renamed `fuse_findings(findings, verifications, *, high_assurance, mitigated_ids)`. The plane counterpart of independent cross-examination is `agree` across independent passes with the 2-of-3 tie-break | Steel-man LLM panels (`decision_source: "llm-panels"`). Unmeasured. `fusion.json` for deterministic decisions is unchanged. |
| Deterministic stages hosted under HarnessX | `stage_processors.py:129-156` (`host_under_harnessx`); only caller is the skipped test `test_slot_contract.py:119-133` | **Retired, no replacement.** ax runs whole tasks, so a model-disabled in-loop host has nothing to host. `stage_processors`/`slot_contract` stay as the standalone deterministic pipeline that `test_gate.py:147-148` pins | Nothing. The function has no production caller. |
| LLM evolution proposer (`MetaAgent.evolve`) | docstring only: `improve/evolve.py:8-9`, `harness_ext.py:7-8` | **Carried by the memory layer** (Requirement 6): `improve/memory.py` rules over the store, fed into `run_round` as extra proposals | Nothing implemented is lost. |
No CLI flag is HarnessX-specific, so Requirement 1.3 applies to config keys. A key that asked for a retired
capability makes `load_config` raise `RetiredConfigError(ValueError)`, and the CLI prints the message and exits 2.
A silent downgrade is not allowed:
| Key | Message (abridged) |
| --- | --- |
| `[models] verifier` | "`[models] verifier` was retired with HarnessX on <date>: LLM verification runs on the plane (`verify` + `agree`, `ousast plane run`, see ops/ax/README.md). Remove the key." |
| `[fusion] panel_model`, `[fusion] decider_model` | "... LLM fusion panels were retired; fusion is deterministic; independent LLM agreement is the plane's `agree` task ..." |
`[harnessx]` is different (Requirement 3.3). It only tuned budgets and never requested a capability, so it loads
with **one**`logging.warning` on logger `openultrasast.config` that names the plane ("`[harnessx]` is ignored:
HarnessX was removed; agentic work runs on the ax plane with per-task budgets, see ops/ax/README.md"). The section
is otherwise ignored. `[models] hunter` keeps its meaning (tool hunter, obligations intent adjudication at
`cli.py:1237`), so it gets no message. With the extra installed it no longer starts a second, HarnessX hunter. The
release notes say so.
Requirement 1.2 (moved capabilities have a CLI path, a Model per task and a fixture test) is met by the existing
`verify`/`agree` tasks (`ousast plane run`; Model `deepseek-flash` bound per Task; `tests/test_plane_verify.py`,
`tests/test_plane_scoring_parity.py`, `tests/test_plane_tiebreak.py`), and by the new memory and loop tasks below, each with its own fixture test. No
capability is moved into a new Model-bound task.
### 2. Removal mechanics (Requirement 2)
**Source.**
- Delete `harness_ext.py` (97 lines), `hunter_harness.py` (197) and `verify_judge.py` (130).
-`cli.py`: drop the imports at :44, :46 and :94. Drop `harnessx_present`, `hx_hunter` and `hx_verify` (:400-406),
the orchestrator branch and its degradation (:438-456), and the verify degradation (:499-502). Call
`verify_findings(findings)` at :503-511. Drop `hx_fusion` and its degradation (:521-527). Call
`fuse_findings(findings, verifications, high_assurance=config.fusion.high_assurance)`. The `verifier_model`
local goes away.
-`fusion.py`: drop the import at :24, the `panel_model`/`decider_model`/`provider`/`use_harnessx` parameters, and
`_with_degradation`, `_fuse_with_llm_panels`, `_run_llm_panels` and `_text` (:229-338). Rewrite the module
docstring (:10-14) and update `__all__`.
-`stage_processors.py`: drop `host_under_harnessx` (:129-156) and its `__all__` entry, and rewrite the module
docstring (:10-11). `slot_contract.py:7-14` becomes "the zero-dependency substrate for the deterministic
pipeline".
-`config.py`: drop `HarnessxConfig` (:99-107), `ResolvedConfig.harnessx` (:201), `_load_harnessx` (:345-351) and
its call (:230). Drop `ModelConfig.verifier` (:37), `FusionConfig.panel_model/decider_model` (:123-124) and
their loaders (:266, :366-367). Add `RETIRED_KEYS` and the one-warning rule above. `harness.py:183` keeps the
`"verifier": None` literal in `model_roles`, so `harness.json` keeps its schema byte for byte (the key has always
been `null` in the baseline config).
- Comment-only mentions: `gate.py:174` ("a model-backed stage"), and `improve/evolve.py:8-9`, which now names the
memory proposer.
-`plane/Dockerfile.runner:23` loses "no HarnessX".
**Packaging.**
- Delete the `harnessx` extra and its comment (`pyproject.toml:14-22`) and the `harnessx.*` mypy override and its
comment (:67-71).
-`uv lock` regenerates `uv.lock`. The `harnessx` package (:1062), the extra (:2145-2146), the requirement
(:2169) and `provides-extras` (:2178) go.
- Extras that remain: `semantic`. There is no `all` extra. `pip install .` and `pip install '.[semantic]'` in a
fresh venv are the Requirement 2.2 check.
- No source or test imports a HarnessX transitive dependency. The only third-party imports are `yaml`,
`tree_sitter` and `pytest` (checked with an import scan), so dropping the lock entry cannot break an import.
**Tests** (14 files plus `conftest.py`):
| File | Action |
| --- | --- |
| `test_harness_ext.py`, `test_hunter_harness.py`, `test_verify_judge.py`, `test_cli_hx_dispatch.py` | **Delete.** Each exercises only HarnessX or its dispatcher. |
| `conftest.py` | Rename fixture `assert_cold_of_harnessx` to `assert_cold_import(code)`: a fresh interpreter runs `code`, with no module-name guard. `test_semantic_extra.py:28-35` is its remaining user (the tree-sitter check brings its own assertion). |
| `test_gate.py` | Delete `test_harnessx_stage_stays_within_tolerance_of_baseline` (:124-150) and the `has_harnessx` import (:23). Replace the two "never import" tests (:155-175) with `test_governance_planes_import_only_stdlib_and_declared_dependencies`, an allow-list (stdlib, `openultrasast`, `yaml`) over module-level imports of `GOVERNANCE_SCORING_BENCHMARK_PLANES`. It covers any extra without naming one. Update the docstring at :5-6. |
| `test_scan_pipeline_integrity.py` | Rename `test_standard_scan_extra_absent_matches_deterministic_baseline` to `test_standard_scan_with_a_hunter_model_keeps_deterministic_findings` (config `hunter` only). Rename `test_quick_scan_is_unaffected_by_the_extra` to `test_quick_scan_records_no_degradation`. Drop the `has_harnessx` monkeypatch and the `harnessx` parameter. Recompute the expected degradation set from the post-removal run, since `hunter_pool`/`verify` entries no longer exist. |
| `test_slot_contract.py` | Delete `test_host_under_harnessx_disables_the_model` and the imports at :16 and :22. Keep the rest. |
| `test_config.py` | Replace `test_harnessx_defaults_when_section_absent` and `test_load_config_reads_harnessx_section` with `test_retired_agentic_section_loads_with_one_warning` (caplog: exactly one record naming the plane) and `test_retired_llm_keys_fail_naming_the_replacement` (verifier, panel_model, decider_model). |
| `test_fusion.py` | Delete `test_dispatch_records_degradation_when_llm_requested_but_extra_absent` (:142-146) and the import at :16. Rename the dispatch calls to `fuse_findings`. |
| `test_tool_hunter.py` (:229, :266), `test_scan_deep.py` (:144) | Drop the `has_harnessx` monkeypatch line. |
| `test_semantic_extra.py` | Use `assert_cold_import`. |
| `test_module_audit.py` | The subsystem tuple at :31 becomes `("fusion", "mcp", "skills")`. A new assertion checks that the three deleted modules are absent from the audit. |
**Module audit.**`model/audit.py:76-77` (`harness_ext`, `hunter_harness`) are deleted, and `:78`
(`stage_processors`) is reworded to "deterministic slot-contracted scan stages; pinned by the zero-dependency
guard in test_gate". Regenerate `benchmarks/measurements/2026-09-08-module-audit.json` in place with `to_dict(audit(SRC))`.
`test_the_manifest_matches_the_tree` pins that path. The diff shows three rows removed and no orphan.
**Search (Requirement 2.4).**`grep -rni harnessx src tests pyproject.toml README.md` may match only lines tagged
`retired 2026-..`:
- the `RETIRED_KEYS` entry and warning text in `config.py`
- the two retirement tests in `test_config.py`
- the one README line in the plane section
Requirement 3.3 cannot be implemented or tested without naming the section, so these lines count as retirement
notes. The check is a test (`test_no_harnessx_outside_retirement_notes`) that fails on any untagged match. It
builds its pattern at runtime, so it does not match itself.
### 3. Equality proof (Requirement 3)
The spec's "`ousast regress`" is not a subcommand (`cli.py:133-251`). Regress exists only as the deep-mode
sandbox stage. On this project the regression command over the development pins is `ousast pairs`
(README.md:38): the vuln-vs-fixed replay over local fixtures and vendored pinned VFC pairs, offline by default.
The benchmark replay is README.md:35's manifest. The baseline also covers the two paths the removal touches
directly: one standard-mode scan (verify and fusion) and one `improve --dry-run` (evolve). Everything is
quick or standard mode with no Joern on the host, so each command takes minutes, not hours, on this 7 GB host.
**Environment (instrument first).**
- Run from a `git archive HEAD` export in the scratchpad (the source is frozen, per the long-runs rule).
-`uv sync --frozen --extra semantic`, which drops the installed `harnessx`. It is installed in `.venv` today.
| `loop-improve` (once) | none | `{0, 0}`, `serialize: engine` | proposals; Workspace = this repository at the commit under test -> `gate.json` (the `RoundOutcome`), `journal.json`, `rule_policy.json` (only when accepted) |
Before the Run starts, `memory-snapshot` is seeded into the run directory like reused facts. It holds the store
rows after the guard, since a sandboxed task cannot read the host store. A `{usd: 0, calls: 0}` budget makes
`budget.py` refuse any call, so a model-free task that tries one fails loudly rather than spending. Attribution
per task is the existing `ousast plane status` table.
**The gate's verdict** lands in `~/ousast-results/plane/<run>/loop-improve/gate.json` and is ingested as
`proposal_outcome` rows. An accepted `rule_policy.json` is adopted into the repository only by a maintainer
commit, as any ledger change is today (README "Rule-level loop").
### 7. Documentation and specs (Requirement 4)
- **README.** Replace "Configuring the HarnessX agentic plane" (README.md:293-329) with "The agentic plane (ax)":
what runs there (repo-facts, verify, agree, memory), `ousast plane run|status|remember`, a pointer to
`ops/ax/README.md`, and one tagged line saying HarnessX was retired. Rename "The HarnessX self-improving cycle"
(:330) to "The self-improving cycle". Its calibration ledger and `ousast improve` content is not HarnessX and
stays. The MetaAgent paragraph (:393-400) is replaced by the memory proposer (`--memory`, provenance, guard).
Drop the `panel_model`/`decider_model` lines at :227-228.
- **`docs/examples.md` section 6** becomes "Running the agentic plane", with the plane doctor, run and status
commands.
- **`docs/threat-model.md:17-20, 32-34`**: egress is the per-task EgressPolicy (receiver, Git hosts, Model
`egress-hosts`, passthrough only). Credentials travel only in the start request. Spend is the per-task
`usd`/`calls` budget in the Run.
- **Retirement notes.** A dated note ("Retired 2026-..: HarnessX was removed by `harnessx-removal`; the agentic
plane is ax (`ai-service-plane`), proposals come from the plane memory. History below is unchanged.") goes at
the top of the requirements (and design, where present) of `harnessx-self-improving-rulesets`,
`learning-harness`, `three-stage-scan` and `openrouter-sast-harness`, and in `.kiro/steering/overview.md` near
:175. The other specs that mention HarnessX in passing keep their history untouched.
-`RELEASE_NOTES.md` gets one entry listing the retired keys. Kiro tooling stays out of user docs.
## Data Models
-`memory.jsonl` (remember output) and `repos/<repo>/<pin>.jsonl`: the rows above, one JSON object per line,
- `RetiredConfigError`: exit 2 with the key and its replacement, before any stage runs.
- A `[harnessx]` section produces exactly one warning, and loading continues.
- `memory.ingest` on a run without `remember` outputs is a no-op that says so. A malformed row fails the ingest
and names the file and line, and nothing partial is written: rows are staged in a temporary file and renamed.
- A low-disk refusal names the store and the free space. The plane run's result is unaffected, and ingest can be
repeated with `ousast plane remember`.
- `seed` never overwrites an existing task directory or a non-pending state. A facts file whose sha256 no longer
matches is dropped from the store with a warning, and that task recomputes.
- `propose_from_memory` with an empty store after the guard returns no proposals, and `run_round` reports
`no_proposals` as today.
- The equality script exits non-zero on the first failed instrument check. It also exits non-zero on any
difference outside the exclusion list and prints the first differing path and key.
## Testing Strategy
- `tests/test_config.py`: the one-warning section and the retired keys (above).
- `tests/test_no_harnessx_references.py`: the tagged-only search of Requirement 2.4.
- `tests/test_plane_memory.py`: ingest is idempotent and content-addresses identical facts. `seed` marks a facts
task done and serves its file, and does not for a changed pin, candidate digest or image. Low-disk refusal
(monkeypatched `disk_usage`).
- `tests/test_plane_remember.py`, `test_plane_alerts.py`, `test_plane_loop.py`: fixture run directories built from
the recorded validation-46 shapes. `remember` rows match `agree`'s per-candidate rows. `alerts` attributes
functions. `measure` reproduces the increment's recorded metrics on the recorded artifacts. `propose` and
`improve` work end to end on a fixture manifest.
- `tests/test_improve_memory.py`:
- M1 and M2 fire at their thresholds and not below.
- A disputed candidate counts neither way.
- The guard removes rows by population, manifest case and holdout pair, so a proposal that exists only because
of a holdout row disappears.
- Provenance ids are stable across runs.
- A reverted memory edit is not re-proposed on the same evidence, and is re-proposed on new evidence.
- `run_round` without proposals writes a journal byte-identical to today's.
- `tests/test_plane_reconciler.py`: `mark_done` under the lock. The existing under-500-lines test stays.
- The existing gate and pair tests, `test_benchmark_output_is_byte_identical_to_golden_baseline`
(`test_gate.py:104`) and the equality script (section 3) are the regression net.
## Requirement Traceability
| Requirement | Design section | Verified by |
| --- | --- | --- |
| 1.1 fate of every capability | 1 (table) | this document; commit 5 |
| 1.2 moved capabilities reachable, Model per task, fixture test | 1 (existing `verify`/`agree`), 4-6 (new tasks are Model-free) | `test_plane_verify.py`, `test_plane_tiebreak.py`, new plane task tests |