# Changelog

Full version history for `world-model-mcp` from v0.7.0 onward.

For the current release, install instructions, and positioning, see [README.md](README.md).

## What's new in v0.15.7

- **Mutation-path cache invalidation.** `KnowledgeGraph.purge_fact`, `invalidate_fact`, and `supersede_fact` now call `self._cache_invalidate("facts:")` after commit, matching the existing pattern on `create_fact` and `apply_fact_decay`. Without this, a same-instance `query_facts` following one of these mutations could return the pre-mutation `QueryFactResult` from the in-memory cache until the TTL expired. Callers whose flow was `query → mutate → verify-with-query` on a single `KnowledgeGraph` instance (right-to-erasure verification in particular) may have seen the mutation as pending in-process; fresh-instance queries were unaffected. Storage-layer behavior was already correct in v0.15.6.
- **7 regression tests in `tests/test_v0157_cache_invalidation_on_mutations.py`.** Locks the same-instance query-mutate-query sequence for all three mutation paths, verifies the public `WorldModelMemoryBackend.purge()` return string is honest under this repro, confirms invalidated rows remain reachable via `current_only=False` for audit-chain reconstruction, and locks the narrow `facts:` prefix scope so mutations do not over-clear unrelated cache lanes. Complements the v0.15.6 storage-layer regression suite in `tests/test_v0156_delete_purge_semantics.py`.

## What's new in v0.15.6

- **`WorldModelMemoryBackend.purge(path)` primitive.** New method that hard-deletes the underlying fact: calls `KnowledgeGraph.purge_fact` which issues `DELETE FROM facts` and fires the existing `facts_fts` sync trigger, removing the row from both the primary table and the full-text-search index. Distinct from `delete()` (which is soft-delete / invalidate). Use for GDPR Article 17 right-to-erasure requests, HIPAA retention window purges, ephemeral credential cleanup, and any case where "the fact must not remain retrievable" is the actual compliance requirement rather than "mark invalid."
- **`WorldModelMemoryBackend.delete()` return string now says `"Invalidated memory at {path} (fact remains retrievable; call purge() for row-level erasure)"`.** The previous string was `"Deleted memory at {path}"` while the underlying implementation only marked the row invalid. The docstring was accurate; the return string and two field descriptions in `models.py` were not. A caller reading only the return string would reasonably conclude the row was gone.
- **`models.py` field descriptions for `influence_state` and `expires_at` rewritten to note the consumer-wiring gap.** The previous descriptions promised drop-behavior the shipped wheel did not enforce. New descriptions explicitly state that the consumer wiring (planning-query filter for `influence_state`, sweep worker for `expires_at`) is tracked separately and is not guaranteed on-by-default in the shipped wheel. A caller with a retention requirement is now told to either invoke `purge` explicitly or verify the sweep is enabled in their deployment.
- **`KnowledgeGraph.purge_fact(fact_id)`.** New primitive that returns `True` when a row was actually removed, `False` when no fact with the given id existed. The bool return supports downstream signed-purge-event audit records (distinguishing "purged" from "was already absent"). Docstring on `invalidate_fact` also expanded to point at `purge_fact` for erasure semantics.
- **13 regression tests in `tests/test_v0156_delete_purge_semantics.py`.** Locks every axis of the fix: return-string honesty, delete-leaves-row-by-design, purge-actually-removes-row, purge-fires-FTS-trigger, `purge_fact` return-code semantics, and both field descriptions matching the shipped behavior.
- **CLI install→signup nudge in `world_model_server/cli.py`.** One-line pointer to the hosted Etch notary at `world-model setup` and `world-model demo`. Suppressible with `WORLD_MODEL_NO_NUDGE=1` (mirrors the existing `WORLD_MODEL_NO_PROMPT` telemetry opt-out). Deliberately NOT wired into the MCP stdio server path: that stdout is JSON-RPC and a nudge there would corrupt the protocol. 4 regression tests in `tests/test_v0156_hosted_notary_nudge.py`.
- **README.md gains a "Next (optional)" pointer after the pip-install block** to the hosted Etch companion for auditor-verifiable chains without infra to run.
- **Credits @DanceNitra** for the measured reproduction script and honest matched-comparison methodology in #37 that motivated the delete/purge two-primitive design.

## What's new in v0.15.5

- **`verify_manifest_streaming(file_path)` in `world_model_server.etch_verify`.** Parses the manifest row-by-row via `ijson` so verify memory usage is O(single row + compact row_hash lookup) rather than O(manifest size). Same verdict, same check names, same counts as the in-memory `verify_manifest(dict)` for the same bytes.
- **`etch-verify` CLI switches to streaming by default.** `--in-memory` remains as a debugging escape hatch. `manifest_sha256` is computed in a 64KB-chunked streaming pass so hashing a 10GB manifest does not require the whole file in RAM.
- **Hosted-side attestation timer is fully streaming.** Pre-v0.15.5 already streamed the export but then `json.load`'d the file back into a Python dict for verify — verified 2026-07-24 to pin multi-GB RSS on a 760MB manifest, forcing preflight_skipped rows. v0.15.5 removes the `json.load`, so memory stays bounded through export + hash + verify.
- **FIPS 205 SLH-DSA known-answer tests in `tests/test_fips_205_slh_dsa_kat.py`.** Locks parameter sizes for SLH-DSA-SHA2-128f (public key = 32 bytes, secret key = 64 bytes, signature = 17,088 bytes), sign/verify round-trip with wrong-key + wrong-message + mutated-signature rejection, and a fixed KAT-vector fixture (`tests/fixtures/slh_dsa_kat_vectors.json`) that must verify true forever. Byte-format equivalence across the FIPS 205 and legacy SPHINCS+ mechanism names is also locked.
- **Atheris fuzz targets in `fuzz/fuzz_verify_manifest.py`.** Feeds random and mutated bytes into both verify entry points, asserts no unhandled exception, no hang, no memory blow-up. Seed corpus checked into `fuzz/corpus/`. Run locally with `python fuzz/fuzz_verify_manifest.py fuzz/corpus/ -max_total_time=60`.
- **Non-Atheris smoke fuzz in `tests/test_fuzz_smoke_verify.py`.** Same invariant exercised via 29 adversarial payloads plus 100 deterministic-random 200-byte seeds against both verify paths. Runs in every CI pass without needing Clang / libFuzzer.
- **Pre-existing env-leak flake fixed in `tests/test_operational_e2e.py`.** Hard-kill recovery test was mutating `os.environ` directly in the parent process without cleanup, leaking `WORLD_MODEL_AUDIT_LOG` into subsequent tests under random ordering. Fixed to use `mock.patch.dict` for scoped env cleanup.
- **Coverage floor bumped 69/64 → 71/66** in `pytest.yml` to match the 2026-07-24 baseline of 72.06% across `world_model_server`. Two-tier floor stays in place (lower tier for CI runners without SLH-DSA in liboqs).
- **Deferred:** ruff hard gate + mypy strict. ~745 pre-existing typing-deprecation warnings across the source tree need a dedicated cleanup PR before either can be turned on as a hard gate. Landing separately.
- **`ijson >= 3.2`** added to core dependencies. Ships a pure-Python fallback backend so no system deps are required.

## What's new in v0.15.4

- **`export_audit_dump_to_file_streaming(kg, out_path)` in `world_model_server.audit_dump`.** Same signature and return value as `export_audit_dump_to_file`, but writes the manifest JSON directly to disk one DB row at a time via async cursors over `tamper_evident_log`, `tamper_evident_epochs`, `annotations.db`, and `events.db`. Memory footprint is O(single row) instead of O(chain). Fixes the OOM path on chains too large for in-memory expansion.
- **Byte-identical output.** Both exporters produce sort_keys=True 2-space-indent JSON with the same whitespace, comma placement, and escape rules. If an auditor hashes the manifest as an artifact of record (which is exactly what `etch-verify --json`'s `manifest_sha256` field supports), the same chain state hashes to the same value regardless of which exporter the operator used.
- **7 new tests** locking byte-parity (parsed round-trip AND raw-file comparison), `etch-verify` PASSes on streaming output for single- and multi-epoch chains, memory scaling stays sublinear under `tracemalloc`, empty-chain edge case produces valid parseable JSON, same fail-closed error when audit chain is disabled.
- **No API changes elsewhere.** `pin_annotation`, chain integrity, `etch-verify` all unchanged. The in-memory `export_audit_dump_to_file` remains available for small chains and existing callers — no forced migration.
- **Recommendation:** Upgrade any deployment running scheduled offline verification against audit chains large enough to strain host memory. Switch the export call from `export_audit_dump_to_file` to `export_audit_dump_to_file_streaming`. Same signature.

## What's new in v0.15.3

- **`verify_slh_dsa` raises `RuntimeError` when SLH-DSA is unavailable in the local liboqs build.** v0.15.2 returned `False` in this case with a docstring line claiming "compliance-correct." That framing was wrong: a signature that CANNOT be verified in the environment is not evidence that the signature is invalid. Returning `False` for both silently reclassified an environment problem as tamper detection.
- **`verify_hybrid` propagates the exception.** Any caller — `etch-verify`, `tamper_evident` chain verification, downstream integrations — must distinguish "unverifiable here" from "verified as invalid." Silently swallowing the exception into a `False` return would restore the v0.15.2 semantic drift.
- **`etch-verify` catches the RuntimeError and reports a distinct `epoch_signatures_unverifiable` check** with the underlying error verbatim in the detail column. The `chain_integrity` and `epoch_signatures` check names remain reserved for real tamper detection, so an operator reading the report can distinguish tool problems from chain problems at a glance.
- **New test file `tests/test_verify_slh_dsa_semantics.py` (+5 tests)** locks in every branch: unavailable raises, invalid returns False, wrong-length returns False, verify_hybrid propagates, etch-verify reports the correct check name. If a future refactor collapses these outcomes back together, the tests fail loud with the compliance-story reasoning in the assertion message.
- **No other API changes, no schema changes.** Environments with a proper liboqs installation (the vast majority — Debian `apt install liboqs-dev`, macOS `brew install liboqs`, source builds) see identical behavior.
- **Recommendation:** Upgrade any v0.15.2 install that may run signature verification on an environment where liboqs might not have SLH-DSA. Prior versions could silently misreport an environment problem as tamper detection.

## What's new in v0.15.2

- **`BEGIN IMMEDIATE` around the tamper-evident append transaction.** Prior versions read the last `entry_hash` and `MAX(seq)` in separate statements before computing the new entry's `entry_hash` and inserting. Two concurrent writers against the same committed snapshot could both compute the same `prev_hash`, breaking the chain-integrity invariant that entry N+1's `prev_hash` equals entry N's `entry_hash`. Taking the reserved write lock BEFORE the SELECTs closes the window: any concurrent writer either blocks on the lock or fails-fast with `SQLITE_BUSY`.
- **How it was found: our own offline verifier caught it in production.** A `chain_integrity` FAIL surfaced on a running project's ledger with the exact seq numbers of two racing writes and their identical `prev_hash` values. That is the trust surface working as intended — this release closes the append-path serialization gap so the verifier's catch is a secondary defense, not the only one.
- **New regression test file (`tests/test_tamper_evident_concurrent_append.py`, +2 tests):** 40 concurrent `create_event` tasks land 40 contiguous entries with valid chaining and pass `etch_verify` end-to-end. 25 concurrent tasks across two epoch closes still pass chain + epoch signatures + content lock. Both tests are proven-signal — reverting the `BEGIN IMMEDIATE` line makes them fail with `prev_hash` mismatches at exact seq boundaries where the writers race.
- **No API changes, no schema changes.** Chains produced by prior versions are still verifiable, and `etch-verify` will identify any historically-broken segments with a specific "entry seq=N prev_hash does not match previous entry_hash" FAIL reason. Historically-broken chains cannot be repaired retroactively — the broken chain IS the truth about what happened — but the offline verifier tells you exactly where and why.
- **Recommendation:** Upgrade any deployment that may see two or more concurrent MCP writers against the same audit chain, then run `etch-verify <dump.json>` against your existing chains to surface any historical breaks.

## What's new in v0.15.1

- **`etch-verify` offline reference verifier CLI.** New console script (installed via `pip install world-model-mcp` and registered in `pyproject.toml [project.scripts]`) that reads a signed audit-chain dump manifest and runs four verification passes in short-circuit order:
  1. **Chain integrity** — every `entry_hash` recomputes from stored fields and `prev_hash` chains back to `GENESIS_HASH`.
  2. **Epoch chain** — every `prev_epoch_root` links to the previous `merkle_root` back to `EPOCH_GENESIS_ROOT`.
  3. **Hybrid signatures** — every closed epoch's envelope verifies under the manifest-declared Ed25519 + SLH-DSA-SHA2-128f public keys via `hybrid_signer.verify_hybrid`.
  4. **Content lock** — every `source_rows.annotations` and `source_rows.events` row reconstructs to the exact `row_hash` the log locked in. Any post-hoc rationale rewrite, author swap, range retarget, or event mutation fails this check.

  Usage: `etch-verify <manifest.json> [--json]`. Exit codes `0` clean / `1` at least one failed / `2` unreadable. `--json` emits a SHA-256 of the exact bytes read so auditors chain the manifest hash into their own scanned-artifact ledger.
- **`audit_dump.export_audit_dump(kg)` + `export_audit_dump_to_file(kg, path)`.** Self-contained JSON manifest v1 an offline auditor can verify without live DB access. Contains: full `tamper_evident_log` in seq order, all closed `epochs` with parsed signature envelopes, both hybrid public keys base64-Raw, and `source_rows` for annotations + events. File export is sorted-key + fixed-indent so two dumps of identical state produce byte-identical files — auditors hash the manifest as artifact of record.
- **Clean stdout for pipe consumers.** `import oqs` prints a "liboqs-python faulthandler is disabled" banner to stdout on first-import. The CLI's crypto import chain now runs inside a `contextlib.redirect_stdout(io.StringIO())` guard so `etch-verify --json | jq` gets unpolluted input.
- **Test surface (+34):** `tests/test_audit_dump_export.py` (10), `tests/test_etch_verify.py` (18), `tests/test_etch_verify_subprocess_e2e.py` (6). The `test_pin_annotation_e2e.py` mid-run intervention workflow now ALSO shells out to the etch-verify CLI subprocess against a real dump on disk, closing the compliance-facing "run offline etch-verify CLI against the dump" line ADR-0001 §5 e2e opened.
- **Repo total: 1194 tests pass, 71.01% coverage.** All three CI gates (pytest, mcp-conformance, security) green through the three-commit sequence: `049a107` → `0cf9ba0` → `67e6a03`.

## What's new in v0.15.0

`pin_annotation`: signed human interventions on the tamper-evident audit chain (ADR-0001).

- **`pin_annotation` MCP tool.** Attach a signed human note, override rationale, or intervention record to a span of agent events. Rationale is capped at 8 KB and lives in a dedicated `annotations.db` with a CHECK constraint on `annotation_type` (`human_intervention` / `human_note` / `override_justification`).
- **Chain integration with domain separation.** Annotations chain into the same tamper-evident log as agent writes with `kind="annotation_create"`. Leaves embed the `DOMAIN_ANNOTATION = "world-model-mcp/transparency-log/annotation/v1"` string so annotation leaves and event leaves hash to different values even when other fields collide — cross-type replay defense.
- **Rationale hash tamper detection.** The tamper-log entry carries a SHA-256 of the rationale bytes but not the rationale text itself (PII discipline, matching the existing fact/event pattern). Modifying rationale in `annotations.db` post-write means a recomputed hash no longer matches the log's stored one — verifier detects it.
- **Offline verifier: `prove_annotation_inclusion`.** Bundle contains the Merkle inclusion proof, the reconstructed payload, and a `span_consistency` verdict. Kind assertion rejects passing a non-annotation `row_id` through the annotation verifier path. Span check confirms both endpoints of the annotated event range exist in the tamper log at seq ≤ annotation's seq — an annotation cannot be back-dated to a span of not-yet-logged events.
- **97 new tests across five files:** schema (23) + MCP tool (17) + chain integration (22) + offline verifier (19) + integration/security/E2E (16). ADR-0001 §1-5 fully attested. See `docs/decisions/0001-pin-annotation-mcp-tool.md` for the full design record.

## What's new in v0.14.1

- **Cross-platform determinism fix in confidence decay.** `world_model_server.decay.compute_decayed_confidence()` now quantizes its output to 6 decimals at the function boundary. Previously the raw IEEE 754 multiplication of `confidence * (0.5 ** half_lives)` produced platform-dependent last-bit drift on sub-nanosecond half-life inputs (macOS often landed on the input value exactly; Linux CI landed ~1e-8 below). Confidence values are never consumed at more than a few decimals of precision anywhere in the system (thresholds like 0.8 and 0.6 are exact literals), so 6-decimal quantization is a semantic no-op that eliminates the drift. A new test file `tests/test_decay_platform_stable.py` (8 tests) locks in the quantization contract, the platform-stability property across sub-second timing variation, and the physical decay semantics (one half-life halves confidence, clamps at 0.0 and 1.0 still fire).
- **First-party adapter docs for [Mesh-LLM](docs/adapters/mesh-llm.md), [BUZZ](docs/adapters/buzz.md), and [Goose](docs/adapters/goose.md).** BUZZ is verified end-to-end by a real integration test (`tests/integration/test_buzz_acp_handshake.py`) that spawns a real `buzz-agent` binary + real world-model-mcp Python subprocess + a local fake OpenAI-compatible LLM, then asserts a `query_fact` tool call round-trips cleanly. A second integration test verifies multi-agent handoff at N=5: four buzz-agent processes each write a `record_decision` to a shared world-model-mcp DB, a fifth agent (fresh process, distinct ACP session id) reads all four back via `get_decision_log`. Runs in ~5 seconds; skips cleanly when `BUZZ_AGENT_BIN` is unset (the one class of skip explicitly documented as legitimate).
- **Adapter docs validation contract.** Every doc under `docs/adapters/` is now enforced by `tests/test_adapters_docs_config.py` (18 tests). Any `WORLD_MODEL_*` env var referenced in a doc must be one that `Config` actually reads; every fenced JSON/YAML block must parse; the `world-model` MCP server invocation must match the reference config shipped in `world_model_server/adapters/cursor/mcp.json`. Rule: no new adapter doc merges without matching validation tests. Two BUZZ-specific schema gotchas caught by writing the integration test are pinned by regression tests: `mcpServers[].env` is a list of `{name, value}` objects (not a dict); MCP server names should use underscores, not dashes, so LLM function-name namespacing stays clean.
- **First pytest CI workflow (`.github/workflows/pytest.yml`).** The 900+ test suite in `tests/` was previously not run in CI (only `cla.yml` and `contradictions-benchmark.yml` ran on PR). It now runs on push-to-main, PR, and workflow_dispatch, with a 15-minute timeout and concurrency cancellation on the same ref. Coverage floor: 69% via `--cov-fail-under=69`, matching the honest baseline measured on 2026-07-22 (69.74% across `world_model_server`). The floor ratchets up, never down.
- **New dev dependencies:** `pytest-cov`, `hypothesis`, `pytest-timeout`, `pytest-randomly`. Order-randomization is on by default; a 5-minute per-test default timeout catches hangs.
- **Adapter index README (`docs/adapters/README.md`).** Entry point for `docs/adapters/`, lists all three current adapters with verification status, documents the sovereign-stack composition, and points contributors at the validation contract.
- **First ADR (`docs/decisions/0001-pin-annotation-mcp-tool.md`).** Design record for the `pin_annotation` MCP tool. Shipped in v0.15.0; the ADR covers data model, MCP tool signature, chain integration, offline verifier updates, OSS-vs-hosted split, and the mandatory 5-test-file surface (unit / integration / security / e2e / property-based).

## What's new in v0.14.0

- **Opt-in telemetry sink migration.** Client now POSTs to `https://etch.systems/api/telemetry/ingest`. Previous v0.7.3–v0.13 series shipped an empty PAT stub, so opt-in telemetry silently no-op'd for two months — this is the first release where it actually delivers events. Endpoint is unauthenticated (rate-limited server-side by `install_id`), 8 KB payload ceiling, source IP stripped from access logs. Payload schema: `event / install_id / version / ts / os_family / python_version / adapters / fields`. Everything on that list, nothing off it — enforced by strict server-side whitelist validation.
- **Daily heartbeat.** Once per 24h per opted-in install, the CLI fires a `heartbeat` event carrying the installed-adapter list. Enables an MAU-of-OSS-users number without touching content.
- **`world-model telemetry --forget-me` (GDPR right-to-erasure).** DELETEs every server-side row for your install_id and wipes local telemetry state (install_id + consent + last-heartbeat). Works even when the server is unreachable — local state is wiped regardless, so consent revocation is honored offline.
- **90-day server-side auto-TTL.** Data minimisation enforced automatically. See [docs/PRIVACY_TELEMETRY.md](docs/PRIVACY_TELEMETRY.md) for the full retention + IP-strip + rate-limit spec.
- **Dead code removed.** `world_model_server/_embedded_token.py` and `scripts/embed_token.py` deleted along with six now-obsolete tests. No PAT dance in the release process anymore.

## What's new in v0.12.13

- **OpenAI-compatible Coach backend.** `verify_retrieval` (v0.12.12's adversarial verification tool) can now route Coach calls through any OpenAI-shape endpoint — OpenRouter, Ollama, vLLM, LiteLLM, or a self-hosted deployment — without going through a proxy. Set `WORLD_MODEL_VERIFICATION_BACKEND=openai-compatible` and `WORLD_MODEL_VERIFICATION_BASE_URL=https://openrouter.ai/api/v1` (or your endpoint of choice); the Coach client is built via `AsyncOpenAI(base_url=...)` and dispatches through `chat.completions.create` with the system prompt in the messages list (OpenAI convention). API key priority: explicit `WORLD_MODEL_VERIFICATION_API_KEY` → `OPENROUTER_API_KEY` → `OPENAI_API_KEY` → a placeholder for local endpoints that don't authenticate. New optional `[openai]` extra ships `openai>=1.0`. Backward compat: default backend stays `anthropic`; existing installs and the v0.12.12 baseline are unaffected.
- **`doctor` Copilot log-signature scan.** New `check_copilot_hook_signatures` check parses `~/.copilot/logs/*.log` for the two documented failure modes from [copilot-cli #4001](https://github.com/github/copilot-cli/issues/4001): PowerShell `ParserError` (Copilot running bash-shaped commands through PowerShell on Windows) and `/.claude/...` path resolution (Copilot not exporting `$CLAUDE_PROJECT_DIR`; hooks run from cwd `/`). SKIPs gracefully when Copilot isn't installed. Reports the two signature counts separately so users can tell which of the two Copilot-side bugs is affecting them. Does NOT fix them — the fix has to come from Copilot — but separates "my hook wrapper is broken" from "the runtime is running my hook wrong."

## What's new in v0.12.12

- **Coach-Player adversarial verification (`verify_retrieval`).** New MCP tool that runs an independent Coach LLM call against a candidate answer plus supplied source facts and returns a confidence band (HIGH / MEDIUM / LOW) with itemized verified + unverified claim lists and per-claim source pointers. Coach lives in its own module (`world_model_server/verification.py`) with its own prompt — no state shared with extraction or reasoning models. That's the adversarial part: Coach doesn't know how the answer was produced, only what the facts say. Contract: never raises. Every failure mode (no API key, empty answer, no facts, Coach LLM error, malformed Coach response) returns a LOW-confidence result with `error` populated. Cheap default: `verification_model` defaults to Haiku 4.5 (~$0.001 per verify call), env-configurable via `WORLD_MODEL_VERIFICATION_MODEL`.
- **12-pair hand-labeled benchmark (`benchmarks/coach-player/`).** 4 grounded, 4 partial, 4 hallucinated pairs plus a runner that reports hallucination catch rate, false positive rate, MEDIUM band correctness, and overall exact match. Ship-floor policy: false positive rate ≤10% is enforced (non-zero exit); hallucination catch ≥95% is aspirational at N=12 and gets enforced once `pairs.json` expands to ≥30 pairs. Full run costs ~$0.03 at Haiku 4.5 pricing.
- **Exposed on both MCP + Hermes surfaces.** MCP `list_tools` gains `verify_retrieval` (27 → 28 tools); Hermes surfaced tool count 7 → 8 with the same tool schema.
- **Pattern origin.** Ported from the maintainer's earlier `y=c` project (Coach-Player adversarial cooperation between a Player synthesizer and a Coach verifier). world-model-mcp is the first MCP server to ship it as a first-class tool with a benchmark harness.

## What's new in v0.12.0

- **`world-model doctor` command (v0.12.1).** Eight diagnostic checks including `.claude/settings.json` shell-quoting (the pre-v0.11.0 unquoted-`$CLAUDE_PROJECT_DIR` bug pattern the dogfooding investigation surfaced), hook script presence, `.mcp.json` registration, world-model DB directory + stale `events_queue.jsonl`, and Claude Code hook-error history filtered by `settings.json` mtime. `--json` for machine-readable output; `--fix` for safe auto-rewrites. Would have caught the v0.11.0 shell-quoting bug automatically instead of via manual investigation.
- **`influence_state` + `expires_at` on Fact (v0.12.2).** Two nullable additive fields. `influence_state` (`observed` / `pending_review` / `approved` / `blocked`) separates two decisions made at different times: **storage is an ingest question** (do we keep this fact at all), **influence is a per-turn question** (do we surface it to the planner right now). Conflating them forces you to re-evaluate storage every time your planning threshold changes; splitting them lets you block a high-confidence fact from influencing a plan *without deleting it* — the disputed-entity-update-pending-review case that comes up often in graph work. A single trust-score is simpler until the first time you need that separation, which in practice happens fast. `expires_at` complements the continuous `last_decay_at` erosion with hard drop-dead timestamps for compliance retention and ephemeral credentials. Migration mirrors the v0.11.1 pattern: NULL-default ALTER, index, no backfill, idempotent.
- **Universal content-type routing consumers (v0.12.3).** Closes the write- and consumer-side loop opened by v0.11.1. That patch added `content_type` to the model and table but never wired a consumer — worse, `create_fact` silently dropped the field on write. v0.12.3 fixes both: `create_fact` persists all three v0.11.1/v0.12.2 new fields, `query_facts` hydrates them on read, and `query_facts` accepts a `content_type` filter. `get_injection_context` is now routing-aware: rules always inject at PostCompact / UserPromptSubmit / SessionStart under a dedicated "## Rules (always active)" section; facts (or NULL) fill remaining slots; procedures are excluded from auto-injection entirely and reachable only via explicit `query_fact(content_type='procedure')`.
- **GitHub Copilot Chat adapter (v0.12.4, `install-copilot`).** Merges into `.vscode/mcp.json` per workspace. Copilot Chat uses top-level `"servers"` (not `"mcpServers"` like every other adapter world-model ships — silently registers nothing if wrong). Merge semantics: absent → write; existing → preserve other servers; existing `world-model` → skip unless `--force`; malformed / wrong-shape JSON → refuse and leave the file untouched.
- **`install-continue --global` config-merge path (v0.12.5).** Merges into `~/.continue/config.yaml`'s `mcpServers` LIST (Continue's schema — distinct from Hermes' mcp_servers-mapping and from Claude Code / Cursor / Copilot / Cline / Windsurf's mcpServers-mapping). `ruamel.yaml` round-trip preserves comments, blank lines, and key ordering.
- **Cline adapter (v0.12.6, `install-cline`).** Merges into `~/.cline/mcp.json`. Cline uses the `mcpServers` mapping shape — same as Cursor / Claude Code.
- **Windsurf adapter (v0.12.7, `install-windsurf`).** Merges into `~/.codeium/windsurf/mcp_config.json`. Same `mcpServers` mapping shape as Cline; only the default path differs.
- **Hermes lifecycle hooks (v0.12.9).** Layers the five optional hooks (`sync_turn`, `on_pre_compress`, `prefetch`, `on_session_end`, `on_memory_write`) on top of the v0.11.0 MemoryProvider ABC. `on_pre_compress` returns a compact injection bundle that honors the v0.12.3 content-type routing — rules always inject, procedures never do. Best-effort contract: exceptions caught, safe default returned; a broken hook must never crash the Hermes loop.
- **MCP 2026-07-28 spec readiness scaffolding (v0.12.11).** Non-behavior-changing observability + public audit against the MCP 2026-07-28 specification (Release Candidate). Ships `world_model_server.spec_readiness.READINESS_STATE` (machine-readable audit matrix locked to five row states), `extract_meta` / `log_meta_if_present` observability helpers wired into `server.py:call_tool`, and `docs/MCP_2026_SPEC_READINESS.md` public audit doc. Backward compatibility with the 2025-03-26 spec preserved unconditionally.
- **Adapter matrix now covers ten runtimes:** Claude Code, Cursor, Codex, Hermes, Continue (per-project + `--global`), OpenClaw, pi, GitHub Copilot Chat, Cline, Windsurf.
- **Deferred per roadmap-gated conditionals:** OpenClaw TypeScript plugin (v0.12.8, no adoption signal within 5 days of v0.10) and Antigravity CLI adapter (v0.12.10, SDK still lacks `TransformCompactionHook` through v1.0.16).

## What's new in v0.11.0

- **`auto` contradiction-resolution strategy rewrite (v0.11.0 A).** Folds in `confirmer` awareness, per-evidence-type decay, distinct-source-tool counting, and tie-detection. Lifts the v0.8.1 contradiction-resolution benchmark's `auto` score from **77.1% → 100.0%** on the same 105-pair × 19-category dataset. Overall benchmark accuracy across four canonical strategies + the decayed strategy rises from 78.2% to 83.7%. Non-auto strategies unchanged. The `keep_higher_confidence_decayed` strategy is promoted from benchmark-only to a first-class option in `pick_winner`. Full detail in `benchmarks/contradictions-200/`.

- **Hermes native `MemoryProvider` plugin + `install-hermes-provider` CLI (v0.11.0 B).** Python plugin implementing Hermes' `agent/memory_provider.py` ABC (`initialize`, `get_tool_schemas`, `handle_tool_call`, `get_config_schema`, `save_config`). Intercepts writes at Hermes' routing layer rather than only surfacing tools — the architectural distinction the v0.10 MCP adapter could not close. Motivated by the [Hermes #47349 exchange](https://github.com/NousResearch/hermes-agent/issues/47349) where @TechFlipsi surfaced that adding another MCP-registered store doesn't fix "the agent still defaults to writing `MEMORY.md`" — only a MemoryProvider does. Ships as `world_model_server/hermes_memory_provider/` in the wheel; `install-hermes-provider` copies the plugin to `<hermes_home>/plugins/memory/world-model/`. Seven surfaced tools (`query_fact`, `get_constraints`, `get_injection_context`, `record_event`, `record_correction`, `find_contradictions`, `resolve_contradiction`) — trimmed from the 27 exposed via MCP to keep Hermes' tool namespace focused. Optional Hermes lifecycle hooks (`sync_turn`, `on_pre_compress`, `prefetch`, `on_session_end`) tracked as v0.12.

- **Content-type routing schema field (v0.11.1).** Nullable `content_type: Optional[Literal["rule", "fact", "procedure"]]` on the `Fact` model and the facts table. Additive-only migration; existing rows keep NULL and continue to work. Distinct from `evidence_type` (which describes where the fact came from) — `content_type` describes what shape of content the fact carries, so a MemoryProvider can route writes intelligently (rules → always-inject, facts → search-on-demand, procedures → skills store) instead of dumping everything into one destination. Sourced from the Hermes #47349 architectural framing. Consumers (query filters, MemoryProvider write routing) are v0.11.x follow-ups; this ships the schema, tests, and migration only.

- **Dogfooding case study (v0.11.2) — surfaced a real shipped bug that was fixed in the same release.** Publishes what the fact graph actually captured about the world-model-mcp codebase in `.claude/world-model/`: **3 learned constraints** with real violation counts (including `check-twine-before-tag` and `tag-before-upload`, both derived from real release-mechanics incidents matching the v0.9.1 telemetry-token miss and the v0.10.1 tagging lesson), **1 bug_fix reflection** citing a real bug in `world_model_server/knowledge_graph.py:120-135`, 608 facts (607 from the seeder + the one bug_fix), 600 entities. **Honest about what was NOT captured** (empty `events` / `decisions` / `sessions` tables). Pushing on that anomaly hard enough surfaced the actual root cause: `setup_command` wrote unquoted `$CLAUDE_PROJECT_DIR` in every generated hook command, so any user whose project path contains a space (macOS defaults like `~/Documents/`, corporate paths, or the maintainer's own `claude context graph/world-model-mcp`) has been silently failing every hook invocation since v0.7.3 shipped hooks. **The fix ships in this release** — two-line shell-quoting change in `setup_command` + regression test. This is the exact kind of latent bug that dogfooding is supposed to catch, and it did. Reproducibility contract: `python scripts/dogfooding_snapshot.py --db-path .claude/world-model` regenerates the committed JSON byte-for-byte, and drift-protection tests fail if the writeup and the snapshot diverge. See [`case-studies/v011-dogfooding/`](./case-studies/v011-dogfooding/).

- **What is unchanged.** All v0.10.x code paths: the 27 MCP tools reported by adapters (no new server-side tools in v0.11), the SWE-bench Verified benchmark and its multi-seed appendix, the seven-runtime adapter coverage (Claude Code + Cursor + Codex + pi + OpenClaw + Hermes Agent + Continue), the Zenodo preprint (paper unchanged since v0.9.2; no new Zenodo version). v0.11 is a depth release — better contradiction-resolution intelligence, a second Hermes integration path, a schema axis for future routing work, and honest evidence for the dogfooding claim. Test count grew from 417 (v0.10) to 457 (+21 for the Hermes MemoryProvider plugin, +10 for the content-type schema, +9 for the case-study drift protection).

## What's new in v0.10.0

- **Three new adapters in one release: OpenClaw, Hermes Agent, Continue.** All three verified end-to-end against real installations of the target runtime:
  - **OpenClaw** — `install-openclaw` merges into `~/.openclaw/openclaw.json`. Verified against OpenClaw `2026.6.11 (e085fa1)` on macOS: `openclaw mcp probe world-model` reports 27 tools discovered. Root cause of the first-attempt "MCP error -32000: Connection closed" surfaced and fixed during E2E: OpenClaw's process spawn does not inherit shell PATH, so `--command python3` fails while an absolute path works. The CLI now defaults `command` to `sys.executable` (absolute) and rejects relative `--python` overrides as a hard error. Documented as an install-time gotcha in the adapter README.
  - **Hermes Agent** — `install-hermes` merges into `~/.hermes/config.yaml` under `mcp_servers.world-model`. Uses `ruamel.yaml` round-trip mode to preserve every comment and blank line in Hermes' heavily-commented 1327-line reference config. A regression test (`test_f2_install_hermes_preserves_comments_and_blank_lines`) locks this down after a pre-E2E `pyyaml.safe_dump` implementation stripped ~1170 lines of documentation. Verified against Hermes Agent `v0.17.0 (2026.6.19)` on macOS: `hermes mcp test world-model` reports 27 tools discovered. Hermes' built-in memory (character-capped, no auto-decay per Hermes docs) is complemented additively by world-model-mcp's provenance + decay schema. Requires the `[hermes]` optional extra (`pip install "world-model-mcp[hermes]"`) so `ruamel.yaml` is available.
  - **Continue** — `install-continue` writes a standalone `<project>/.continue/mcpServers/world-model.yaml` following Continue's documented per-server-file pattern. No config merge needed. CLI-side E2E: the exact stdio spawn Continue would perform returns 27 tools via a live `tools/list` roundtrip. Last-mile "does Continue's LLM see them in agent mode" verification requires a live VS Code / JetBrains session. Reprioritized after the SpaceX/Cursor acquisition to serve teams standardizing on OSS-neutral coding-agent workflows.

- **Absolute-path posture across all v0.10 adapters.** OpenClaw's PATH-spawn issue was caught first, but the same absolute-path default applies to Hermes and Continue as a precaution. Every new install command defaults `command` to `sys.executable` and rejects relative `--python` overrides. Users who hand-edit config files are directed to `$(which python3)` in both READMEs.

- **Cross-runtime shared memory.** All v0.10 adapters (and every prior adapter) default `WORLD_MODEL_DB_PATH` to `.claude/world-model` — a relative path resolved against the client's working directory. This means a project that runs in multiple clients (e.g., Claude Code + Continue + OpenClaw) shares one SQLite fact graph across all of them. For user-wide shared memory regardless of CWD, override with an absolute `--db-path`. The differentiator against [ClawMem](https://github.com/yoloshii/ClawMem) (which does cross-runtime memory with a plain key-value SQLite vault) is depth: per-fact provenance, per-evidence-type decay half-lives, PreToolUse defer enforcement.

- **What is unchanged.** All v0.9.2 code paths: the 26 base MCP tools (v0.10 adds no new server-side tools; the "27 tools" count reported by adapters includes `resolve_contradiction` which shipped in v0.8.0), the SWE-bench Verified benchmark, the multi-seed replication appendix, the wedge claims. v0.10 is an adapter-surface release, not a schema-or-benchmark release. Test count grew from 375 (v0.9.2) to 417 with the three new adapter test suites; every baseline test still passes.

- **Test breakdown.** 375 baseline + 14 OpenClaw + 16 Hermes + 12 Continue = 417 tests. Every adapter's test suite includes: bundled-file validity, dry-run behavior, first-install writes with absolute-path defaults, idempotence (refuse to overwrite without `--force`), `--force` overwrite, relative-`--python` rejection, parent-directory creation, malformed-config-file handling, and subparser-registration regression coverage.

## What's new in v0.9.2

- **Multi-seed replication appendix shipped per `SEED_PLAN.md`**. The v0.9 paper's primary limitation was single-trial design. v0.9.2 ships the multi-seed test that SEED_PLAN.md (locked 2026-06-25) committed to running. The result is published verbatim per the pre-registered acceptance criteria.

- **Honest update to the v0.9 headline**. On the 17-instance pre-registered subset, baseline pass rate swung +41 percentage points between seed 1 and seed 2 with no methodology change. Load-bearing replication is 0 of 7 instances. Mean paired delta across both seeds is +0.24 per instance with bootstrap 95 percent CI [0.00, 0.47]. The v0.9 +10.2 pts paired delta should be read as a single-trial upper bound; the replicated effect size is small, possibly nonzero.

- **What is unchanged**: all v0.9.1 code, the 26 MCP tools, the 19 CLI subcommands, the 375 tests, the wedge claims at the architectural level (lifecycle-hook capture, per-fact provenance, per-evidence-type decay, PreToolUse defer). Architectural claims do not depend on the empirical effect size and survive the multi-seed update.

- **Documentation diffs**: `benchmarks/repeat-mistake/RESULTS.md` adds a "Multi-seed replication appendix (v0.9.2 update)". `benchmarks/repeat-mistake/paper.md` adds Appendix A with the same content. `benchmarks/repeat-mistake/paper.pdf` is regenerated. `benchmarks/repeat-mistake/SEED_PLAN.md` adds a status update (the locked plan above is unchanged). Raw seed-2 artifacts (`baseline_progress_seed2.jsonl`, `treatment_progress_seed2_treatment.jsonl`, predictions, results, and the `multi_seed_summary_seed2.json` from `multi_seed_aggregate.py`) committed.

- **The methodology discipline held**. Pre-registration prevented goalpost-moving. The honest update is published per the locked SEED_PLAN.md acceptance criteria. This is what pre-registration is for.

## What's new in v0.9.0

- **Repeat-mistake benchmark on SWE-bench Verified** — the central wedge proof. 50 SWE-bench Verified tasks across django, sympy, matplotlib, scikit-learn, and sphinx, run as a paired baseline-vs-treatment comparison. Methodology was locked at [`benchmarks/repeat-mistake/DESIGN.md`](benchmarks/repeat-mistake/DESIGN.md) on 2026-06-17 (before the data existed) so the result cannot be accused of goalpost-moving.

- **Headline results** — Subset 1 (within-domain: django + sympy) baseline 15/20 = 75.0 percent, treatment 18/20 = 90.0 percent, delta +15.0 pts with 4 FAIL to PASS flips and 1 regression. Subset 2 (cross-domain: matplotlib + scikit-learn + sphinx) baseline 18/29 = 62.1 percent, treatment 20/29 = 69.0 percent, delta +6.9 pts with 2 flips and zero regressions. Combined paired result across 49 instances: 33/49 to 38/49, delta +10.2 pts.

- **Cross-domain transfer isolated cleanly** — the Subset 2 treatment arm loaded ONLY the 4 Subset 1 constraints (django and sympy directives), holding out the 11 Subset 2 constraints to test whether learning from one repo family generalizes to a different one. Two cross-domain flips with plausible mechanistic explanations grounded in the loaded constraints. Sphinx-9461 is the strongest case: a sympy classmethod constraint transferred to a sphinx classmethod-wrapper unwrapping bug.

- **Honest caveats embedded in RESULTS.md** — seven explicit limitations including single-trial design, constraint-failure overlap on Subset 1, the small cross-domain transfer rate, one dropped instance due to an upstream SWE-bench pip flag issue, and judge-model self-reference risk. Stated verbatim rather than hidden in an appendix.

- **Full reproducibility artifacts** — every progress JSONL, predictions JSON, results JSONL, classification JSONL, constraints JSON, and harness report JSON committed in [`benchmarks/repeat-mistake/`](benchmarks/repeat-mistake/). Locked judge prompts in `failure_classifier.py` and `learning_hook.py`. Total agent cost across both arms was approximately 90 USD on a Claude Code subscription.

## What's new in v0.8.1

- **Contradiction-resolution benchmark expansion** -- the v0.7.4 24-pair benchmark grew to 105 hand-curated pairs across 19 categories. Six new categories exercise the v0.8.0 schema specifically: `source_tool_corroboration`, `confirmer_overrides_pending`, `decay_advantage_session_vs_source`, `decay_advantage_stale_session`, `evidence_type_user_correction`, `settled_beats_higher_confidence`. Deterministic runner at [`benchmarks/contradictions-200/run.py`](benchmarks/contradictions-200/run.py); full per-strategy + per-category breakdown at [`benchmarks/contradictions-200/RESULTS.md`](benchmarks/contradictions-200/RESULTS.md).

- **Honest framing on the numbers**: the new dataset is harder than v0.7.4's 24-pair set because the new categories deliberately test schema awareness (confirmer, evidence_type, decay) rather than raw confidence ranking. Headline numbers: `keep_most_sources` 99.0%, `keep_higher_confidence` 81.0%, `auto` 77.1%, `keep_higher_confidence_decayed` 90.5% (on the 21 pairs where evidence_type is present), overall 78.2% across all strategies. The original 24-pair v0.7.4 93.5% number is preserved unchanged at `benchmarks/contradictions/` and is not invalidated; it tested a different (smaller, easier) corpus.

- **The wedge benchmark is v0.9**: "does the learning loop measurably reduce repeated coding-agent mistakes on a public task corpus?" The contradiction-resolution work in this release is internal schema-correctness validation. The empirical artifact that maps to the published essay framing — the learning loop is the durable layer — lands in v0.9 with a SWE-bench-style repeat-mistake benchmark.

## What's new in v0.8.0

- **Domain-aware confidence decay** -- new `world_model_server/decay.py` module with exponential half-life decay per `evidence_type`. Half-lives: source_code 365d, test 180d, session 14d, user_correction 730d, bug_fix 365d. Decay applies on read (no background task), so the next `query_fact` call returns the time-corrected confidence. Settled facts (`canonical` status, or any fact with `confirmer != NULL`) never auto-transition. Synthesized facts that decay below 0.2 confidence and corroborated facts that decay below 0.1 confidence auto-supersede on read, surfacing rot to the next compaction injection.

- **Per-item provenance fields on facts** -- three additive columns (`source_tool TEXT`, `confirmer TEXT`, `last_decay_at TIMESTAMP`), all NULL-defaulted, no backfill. `source_tool` records which tool wrote the fact (e.g. `claude_code`, `codex`, `cursor`, `pi`, `user`). `confirmer` records who confirmed it, distinct from the asserter; NULL means pending, non-NULL means settled. Both are exposed on the `Fact` model and propagated through `create_fact`. Honors the public commitment to Patdolitse (anthropics/claude-code#47023) and ferhimedamine (openai/codex#19195).

- **Slash command write operations** -- two new subcommands. `/world-model resolve <id>` marks a contradiction as resolved (manual; for confidence-weighted picking use the `resolve_contradiction` MCP tool). `/world-model forget <id>` sets `invalid_at` on a fact (preserved in the audit log; current-only reads skip it from then on). Both are idempotent and report cleanly on unknown ids. Help text now lists both alongside the read-only subcommands shipped in v0.7.6.

- **`resolve_contradiction` accepts `confirmer`** -- when a `confirmer` argument is provided to the MCP tool or its underlying `resolve` function, the winning fact gets its `confirmer` column stamped with that value. This is the spec primitive that distinguishes "the asserter says X" from "X is confirmed by Y" per the working group sketch.

- **Antigravity adapter held for the third consecutive release.** The 2026-06-13 re-verification found `OnCompactionHook` declared as `InspectHook` in the SDK with no `TransformCompactionHook` and no `additional_context` return field. The load-bearing memory-injection contract still does not exist in the SDK.

## What's new in v0.7.6

- **In-agent `/world-model` slash command** -- typed by the user inside the agent harness, surfaces the world model state without leaving the chat. Read-only in v0.7.6 (`status`, `contradictions`, `recent`, `help`); write operations (`resolve`, `forget`) land in v0.8. Works across Claude Code, Cursor, Codex, and pi by intercepting `UserPromptSubmit` in the existing `inject_helper`. Returns `additionalContext` in the strict camelCase shape Codex enforces (`deny_unknown_fields`), so the same wire-up serves all four harnesses without a per-harness branch.
- **`world-model status-watch` TUI widget** -- terminal pane that runs alongside the agent and refreshes every 5 seconds. Shows constraints (total, severity=error, severity=warning), unresolved contradictions, facts (canonical / synthesized / superseded), and last compaction time. Built on the `rich` library already in the dependency tree; falls back to a plain-text one-shot dump when `rich` is not installed.
- **Antigravity CLI adapter intentionally NOT shipped in this release** -- the re-verification on 2026-06-13 against `google-antigravity/antigravity-sdk-python` HEAD surfaced an architectural gap: `OnCompactionHook` is declared as an `InspectHook` (read-only, non-blocking) with no `additional_context` return field and no `TransformCompactionHook` subclass. The load-bearing memory-injection contract does not exist in the SDK today. v0.7.6 ships without Antigravity rather than against a contract that cannot do the work.

## What's new in v0.7.5

- **Codex CLI adapter** -- new `install-codex` CLI subcommand appends a `[mcp_servers.world_model]` block plus PreToolUse, PostToolUse, PostCompact, and SessionStart hooks to `~/.codex/config.toml`. The bundled snippet was verified against `openai/codex@main` at v0.138.0-alpha (server name uses underscore to dodge the tool-name hyphen-strip in `codex-rs/codex-mcp/src/mcp/mod.rs`; hook output sticks to camelCase with `deny_unknown_fields` compliance). Schema regression tests in `tests/test_v075_features.py` lock the contract down. See [adapters/codex/README.md](adapters/codex/README.md).
- **Dual-shape payload normalization in `hook_helper` and `inject_helper`** -- both helpers now accept either Claude Code's payload shape (`event`, `project_dir`) or Codex's (`hook_event_name`, `cwd`), so the same Python code drives all four adapters (Claude Code, Cursor, pi, Codex).
- **Antigravity CLI adapter intentionally NOT shipped this release** -- the Antigravity API surface is still settling (six 1.0.x releases in three weeks, the `url` field for HTTP MCP servers landed June 3, hook JSON event-name casing remains undocumented). Targeting June 25 for that adapter after the API stabilizes. Detailed reasoning in the v0.7.5 RELEASE_NOTES entry.

## What's new in v0.7.4

- **AGENTS.md / `.agents/skills/` constraint reader** -- world-model-mcp now reads declarative project conventions from `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and `.agents/skills/*.md` files and mixes them into PreToolUse enforcement alongside the SQLite-backed constraints. Supports structured fence blocks (```` ```constraint ```` and YAML frontmatter) and heuristic imperative-sentence extraction for prose-style AGENTS.md files. New MCP tool: `get_agents_md_constraints`. ([anthropics/claude-code#6235](https://github.com/anthropics/claude-code/issues/6235) has 4,000+ thumbs-up for AGENTS.md as the cross-agent format.)
- **Self-hosted Claude Managed Agents deployment guide** -- Anthropic's [official position](https://claude.com/blog/claude-managed-agents-updates): *"Memory is not yet supported in self-hosted sessions."* world-model-mcp fills that gap. New guide at [`docs/deployment/managed-agents-self-hosted.md`](docs/deployment/managed-agents-self-hosted.md), with a [Modal quickstart](examples/managed-agents-self-hosted/) you can deploy in under five minutes.
- **Reproducible contradiction-resolution benchmark** -- 24-pair dataset at [`benchmarks/contradictions/dataset.jsonl`](benchmarks/contradictions/dataset.jsonl), runner at [`benchmarks/contradictions/run.py`](benchmarks/contradictions/run.py), results at [`benchmarks/contradictions/RESULTS.md`](benchmarks/contradictions/RESULTS.md). Headline: 93.5% overall accuracy, 100% on `keep_higher_confidence` and `keep_most_sources`, with documented honest weaknesses on tie-handling and small confidence gaps. Re-run with `python benchmarks/contradictions/run.py`. CI workflow guards regressions.

## What's new in v0.7.3

- **`world-model demo`** -- one command to see every primitive working. Initializes the knowledge graph, seeds reproducible demo data via `scripts/demo_seed.py`, then exercises each primitive (PreToolUse enforcement, contradiction detection, PostCompact injection, audit log) with real outputs. New users can see the value without writing any code.
- **Opt-in telemetry** -- off by default, prompted once during `world-model setup`, inspectable with `world-model telemetry --status`, disabled with `world-model telemetry --disable`, fully erased with `world-model telemetry --forget-me` (v0.14+). No file paths, no code, no identifiers tied to a person. Sink migrated to https://etch.systems/api/telemetry/ingest in v0.14 — the previous private GitHub Issues sink shipped an empty PAT stub in every wheel so it silently no-op'd; v0.14 is the first release where opt-in telemetry actually works. See [Privacy and Security](#privacy-and-security) for the exact payload.
- **pi adapter** -- new `adapters/pi/` package. world-model-mcp now plugs into [earendil-works/pi](https://github.com/earendil-works/pi) via pi's extension API (`tool_call` -> PreToolUse, `context` -> auto-injection, `session_compact` -> audit log). Install with `world-model install-pi`.

## What v0.7.0 introduced (still active)

- **PostCompact / UserPromptSubmit auto-injection** -- when the agent's context is compacted, the hook automatically splices the top constraints and recent canonical facts back into the next turn. Configurable, fails open.
- **`defer` enforcement tier** -- PreToolUse now classifies recurring warning-level violations as `defer`, which pauses headless agents (with graceful fallback to `ask` on older clients) instead of either hard-denying or silently passing through.
- **Confidence-weighted contradiction resolution** -- the new `resolve_contradiction` tool picks a winner using `keep_higher_confidence`, `keep_most_recent`, `keep_most_sources`, or `auto`. The loser is marked superseded.
- **Compaction audit log** -- every PostCompact event writes a row with pre/post token counts and what was re-injected. Query with the `audit-compactions` CLI or export to JSONL.
- **Cursor adapter** -- harness-neutral hooks under `adapters/cursor/`. Same Python helpers, different manifest format.
- **Streamable HTTP transport (v0.7.2)** -- `WORLD_MODEL_TRANSPORT=http` so the same 25 MCP tools work behind an MCP tunnel for Claude Managed Agents with self-hosted sandboxes. See [docs/deployment/mcp-tunnel.md](docs/deployment/mcp-tunnel.md).

