From 627a02bea93072af28cda3edfa7d19eb409a6194 Mon Sep 17 00:00:00 2001
From: Denys Kashkovskyi
Date: Mon, 27 Jul 2026 21:40:19 +0200
Subject: [PATCH 01/86] Make Threadnote self-contained for 4.0 beta
---
.github/workflows/benchmarks.yml | 91 +
.github/workflows/ci.yml | 58 +-
.gitignore | 2 +
README.md | 203 +-
THIRD_PARTY.md | 45 +-
.../io.threadnote.openviking.plist.template | 31 -
config/ov.conf.template.json | 14 -
config/ovcli.conf.template.json | 7 -
config/post-update-migrations.json | 43 +-
config/seed-manifest.example.yaml | 2 +-
docs/4.0-plan.md | 690 ++++
...013-evaluation-and-benchmark-governance.md | 24 +
docs/adr/014-native-storage-and-indexes.md | 43 +
docs/adr/015-effect-ai-and-node-llama-cpp.md | 26 +
docs/adr/016-home-migration-and-rollback.md | 22 +
docs/agent-instructions.md | 88 +-
docs/effect.md | 216 +-
docs/index.html | 80 +-
docs/migration.md | 285 +-
docs/rollout.md | 34 +-
docs/security.md | 40 +-
docs/share.md | 485 +--
docs/troubleshooting.md | 305 +-
docs/web-manager.png | Bin 821172 -> 0 bytes
package-lock.json | 1801 ++++++++-
package.json | 18 +-
scripts/benchmark-recall-micro.mjs | 79 +
scripts/benchmark-recall-v2.ts | 149 +
scripts/benchmark-target.ts | 23 +
scripts/build-benchmark.mjs | 12 +
scripts/build.mjs | 2 +
scripts/capture-recall-baseline.ts | 61 +
scripts/capture-recall-v2-baseline.ts | 89 +
scripts/check-self-contained.mjs | 87 +
scripts/evaluate-recall-models.ts | 230 ++
scripts/evaluate-recall-v2.ts | 148 +
scripts/install-e2e-openviking.mjs | 116 -
scripts/install.ps1 | 46 +-
scripts/install.sh | 6 +-
scripts/local-ai-server.py | 170 -
scripts/smoke-self-contained.mjs | 126 +
src/constants.ts | 26 +-
src/crypto/sha256.ts | 79 +
.../consolidator.ts} | 66 +-
src/effect/ai/embedding.ts | 38 +
.../{ai-enrichment.ts => ai/enrichment.ts} | 77 +-
src/effect/ai/errors.ts | 98 +
src/effect/ai/llama-cpp-engine.ts | 74 +
src/effect/ai/local-model-runtime.ts | 196 +
src/effect/{ => ai}/mcp.ts | 4 +-
src/effect/ai/node-llama-cpp.ts | 477 +++
src/effect/{ai-recall.ts => ai/recall.ts} | 119 +-
src/effect/ai/reranker.ts | 32 +
src/effect/ai/structured-generator.ts | 56 +
src/effect/cli.ts | 206 +-
src/effect/command.ts | 30 +-
src/effect/digest.ts | 21 +-
src/effect/errors.ts | 5 +
src/effect/http.ts | 92 +-
src/effect/local-ai.ts | 1235 +-----
src/effect/openviking.ts | 69 -
src/effect/resource-store.ts | 703 ++++
src/effect/runtime.ts | 20 +-
src/effect/system.ts | 20 +-
src/evaluation/benchmark.ts | 137 +
src/evaluation/recall-baseline.ts | 111 +
src/evaluation/recall-fixture.ts | 688 ++++
src/evaluation/recall-gate.ts | 118 +
src/evaluation/recall.ts | 748 ++++
src/index_repair.ts | 470 ---
src/launchd.ts | 128 -
src/lifecycle.ts | 3578 ++---------------
src/local_ai_models.ts | 47 -
src/manager.ts | 69 +-
src/manager_ui.tsx | 7 +-
src/mcp.ts | 151 +-
src/mcp_server.ts | 1115 ++---
src/memory.ts | 536 ++-
src/memory_document.ts | 8 +-
src/migration/home.ts | 684 ++++
src/models/builtin.ts | 105 +
src/models/catalog.ts | 170 +
src/models/commands.ts | 120 +
src/models/inference.ts | 56 +
src/models/selection.ts | 83 +
src/models/store.ts | 290 ++
src/onboarding.ts | 35 +-
src/recall/index.ts | 319 +-
src/recall/rank.ts | 20 +-
src/recall/runtime.ts | 128 +-
src/runtime.ts | 92 +-
src/search/chunker.ts | 121 +
src/search/commands.ts | 81 +
src/search/vector-index.ts | 405 ++
src/search/vector-search.ts | 138 +
src/search/vector-sidecar.ts | 196 +
src/seeding.ts | 242 +-
src/share.ts | 436 +-
src/storage/layout.ts | 51 +
src/storage/resource-id.ts | 118 +
src/types.ts | 13 -
src/update.ts | 270 +-
src/utils.ts | 152 +-
test/e2e/local-bins.e2e.ts | 2061 +---------
test/e2e/windows-install.windows.e2e.ts | 646 +--
test/evaluation/README.md | 130 +
.../threadnote-3.0.3/benchmarks/README.md | 23 +
.../recall-index-10000.json | 65 +
.../darwin-arm64-m1-max/recall-micro.json | 83 +
.../darwin-arm64-m1-max/recall-rank-1000.json | 106 +
.../recall-rank-10000.json | 106 +
.../recall-rank-100000.json | 106 +
.../darwin-arm64-m1-max/recall-rank-200.json | 106 +
.../baselines/threadnote-3.0.3/recall-v1.json | 90 +
.../threadnote-3.0.3/recall-v2-lexical.json | 309 ++
.../candidates/threadnote-4.0.0/README.md | 28 +
...v1.5-q8+jina-reranker-v1-turbo-en-f16.json | 638 +++
.../bge-small-en-v1.5-q8.json | 632 +++
test/integration/cli.effect.test.ts | 10 +-
...arity.test.ts => mcp.native-tools.test.ts} | 398 +-
test/integration/mcp.share-publish.test.ts | 56 -
.../memory.project-name-migration.test.ts | 3 -
test/integration/share.retry.test.ts | 202 -
test/integration/share.sync.test.ts | 316 +-
test/unit/agent-instructions.test.ts | 3 +-
test/unit/effect-ai-consolidator.test.ts | 2 +-
test/unit/effect-ai-enrichment.test.ts | 2 +-
test/unit/effect-ai-native.test.ts | 419 ++
test/unit/effect-ai-recall.test.ts | 2 +-
test/unit/effect-architecture.test.ts | 32 +-
test/unit/effect-command.test.ts | 58 +-
test/unit/effect-openviking.test.ts | 79 -
test/unit/evaluation.baselines.test.ts | 90 +
test/unit/evaluation.recall-v2.test.ts | 277 ++
test/unit/home-migration.test.ts | 264 ++
test/unit/index_repair.test.ts | 490 ---
test/unit/launchd.test.ts | 201 -
test/unit/lifecycle.doctor.test.ts | 3 -
test/unit/lifecycle.install.test.ts | 568 ---
test/unit/lifecycle.launchd.test.ts | 499 ---
test/unit/local-ai.test.ts | 456 +--
test/unit/manager.test.ts | 3 -
test/unit/mcp.test.ts | 3 -
test/unit/memory-native.test.ts | 177 +
test/unit/memory.enrichment.test.ts | 379 +-
test/unit/memory.recall.test.ts | 46 +-
test/unit/memory.shared-replace.test.ts | 11 +-
test/unit/memory_document.test.ts | 2 +-
test/unit/model-catalog.test.ts | 56 +
test/unit/model-selection.test.ts | 25 +
test/unit/model-store.test.ts | 136 +
test/unit/onboarding.test.ts | 16 +-
test/unit/recall-chunker.test.ts | 45 +
test/unit/recall.index.test.ts | 103 +-
test/unit/recall.rank.test.ts | 27 +-
test/unit/recall.runtime.test.ts | 287 +-
test/unit/resource-store.test.ts | 316 ++
test/unit/runtime.test.ts | 45 -
test/unit/seeding.test.ts | 233 +-
test/unit/sha256.test.ts | 13 +
test/unit/share.admin.test.ts | 32 +-
test/unit/share.artifacts.test.ts | 108 +-
test/unit/share.changes.test.ts | 37 +-
test/unit/share.mappings.test.ts | 2 +-
test/unit/share.publish.test.ts | 101 +-
test/unit/update.test.ts | 229 +-
test/unit/utils.test.ts | 83 +-
test/unit/vector-index.test.ts | 226 ++
test/unit/vector-search.test.ts | 153 +
test/unit/windows-support.test.ts | 341 +-
170 files changed, 18058 insertions(+), 16712 deletions(-)
create mode 100644 .github/workflows/benchmarks.yml
delete mode 100644 config/launchd/io.threadnote.openviking.plist.template
delete mode 100644 config/ov.conf.template.json
delete mode 100644 config/ovcli.conf.template.json
create mode 100644 docs/4.0-plan.md
create mode 100644 docs/adr/013-evaluation-and-benchmark-governance.md
create mode 100644 docs/adr/014-native-storage-and-indexes.md
create mode 100644 docs/adr/015-effect-ai-and-node-llama-cpp.md
create mode 100644 docs/adr/016-home-migration-and-rollback.md
delete mode 100644 docs/web-manager.png
create mode 100644 scripts/benchmark-recall-micro.mjs
create mode 100644 scripts/benchmark-recall-v2.ts
create mode 100644 scripts/benchmark-target.ts
create mode 100644 scripts/build-benchmark.mjs
create mode 100644 scripts/capture-recall-baseline.ts
create mode 100644 scripts/capture-recall-v2-baseline.ts
create mode 100644 scripts/check-self-contained.mjs
create mode 100644 scripts/evaluate-recall-models.ts
create mode 100644 scripts/evaluate-recall-v2.ts
delete mode 100644 scripts/install-e2e-openviking.mjs
delete mode 100644 scripts/local-ai-server.py
create mode 100644 scripts/smoke-self-contained.mjs
create mode 100644 src/crypto/sha256.ts
rename src/effect/{ai-consolidator.ts => ai/consolidator.ts} (79%)
create mode 100644 src/effect/ai/embedding.ts
rename src/effect/{ai-enrichment.ts => ai/enrichment.ts} (77%)
create mode 100644 src/effect/ai/errors.ts
create mode 100644 src/effect/ai/llama-cpp-engine.ts
create mode 100644 src/effect/ai/local-model-runtime.ts
rename src/effect/{ => ai}/mcp.ts (98%)
create mode 100644 src/effect/ai/node-llama-cpp.ts
rename src/effect/{ai-recall.ts => ai/recall.ts} (83%)
create mode 100644 src/effect/ai/reranker.ts
create mode 100644 src/effect/ai/structured-generator.ts
delete mode 100644 src/effect/openviking.ts
create mode 100644 src/effect/resource-store.ts
create mode 100644 src/evaluation/benchmark.ts
create mode 100644 src/evaluation/recall-baseline.ts
create mode 100644 src/evaluation/recall-fixture.ts
create mode 100644 src/evaluation/recall-gate.ts
create mode 100644 src/evaluation/recall.ts
delete mode 100644 src/index_repair.ts
delete mode 100644 src/launchd.ts
delete mode 100644 src/local_ai_models.ts
create mode 100644 src/migration/home.ts
create mode 100644 src/models/builtin.ts
create mode 100644 src/models/catalog.ts
create mode 100644 src/models/commands.ts
create mode 100644 src/models/inference.ts
create mode 100644 src/models/selection.ts
create mode 100644 src/models/store.ts
create mode 100644 src/search/chunker.ts
create mode 100644 src/search/commands.ts
create mode 100644 src/search/vector-index.ts
create mode 100644 src/search/vector-search.ts
create mode 100644 src/search/vector-sidecar.ts
create mode 100644 src/storage/layout.ts
create mode 100644 src/storage/resource-id.ts
create mode 100644 test/evaluation/README.md
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/README.md
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-index-10000.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-micro.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-rank-1000.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-rank-10000.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-rank-100000.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/benchmarks/darwin-arm64-m1-max/recall-rank-200.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/recall-v1.json
create mode 100644 test/evaluation/baselines/threadnote-3.0.3/recall-v2-lexical.json
create mode 100644 test/evaluation/candidates/threadnote-4.0.0/README.md
create mode 100644 test/evaluation/candidates/threadnote-4.0.0/bge-small-en-v1.5-q8+jina-reranker-v1-turbo-en-f16.json
create mode 100644 test/evaluation/candidates/threadnote-4.0.0/bge-small-en-v1.5-q8.json
rename test/integration/{mcp.openviking-parity.test.ts => mcp.native-tools.test.ts} (54%)
delete mode 100644 test/integration/share.retry.test.ts
create mode 100644 test/unit/effect-ai-native.test.ts
delete mode 100644 test/unit/effect-openviking.test.ts
create mode 100644 test/unit/evaluation.baselines.test.ts
create mode 100644 test/unit/evaluation.recall-v2.test.ts
create mode 100644 test/unit/home-migration.test.ts
delete mode 100644 test/unit/index_repair.test.ts
delete mode 100644 test/unit/launchd.test.ts
delete mode 100644 test/unit/lifecycle.install.test.ts
delete mode 100644 test/unit/lifecycle.launchd.test.ts
create mode 100644 test/unit/memory-native.test.ts
create mode 100644 test/unit/model-catalog.test.ts
create mode 100644 test/unit/model-selection.test.ts
create mode 100644 test/unit/model-store.test.ts
create mode 100644 test/unit/recall-chunker.test.ts
create mode 100644 test/unit/resource-store.test.ts
delete mode 100644 test/unit/runtime.test.ts
create mode 100644 test/unit/sha256.test.ts
create mode 100644 test/unit/vector-index.test.ts
create mode 100644 test/unit/vector-search.test.ts
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
new file mode 100644
index 0000000..392144d
--- /dev/null
+++ b/.github/workflows/benchmarks.yml
@@ -0,0 +1,91 @@
+name: Platform benchmarks
+
+on:
+ workflow_dispatch:
+ inputs:
+ include_100k:
+ description: Run the 100k scale-boundary suite on Linux
+ required: false
+ type: boolean
+ default: false
+ schedule:
+ - cron: '23 3 * * 1'
+
+permissions:
+ contents: read
+
+concurrency:
+ group: benchmarks-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ recall-10k:
+ name: Recall 10k · ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version-file: .nvmrc
+ cache: npm
+
+ - run: npm ci
+
+ - name: Capture process benchmark
+ run: >-
+ npm run bench:recall --
+ --documents 10000
+ --samples 25
+ --warmups 5
+ --output artifacts/recall-10k-${{ runner.os }}-${{ runner.arch }}.json
+
+ - name: Capture built-artifact microbenchmark
+ shell: bash
+ run: >-
+ npm run bench:recall:micro --
+ --output artifacts/recall-micro-${{ runner.os }}-${{ runner.arch }}.json
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: recall-benchmarks-${{ runner.os }}-${{ runner.arch }}
+ path: artifacts/
+ if-no-files-found: error
+ retention-days: 30
+
+ recall-100k:
+ name: Recall 100k scale boundary · linux
+ if: github.event_name == 'schedule' || inputs.include_100k
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version-file: .nvmrc
+ cache: npm
+
+ - run: npm ci
+
+ - name: Capture 100k process benchmark
+ run: >-
+ npm run bench:recall --
+ --documents 100000
+ --samples 5
+ --warmups 1
+ --output artifacts/recall-100k-Linux-${{ runner.arch }}.json
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: recall-benchmarks-100k-Linux-${{ runner.arch }}
+ path: artifacts/
+ if-no-files-found: error
+ retention-days: 30
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b04302a..a67797b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,6 +34,8 @@ jobs:
- run: npm run check:bundle-size
+ - run: npm run check:self-contained
+
- run: npm run test:coverage
- name: Upload coverage
@@ -44,8 +46,8 @@ jobs:
if-no-files-found: ignore
retention-days: 14
- local-bins-e2e:
- name: E2E tests · ubuntu · node 22
+ recall-quality:
+ name: Recall quality and non-inferiority
runs-on: ubuntu-latest
steps:
@@ -56,16 +58,20 @@ jobs:
node-version-file: .nvmrc
cache: npm
- - uses: astral-sh/setup-uv@v7
-
- run: npm ci
- - run: npm run test:e2e:install-openviking
+ - name: Frozen v1 quality and indexed-recall smoke
+ run: npm run eval:recall
- - run: npm run test:e2e:local-bins
+ - name: Recall-v2 category and safety gate
+ run: >-
+ npm run eval:recall:v2 --
+ --baseline test/evaluation/baselines/threadnote-3.0.3/recall-v2-lexical.json
+ --fail-on-regression
+ --max-failures 10
- windows-e2e:
- name: E2E tests · windows · node 22
+ windows-smoke:
+ name: No-Python unit smoke · windows · node 22
runs-on: windows-latest
timeout-minutes: 30
@@ -77,10 +83,6 @@ jobs:
node-version-file: .nvmrc
cache: npm
- - uses: actions/setup-python@v6
- with:
- python-version: '3.12'
-
- run: npm ci
- run: npm run typecheck
@@ -90,9 +92,37 @@ jobs:
test/unit/windows-support.test.ts
test/unit/effect-command.test.ts
test/unit/effect-system.test.ts
- test/unit/lifecycle.install.test.ts
test/unit/local-ai.test.ts
test/unit/update.test.ts
test/unit/mcp.test.ts
- - run: npm run test:e2e:windows
+ self-contained-distribution:
+ name: Self-contained package · ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version-file: .nvmrc
+ cache: npm
+
+ - run: npm ci
+
+ - run: npm run check:self-contained
+
+ - run: npm run test:smoke:self-contained
+
+ - name: Installed package E2E
+ if: runner.os != 'Windows'
+ run: npm run test:e2e:local-bins
+
+ - name: PowerShell installer E2E
+ if: runner.os == 'Windows'
+ run: npm run test:e2e:windows
diff --git a/.gitignore b/.gitignore
index 2de90f3..456cb7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@ dist/
build/
out/
coverage/
+artifacts/
+.artifacts/
graphify-out/
*.log
manager/app.js
diff --git a/README.md b/README.md
index 99ac1de..0caee05 100644
--- a/README.md
+++ b/README.md
@@ -1,169 +1,94 @@
-
-
-
-
# Threadnote
-[](https://www.npmjs.com/package/threadnote) [](https://github.com/Kashkovsky/threadnote/actions/workflows/ci.yml) [](https://www.npmjs.com/package/threadnote) [](./LICENSE) 
-
-> One engineer teaches it once. Every teammate's coding agent can use it.
-
-Threadnote is a shared, Git-backed memory layer for the coding agents your team already uses. Alice's Codex can publish
-a hard-won architecture decision; Bob's Claude Code, Cursor, or Copilot can auto-sync and recall it during the next
-task. No copy-pasted handoff, vendor lock-in, or shared chat window required.
+Threadnote gives development agents durable local memory, branch handoffs, curated repo guidance, and opt-in team
+sharing. Version 4 is self-contained: canonical content, indexes, model files, locks, logs, migration receipts, and
+share metadata are owned under `~/.threadnote`.
-Personal working state stays local. Only curated durable knowledge or reusable artifacts that you explicitly publish
-enter the team's memory repo, with a preview, secret scrubber, and Git history. Persistence across sessions still
-matters, but it is the foundation: the differentiator is useful context moving safely between **different users and
-different agents**.
+The installed product requires Node.js 22.19 or newer. It does not require Python, a second memory platform, or a
+background daemon.
-**Walkthrough:** https://kashkovsky.github.io/threadnote/
-**Wiki:** https://github.com/Kashkovsky/threadnote/wiki
+## Install
-## The Value
-
-```text
-Alice + Codex ──publish curated memory──▶ team Git repo
- │
- auto-sync on recall
- ▼
- Bob + Claude Code / Cursor / Copilot
+```sh
+npm install --global threadnote
+threadnote install
+threadnote doctor
+threadnote mcp-install codex --apply
```
-- **Cross-user and cross-agent.** Teammates can use one shared knowledge layer without standardizing on one AI vendor.
-- **Explicit, reviewable sharing.** Publish one durable memory or reusable skill; preview and scrub it before it lands
- in Git.
-- **Private by default.** Personal handoffs, preferences, incidents, and unpublished memories stay on the local
- machine.
-- **Targeted local recall.** OpenViking runs a local GGUF embedding model through `llama.cpp` to rank semantic matches;
- agents load selected `viking://` records instead of replaying the entire memory history.
-- **Optional local recall refinement.** `threadnote local-ai install` adds the recommended pinned, verified Gemma
- model. It helps expand weak and medium-confidence paraphrases and filters their bounded result set while deterministic
- ordering remains in control. When available, it also adds compact retrieval aliases to new personal memories;
- `threadnote enrich-memories --apply` can backfill personal and configured shared durable memories with streamed
- progress. Shared changes remain local until `threadnote share sync`. Toggle the installed model without removing it
- with `threadnote local-ai disable` and `threadnote local-ai enable`; the generic
- `threadnote local-ai model switch` command is retained for future verified models.
-- **Recall explains itself.** Semantic and BM25 relevance, fields, graph links, currentness, authority, and bounded
- feedback produce a confidence level and inspectable ranking reasons.
-- **Routine continuity is automatic.** At meaningful task closeout, the agent writes normal durable feature knowledge
- and handoffs directly. It proposes only additional extracted candidates and writes those after user approval.
-- **Durable and addressable.** Stable pointers let agents update one current `project/topic` instead of accumulating
- stale notes.
-- **Built for engineering work.** Decisions, contracts, gotchas, release workflows, and current branch state have
- distinct lifecycles instead of becoming an undifferentiated chat summary.
-
-## Quickstart
-
-macOS and Linux:
-
-```bash
-curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
-threadnote mcp-install claude --apply # or codex / cursor / copilot
-threadnote doctor --dry-run
-```
+Use `claude`, `cursor`, or `copilot` instead of `codex` for another supported client. MCP is a local stdio process.
+The default `core` toolset stays compact; install `--toolset full` for maintenance and artifact-sharing tools.
-Native Windows PowerShell (experimental):
+Windows PowerShell:
```powershell
irm https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.ps1 | iex
-threadnote mcp-install codex --apply # or claude / cursor / copilot
-threadnote doctor --dry-run
```
-Native Windows support is experimental. The PowerShell installer uses npm's native `threadnote.cmd` launcher and
-installs OpenViking through `uv`; it does not require WSL, Git Bash, or a POSIX shim. Node.js 22.19 or newer is required
-on every platform. The manual native flow is `npm install --global threadnote` followed by `threadnote install`.
+## Daily workflow
-The CLI remains Threadnote's complete execution surface. The default stdio adapter is a compact interoperability layer
-with eight core tools: `recall_context`, `read_context`, `list_context`, `remember_context`,
-`review_session_context`, `apply_memory_candidates`, `share_publish`, and `threadnote_guide`. Advanced workflows can
-run through the CLI without reconfiguring MCP; install with `--toolset full` only when the agent needs those workflows
-as MCP tools.
-
-New to Threadnote? Ask your agent **"what can I do with Threadnote?"** — it calls the
-`threadnote_guide` MCP tool, which returns a short walkthrough tailored to your setup
-(server health, configured share teams, seeded projects) and offers to run each step
-with you. The walkthrough only loads when you ask, so it never sits in context otherwise.
-
-## Updates
-
-```bash
-threadnote update # latest stable release
-threadnote update --beta # latest beta release
-threadnote update --stable # return from beta to the stable release
+```sh
+threadnote recall "threadnote latest handoff" --caller-cwd "$PWD"
+threadnote read viking://user/me/memories/handoffs/active/threadnote/release.md
+threadnote remember --kind durable --project threadnote --topic storage-contract --text "..."
+threadnote handoff --project threadnote --topic release --text "..."
```
-Stable installs only report and install stable releases. After opting into a beta, ordinary `threadnote version` and
-`threadnote update` calls stay on the beta channel. Run `threadnote update --stable` to switch back, even when the
-stable release has a lower version than the installed beta.
-
-## Why Not Just Markdown Files?
+Repo files remain authoritative. `threadnote seed` imports only the files selected by the seed manifest. Canonical
+resources and memories keep stable `viking://` identifiers while their bytes live in the Threadnote-owned store.
-Use Markdown files. Threadnote makes them operational.
+## Recall
-- **`AGENTS.md` / `CLAUDE.md` / repo docs:** stable, reviewed, version-controlled rules.
-- **Random notes:** easy to write, hard for agents to rank, scope, update, or know when stale.
-- **Threadnote memories:** Markdown on disk plus semantic recall, stable URIs, lifecycle (`durable`, `handoff`,
- `archived`), scoped compaction, MCP tools, and safe team sharing.
+Recall works without a model using deterministic lexical, field, scope, lifecycle, authority, time, graph, and
+feedback signals. Optional semantic retrieval and reranking run in process through `node-llama-cpp`. Model selection
+is explicit and experimental until a candidate passes the checked quality gate:
-The source of truth is still local files. The benefit is that agents know how to find the right file, decide whether it
-is current, update it without creating duplicates, and safely move the useful part into a teammate's agent.
-The default semantic index is built locally with a GGUF embedding model through `llama.cpp`, so recall can rank
-relevant records without sending the memory corpus to a hosted embedding service.
-
-## Agent Perspective
-
-These are workflow examples from an agent's point of view:
-
-**Codex before Threadnote:** "I inspect the repo, ask what changed, rediscover the test command, and hope the compacted
-chat summary did not drop the important caveat."
-
-**Codex with Threadnote:** "I recall the branch handoff and durable feature memory first. I can name the files touched,
-the last failing check, the design decision behind the code, and the next step before editing."
-
-**Claude Code before Threadnote:** "A long debugging thread compacts into a vague narrative. The next turn knows the arc,
-but not the exact command, blocker, or decision."
-
-**Claude Code with Threadnote:** "The pre-compact handoff captures the concrete state. The next session reads the same
-memory and continues without asking the user to reconstruct it."
+```sh
+threadnote models list
+threadnote models install bge-small-en-v1.5-q8
+threadnote models select embedding bge-small-en-v1.5-q8
+threadnote index rebuild
+threadnote index verify
+```
-## Real-World Uses
+Models are downloaded only after an explicit install. Every built-in model manifest pins its immutable revision,
+filename, size, SHA-256, license, runtime version, and memory class. Downloads resume, checksums are verified before
+atomic promotion, and native compilation is disabled. The first measured BGE Small and BGE Small + Jina candidates
+improved semantic-query ranking but failed the no-answer gate, so 4.0 keeps lexical recall as the safe default. The
+reviewed candidate summaries are checked in under `test/evaluation/candidates/threadnote-4.0.0/`.
-- **Share a team decision:** Alice publishes an API contract; Bob's different agent auto-syncs it on its next recall.
-- **Continue a branch:** "Continue where we left off" -> agent recalls the active handoff and durable feature memory.
-- **Switch agents:** "Save where we are" -> agent stores a handoff the next MCP-enabled agent can read.
-- **Survive compaction:** Claude Code's hook can snapshot a handoff before compaction; other agents can recall it later.
-- **Remember a repo fact:** "This repo cuts release notes from CI" -> agent stores a durable workflow memory.
-- **Review additional context from a task:** after storing the normal durable memory and handoff, the agent proposes up
- to three extra candidates; approve, edit, defer, or reject them in the same conversation.
-- **Share with teammates:** publish a curated durable memory or reusable skill to a team git repo.
-- **Clean up overlap:** run `threadnote compact --project --topic --dry-run` before archiving stale
- handoffs or forgetting exact duplicates.
+## Upgrade from 3.x
-The adapter keeps the eight core tools above as its default surface. `threadnote_guide` catalogs advanced categories and
-their CLI equivalents without loading their schemas into every agent session. Pass `--toolset full` to `mcp-install`
-to expose compatibility aliases, memory maintenance, advanced sharing/artifact tools, and raw OpenViking parity tools
-with `ov_*` names.
+```sh
+threadnote migrate
+threadnote migrate --apply
+threadnote doctor
+threadnote index status
+```
-## Development
+Migration inventories the legacy home, rejects unsafe links, checks free space, copies into sibling staging, validates
+every copied hash, and atomically promotes `~/.threadnote`. The source home is never modified or deleted, so rollback
+is simply restoring the previous `THREADNOTE_HOME` while investigating.
-Threadnote's infrastructure and orchestration run on Effect 4 beta, including typed command/HTTP failures, scoped
-resources, deterministic polling and retries, Effect Schema MCP inputs, and optional structured Effect AI
-consolidation. See [`docs/effect.md`](./docs/effect.md) for boundaries, opt-in configuration, parity gates, and the beta
-upgrade procedure. See [`CONTRIBUTION.md`](./CONTRIBUTION.md) for development setup, validation requirements, and pull
-request guidance.
+## Quality contract
-## Acknowledgments
+The reviewed recall-v2 corpus contains 200 documents and 250 queries across lexical, semantic, code, scope, lifecycle,
+authority, time, graph, no-answer, adversarial, chunking, and multilingual categories. Frozen 3.0.3 quality and M1 Max
+performance baselines are checked in under `test/evaluation/baselines/threadnote-3.0.3/`.
-Threadnote is a workflow layer over [OpenViking](https://openviking.ai/) (AGPL-3.0).
-It installs OpenViking on your machine (via `uv tool install openviking[local-embed]`) and runs it as a **separate program** —
-shelling out to the `ov` CLI and talking to `openviking-server` over MCP. Threadnote does **not** bundle, modify, or
-redistribute OpenViking; its source and license reach you independently through PyPI. Threadnote's own license covers
-only Threadnote's code.
+```sh
+npm run eval:recall:v2 -- \
+ --baseline test/evaluation/baselines/threadnote-3.0.3/recall-v2-lexical.json \
+ --fail-on-regression
+npm run eval:recall:models -- --embedding bge-small-en-v1.5-q8 --install
+npm run bench:recall:micro -- --json
+```
-See [`THIRD_PARTY.md`](./THIRD_PARTY.md) for the full attribution.
+See the [evaluation contract](test/evaluation/README.md), [4.0 plan](docs/4.0-plan.md),
+[migration](docs/migration.md), [sharing](docs/share.md), and
+[troubleshooting](docs/troubleshooting.md).
## License
-Threadnote is licensed under [AGPL-3.0-or-later](./LICENSE).
+Threadnote is licensed under AGPL-3.0-or-later. Model licenses are recorded separately in their manifests and
+third-party notices.
diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md
index b585c1a..f1c9283 100644
--- a/THIRD_PARTY.md
+++ b/THIRD_PARTY.md
@@ -1,37 +1,24 @@
# Third-party software
-Threadnote orchestrates third-party software that it does not bundle or redistribute. This file records that software
-and its licensing for attribution and clarity.
+Threadnote 4 is a self-contained Node.js application. It does not install or invoke a Python runtime, OpenViking, or a
+separate memory server.
-## OpenViking
+## Runtime and bundled npm software
-- **Homepage:** https://openviking.ai/
-- **Distribution:** PyPI — `openviking` (installed via `uv tool install openviking[local-embed]`, with `pipx` / `pip --user` fallbacks)
-- **License:** GNU Affero General Public License v3.0 (AGPL-3.0)
+Direct runtime software and packages bundled into the published JavaScript retain their own licenses:
-Threadnote is a thin workflow layer over OpenViking. At runtime it:
-
-- installs OpenViking onto the user's machine from PyPI;
-- invokes the `ov` / `openviking` command-line program as a separate process; and
-- communicates with `openviking-server` over MCP / local HTTP.
-
-Threadnote does **not** incorporate OpenViking's source code, does **not** modify OpenViking, and does **not** ship
-OpenViking binaries or source inside its npm package. OpenViking is obtained directly by the user from PyPI under its own
-AGPL-3.0 license, and its source and license notices are distributed with that package independently of Threadnote.
-
-Because OpenViking is used as a separate program at arm's length (subprocess + inter-process communication) rather than
-linked or incorporated, Threadnote is not a derivative work of OpenViking. Threadnote's own license (AGPL-3.0-or-later,
-see [`LICENSE`](./LICENSE)) applies only to Threadnote's own code.
-
-This acknowledgment is provided as good-faith attribution to the OpenViking project; it is not legal advice.
-
-## npm dependencies
+- `node-llama-cpp` (MIT), used in-process with prebuilt `llama.cpp` binaries for optional local GGUF inference
+- `effect`, `@effect/platform-node`, `@effect/ai-openai-compat`, and `@effect/vitest` (MIT)
+- `@modelcontextprotocol/sdk` (MIT)
+- `react`, `react-dom`, and `react-markdown` (MIT)
+- `remark-gfm` (MIT)
+- `js-yaml` (MIT)
-Runtime npm dependencies and build-time packages bundled into the published JavaScript retain their own licenses. As of
-this writing the direct bundled dependencies are:
+Consult each installed package's metadata and license files for the authoritative terms. Model files are installed only
+after an explicit `threadnote models install` action; their catalog entries identify the model source and license.
-- `effect`, `@effect/platform-node`, and `@effect/ai-openai-compat` (MIT)
-- `react-markdown` (MIT)
-- `remark-gfm` (MIT)
+## Historical migration compatibility
-Each is installed from npm under its respective license; consult the package's own metadata for the authoritative terms.
+Threadnote 4 can read a legacy `~/.openviking` directory during the explicit, non-destructive home migration. That
+compatibility path copies user-owned data into `~/.threadnote`, excludes old runtime artifacts, and never executes or
+bundles OpenViking code. OpenViking is not a Threadnote 4 runtime dependency.
diff --git a/config/launchd/io.threadnote.openviking.plist.template b/config/launchd/io.threadnote.openviking.plist.template
deleted file mode 100644
index 6ce7459..0000000
--- a/config/launchd/io.threadnote.openviking.plist.template
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
- Label
- io.threadnote.openviking
- ProgramArguments
-
- {{OPENVIKING_SERVER_PATH}}
- --config
- {{THREADNOTE_HOME}}/ov.conf
- --host
- {{OPENVIKING_HOST}}
- --port
- {{OPENVIKING_PORT}}
-
- EnvironmentVariables
-
- OPENVIKING_CONFIG_FILE
- {{THREADNOTE_HOME}}/ov.conf
-
- RunAtLoad
-
- KeepAlive
-
- StandardOutPath
- {{THREADNOTE_HOME}}/logs/server.log
- StandardErrorPath
- {{THREADNOTE_HOME}}/logs/server.log
-
-
diff --git a/config/ov.conf.template.json b/config/ov.conf.template.json
deleted file mode 100644
index 561ceaf..0000000
--- a/config/ov.conf.template.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "default_account": "{{OPENVIKING_ACCOUNT}}",
- "default_user": "{{OPENVIKING_USER}}",
- "default_agent": "{{OPENVIKING_AGENT_ID}}",
- "storage": {
- "workspace": "{{THREADNOTE_HOME}}/data"
- },
- "auto_generate_l0": false,
- "auto_generate_l1": false,
- "server": {
- "host": "{{OPENVIKING_HOST}}",
- "port": "{{OPENVIKING_PORT}}"
- }
-}
diff --git a/config/ovcli.conf.template.json b/config/ovcli.conf.template.json
deleted file mode 100644
index 97408cb..0000000
--- a/config/ovcli.conf.template.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "url": "http://{{OPENVIKING_HOST}}:{{OPENVIKING_PORT}}",
- "account": "{{OPENVIKING_ACCOUNT}}",
- "user": "{{OPENVIKING_USER}}",
- "agent_id": "{{OPENVIKING_AGENT_ID}}",
- "timeout": 60
-}
diff --git a/config/post-update-migrations.json b/config/post-update-migrations.json
index c0e70c5..e332b25 100644
--- a/config/post-update-migrations.json
+++ b/config/post-update-migrations.json
@@ -41,42 +41,21 @@
"requiresProjectNameConsolidation": true
},
{
- "id": "local-ai-recall-gemma-4-e4b-v1",
- "introducedIn": "3.0.0",
+ "id": "self-contained-home-v1",
+ "introducedIn": "4.0.0",
"appliesToPrereleases": true,
- "title": "Optional local AI recall with Gemma 4 E4B",
+ "title": "Migrate to the self-contained Threadnote home",
"description": [
- "Threadnote can improve weak and medium-confidence recalls with a local instruction model while keeping deterministic retrieval and ranking in control.",
- "This optional action downloads the official Gemma 4 E4B Q4_0 GGUF model (4.59 GB), verifies its pinned SHA-256, and stores its configuration under THREADNOTE_HOME.",
- "The authenticated model service binds to 127.0.0.1 only and uses the llama.cpp runtime already installed with OpenViking.",
- "High-confidence recalls never invoke the model; model failures fall back to the original deterministic result.",
- "Gemma model use is subject to Google's Gemma Terms: https://ai.google.dev/gemma/terms"
+ "Threadnote 4 stores canonical resources, derived indexes, model files, and share metadata under ~/.threadnote.",
+ "The migration inventories ~/.openviking without starting it, excludes runtime-only Python and server artifacts, copies into a sibling staging directory, validates hashes, and atomically promotes the result.",
+ "The legacy ~/.openviking home is preserved unchanged as the rollback source and is never deleted automatically.",
+ "Threadnote 4 uses in-process node-llama-cpp for optional embeddings, reranking, and generation; no Python or background OpenViking service is required."
],
- "commandArgs": ["local-ai", "install"],
+ "commandArgs": ["migrate", "--apply"],
"instructions": [
- "Local AI recall is enabled. Run `threadnote local-ai status` to inspect it or `threadnote local-ai disable` to turn it off while preserving the installation."
- ],
- "markHandledWhenSkipped": true
- },
- {
- "id": "local-ai-memory-enrichment-v1",
- "introducedIn": "3.0.0",
- "appliesToPrereleases": true,
- "title": "Optional full local-AI memory enrichment",
- "description": [
- "Threadnote can add compact retrieval keywords to existing personal and configured shared durable memories so deterministic recall finds stronger paraphrase matches before AI post-processing.",
- "The migration updates eligible memory documents in place. Shared team changes remain local and reviewable until you run the printed threadnote share sync command.",
- "If local AI is not installed, accepting this action downloads and verifies the pinned Gemma 4 E4B model first (4.59 GB).",
- "A full corpus can take a long time because every memory is processed locally. Progress and generated keywords stream to the console.",
- "The added repeated keywords headers are ignored by older Threadnote parsers, so existing shared-memory readers remain compatible."
- ],
- "commandArgs": ["enrich-memories", "--apply", "--install-local-ai"],
- "instructions": [
- "Memory enrichment finished. Future active personal memories are enriched automatically when local AI is available.",
- "Run the printed `threadnote share sync --team ` command after reviewing enriched shared memories.",
- "Rerun `threadnote enrich-memories --apply` to resume remaining memories, or add `--force` to regenerate existing keywords."
- ],
- "markHandledWhenSkipped": true
+ "Home migration finished. Run `threadnote doctor`, then `threadnote index status`.",
+ "Optional semantic recall starts with `threadnote models list`; install and select a measured embedding model before rebuilding the vector index."
+ ]
}
]
}
diff --git a/config/seed-manifest.example.yaml b/config/seed-manifest.example.yaml
index 3e1ecd6..5bb419d 100644
--- a/config/seed-manifest.example.yaml
+++ b/config/seed-manifest.example.yaml
@@ -1,4 +1,4 @@
-# Example only. Developers should generate ~/.openviking/seed-manifest.yaml from their own checkouts with:
+# Example only. Developers should generate ~/.threadnote/seed-manifest.yaml from their own checkouts with:
# threadnote init-manifest --repo /path/to/repo
version: 1
projects:
diff --git a/docs/4.0-plan.md b/docs/4.0-plan.md
new file mode 100644
index 0000000..0fcd02e
--- /dev/null
+++ b/docs/4.0-plan.md
@@ -0,0 +1,690 @@
+# Threadnote 4.0: self-contained storage and local AI
+
+Status: implementation complete through Phase 7; release rollout pending
+Target: Threadnote 4.0.0
+Working branch: `codex/4.0.0`
+Last updated: 2026-07-27
+
+## Implementation snapshot
+
+Phases 0 through 7 are implemented on the release branch. The frozen 3.0.3 recall and performance baselines, recall-v2
+corpus, quality gates, and first real-model candidate results are checked in. Canonical storage, derived search,
+migration, sharing, MCP, manager, model management, and local inference now run inside the Node.js process and use
+`~/.threadnote`.
+
+The measured BGE Small embedding candidate improved semantic-query MRR but regressed no-answer recall to zero. Adding
+the measured Jina reranker did not recover the regression or provide aggregate uplift. Both failed summaries are
+retained under `test/evaluation/candidates/threadnote-4.0.0/`; neither model is selected by default. Phase 8 remains
+the beta, release-candidate, and stable rollout process and must not waive the checked gates.
+
+## Outcome
+
+Threadnote 4.0 will remove OpenViking and Python from the installed product. A normal install, upgrade, recall, memory
+write, share operation, manager session, and local-AI operation must not require `python`, `uv`, `pip`, a Python
+virtual environment, or an OpenViking server.
+
+The replacement is intentionally smaller than OpenViking:
+
+- Markdown files and Git remain the canonical, inspectable data store.
+- Threadnote owns URI resolution, atomic storage, seeding, migration, and derived indexes.
+- Threadnote's existing lexical and hybrid ranker remain the retrieval authority.
+- `node-llama-cpp` provides in-process GGUF inference for embeddings, reranking, and optional structured generation.
+- Effect owns configuration, resource lifetime, concurrency, cancellation, typed errors, observability, and test
+ substitution.
+- Search indexes and embeddings remain derived, versioned, disposable data.
+
+The project will not replace OpenViking with another memory platform, vector server, Python service, or canonical
+database.
+
+## Release success criteria
+
+Threadnote 4.0 is ready only when all of these are true:
+
+1. A clean npm installation succeeds on supported platforms without invoking Python or compiling native code.
+2. Core memory and lexical recall work with no AI model installed and with the network unavailable.
+3. After the selected GGUF models are installed, semantic recall and local generation work with the network disabled.
+4. No production path starts or contacts OpenViking or the Python local-AI server.
+5. Upgrading an existing 3.x `~/.openviking` home into `~/.threadnote` preserves every canonical file, memory URI,
+ relationship, share mapping, and lifecycle state, with a tested rollback path.
+6. Retrieval quality is non-inferior to the frozen 3.x/OpenViking baseline by category, not merely by a blended score.
+7. Security invariants have zero tolerated regressions: no path traversal, scope leakage, stale/superseded leakage,
+ secret transmission, silent data loss, or unsafe PID/process handling.
+8. Recall and index performance stay inside budgets measured on supported platform classes at 10,000 documents.
+ A 100,000-document suite establishes the next scaling boundary.
+9. The published tarball, npm dependency graph, third-party notices, model manifests, install/repair/uninstall flows,
+ documentation, and manager UI all describe the same runtime.
+10. The legacy OpenViking and Python code, tests, configuration, launch records, CI setup, and bundled Python script
+ are deleted rather than left as hidden fallback paths.
+
+## Decisions
+
+### Canonical storage
+
+The default Threadnote-owned home is `~/.threadnote`. An explicit `THREADNOTE_HOME` or `--home` override may relocate
+it for testing or managed environments, but no 4.0 production workflow may continue writing Threadnote-owned state
+under `~/.openviking` after migration.
+
+Markdown and other seeded text resources remain canonical files under `~/.threadnote`. The same root owns
+configuration, derived indexes, model files, locks, migration receipts, logs, and local operational state; the exact
+subdirectory layout is specified by an ADR and versioned independently. Git remains the shared memory transport. No
+SQLite or vector database becomes authoritative.
+
+The initial derived search store will use:
+
+- the existing Threadnote postings/BM25 index;
+- a versioned metadata manifest;
+- a packed vector sidecar;
+- exact cosine similarity over normalized vectors.
+
+At 384 dimensions, 10,000 float32 vectors require about 15.36 MB. An approximate nearest-neighbor index is therefore
+not a 4.0 prerequisite. SQLite FTS5 or a vector extension may be adopted later only if Phase 0 and production-shaped
+benchmarks show a concrete concurrency, cold-start, update, or scale problem.
+
+### External URI compatibility
+
+Storage internals will use a parsed `ResourceId`; they will not manipulate URI strings as paths.
+
+Threadnote 4.0 will continue to accept and emit existing `viking://` identifiers. A storage migration and a public URI
+rename are separate compatibility risks and must not be combined. The internal scheme dependency will be removed, and
+an alias resolver will make a later `threadnote://` transition possible without rewriting canonical content.
+
+### Local inference engine
+
+`node-llama-cpp` is the preferred local inference engine. It has prebuilt macOS, Linux, and Windows packages, supports
+embeddings and reranking, exposes abort signals, and does not require `node-gyp` or Python. It can still fall back to
+CMake when no compatible binary exists, so Threadnote will explicitly call `getLlama` with:
+
+```ts
+{
+ build: "never",
+ skipDownload: true
+}
+```
+
+The default product must fail with a clear `UnsupportedNativeRuntime` diagnostic rather than compiling native code on
+the user's machine. A source build may be offered later as an explicit developer-only command; it must never be an
+automatic install, startup, repair, or recall action.
+
+Relevant upstream contracts:
+
+- [`node-llama-cpp` embeddings and reranking](https://node-llama-cpp.withcat.ai/guide/embedding)
+- [`node-llama-cpp` object lifecycle](https://node-llama-cpp.withcat.ai/guide/objects-lifecycle)
+- [`getLlama` build policy](https://node-llama-cpp.withcat.ai/api/type-aliases/LlamaOptions)
+- [interruptible model downloads](https://node-llama-cpp.withcat.ai/api/classes/ModelDownloader)
+
+### What Effect replaces
+
+Effect does not replace `llama.cpp`; it replaces the handwritten lifecycle and orchestration harness around it.
+
+The 4.0 implementation will use:
+
+- `effect` and `@effect/platform-node` for typed effects, platform services, scopes, concurrency, retries, logging,
+ tracing, metrics, configuration, clocks, and entrypoint lifetime;
+- `effect/unstable/ai/EmbeddingModel` as the provider-neutral embedding contract;
+- `effect/unstable/ai/LanguageModel` where its structured-generation contract fits;
+- `@effect/vitest` for scoped tests, deterministic clocks, service substitution, and typed failure assertions;
+- a Threadnote-owned adapter around `node-llama-cpp`.
+
+Effect's embedding service already supports single and ordered batch embedding, typed `AiError` failures, and request
+batching. The adapter should reuse that contract instead of inventing another batching framework. See the upstream
+[`EmbeddingModel` implementation](https://github.com/Effect-TS/effect/blob/main/packages/effect/src/unstable/ai/EmbeddingModel.ts).
+
+Effect AI is still an unstable API, and using it is an accepted 4.0 tradeoff. Threadnote 4.0 does not need to wait for
+those services to become stable. All Effect packages will be pinned to the same exact version, and imports from
+`effect/unstable/ai` and `node-llama-cpp` must be restricted to `src/effect/ai/` adapters. Recall, memory, sharing,
+manager, and CLI domain code will depend on Threadnote-owned services so a later stable-API migration, Effect beta
+upgrade, or native-engine upgrade cannot spread through the codebase.
+
+No production service may create a hidden runtime. Each executable gets one root Effect runtime and one root scope:
+
+- short-lived CLI commands acquire and release resources in their command scope;
+- the MCP server and manager retain warm scoped resources for their process lifetime;
+- tests replace Layers rather than monkey-patching modules or starting localhost servers.
+
+`ManagedRuntime` is not needed inside domain services. If a non-Effect external callback absolutely requires a Promise
+bridge, a single entrypoint-owned bridge may be used and guarded by the existing architecture tests.
+
+### Model roles
+
+The model catalog will distinguish roles rather than treating every GGUF file as a chat model:
+
+- `embedding`: required only for semantic retrieval;
+- `reranker`: optional cross-encoder refinement of a bounded shortlist;
+- `generation`: optional query expansion, enrichment, consolidation, and other structured generation.
+
+Each model manifest records:
+
+- stable Threadnote model ID and role;
+- repository, file, immutable revision, SHA-256, byte size, and license;
+- architecture, quantization, context limit, dimensions, normalization, and prompt prefixes;
+- minimum RAM/VRAM class and supported runtime version;
+- chunker and task compatibility;
+- replacement/migration rules.
+
+Phase 0 will choose the default embedding and reranking models from measured candidates. The current Gemma generation
+model remains a compatibility candidate, not an automatic 4.6 GB dependency of semantic recall.
+
+## Target architecture
+
+```mermaid
+flowchart TD
+ Entry["CLI / MCP / manager entrypoints"] --> Runtime["One root Effect runtime and scope"]
+ Runtime --> Store["ResourceStore"]
+ Runtime --> Recall["RecallEngine"]
+ Runtime --> Models["LocalModelRuntime"]
+ Runtime --> Share["Share and seed services"]
+
+ Store --> Canonical["Markdown resources and Git\ncanonical"]
+ Recall --> Lexical["BM25/postings index\nderived"]
+ Recall --> Vectors["Packed vector index\nderived"]
+ Recall --> Ranker["Threadnote hybrid ranker"]
+
+ Models --> EffectAI["Effect AI provider contracts"]
+ EffectAI --> Adapter["Threadnote node-llama-cpp adapter"]
+ Adapter --> Native["Prebuilt llama.cpp binding"]
+ Native --> GGUF["Pinned GGUF models"]
+
+ Share --> Canonical
+ Store --> Lexical
+ Store --> Vectors
+ Lexical --> Ranker
+ Vectors --> Ranker
+```
+
+## Effect service design
+
+The exact names may change during implementation, but the boundaries may not collapse.
+
+### Storage and indexing
+
+- `ResourceStore`
+ - parse/resolve identifiers;
+ - read, stat, list, glob, grep, create, compare-and-replace, remove, and make directories;
+ - atomic writes and safe traversal rules;
+ - no search-index behavior.
+- `SeedStore`
+ - manifest discovery, ignore rules, secret scan, fingerprints, curated copies, and source validation.
+- `RecallIndex`
+ - incremental lexical and vector index lifecycle;
+ - rebuild, validation, corruption recovery, version switching, and index statistics.
+- `VectorSearch`
+ - normalized exact scan and deterministic top-k;
+ - no model loading.
+
+### Local AI
+
+- `LlamaCppEngine`
+ - the only service that imports `node-llama-cpp`;
+ - one scoped `Llama` instance per process;
+ - typed wrappers for model, embedding context, ranking context, generation context, and download operations;
+ - interruption forwarding and native error translation.
+- `LocalModelCatalog`
+ - role-aware manifests, compatibility checks, licenses, paths, and selected defaults.
+- `LocalModelStore`
+ - resumable download, temporary files, checksums, free-space checks, atomic promotion, and safe deletion.
+- `LocalModelRegistry`
+ - scoped model-handle sharing;
+ - bounded context acquisition;
+ - idle unloading where it measurably helps;
+ - no global mutable singleton.
+- `EmbeddingModel`
+ - implemented with Effect AI's provider-neutral constructor over `LlamaCppEngine`;
+ - batched indexing and single-query embedding.
+- `Reranker`
+ - Threadnote-owned service because Effect AI has no stable provider-neutral reranking contract.
+- `StructuredGenerator`
+ - Threadnote-owned domain port used by enrichment, expansion, selection, and consolidation;
+ - may be implemented through Effect `LanguageModel` where the adapter is complete;
+ - JSON-schema grammar plus post-generation Effect Schema validation.
+
+### Native resource lifetime
+
+Every `Llama`, model, and context allocation must be acquired through `Layer.scoped` or `Effect.acquireRelease`.
+Finalizers explicitly call `.dispose()`; garbage collection is not a resource-management strategy.
+
+The layer must:
+
+- share one `Llama` instance for the process;
+- dispose contexts before models and models before the `Llama` instance;
+- forward Effect interruption to `AbortSignal`;
+- cap concurrent evaluations and model loads;
+- prevent use-after-dispose;
+- reject unsafe memory configurations rather than disabling upstream safety checks;
+- surface load time, inference time, token counts, model identity, backend, RAM/VRAM estimates, and disposal failures as
+ structured telemetry.
+
+### Error model
+
+Native or model failures must not become untyped `Error` values at domain boundaries. At minimum:
+
+- `NativeRuntimeUnavailable`
+- `UnsupportedNativeRuntime`
+- `ModelNotInstalled`
+- `ModelManifestInvalid`
+- `ModelChecksumMismatch`
+- `ModelDownloadFailed`
+- `ModelLoadFailed`
+- `InsufficientMemory`
+- `EmbeddingFailed`
+- `RerankingFailed`
+- `GenerationFailed`
+- `InvalidModelOutput`
+- `InferenceInterrupted`
+
+Recall continues to fail open to lexical results for model absence, timeout, interruption, or inference failure.
+Canonical writes must never depend on optional enrichment succeeding.
+
+## Phase 0: measurement before behavior changes
+
+Phase 0 is a release gate, not a disposable spike. It must land before native storage or inference changes so the
+existing 3.x/OpenViking system can produce a frozen comparison baseline.
+
+### 0.1 Evaluation contract v2
+
+Replace the small recall-v1 fixture as the primary quality gate. It currently contains 10 documents, 8 queries, and
+precomputed semantic scores; it tests ranker behavior but does not execute a real embedding pipeline.
+
+Create a versioned evaluation package with:
+
+- at least 200 reviewed canonical documents and 250 reviewed queries;
+- relevance grades from 0 through 3 rather than one expected URI;
+- explicit forbidden results and expected no-answer cases;
+- corpus/query provenance, schema version, category, language, project, timestamp, and expected retrieval stage;
+- deterministic distractor generation to 1,000, 10,000, and 100,000 documents;
+- no copied user memories, secrets, customer data, or dependency on a developer home directory;
+- stable fixture hashes and a schema migration utility.
+
+Required query categories:
+
+| Category | Required coverage |
+| ----------------- | --------------------------------------------------------------------------------------------- |
+| Exact and lexical | rare identifiers, file names, symbols, error codes, phrases, prefixes, typos |
+| Semantic | paraphrases, synonyms, indirect descriptions, vocabulary mismatch |
+| Code and docs | API contracts, commands, code tokens, prose-to-code and code-to-prose |
+| Scope | current project, workset, another project, global guidance, ambiguous project names |
+| Lifecycle | active, archived, superseded, historical, replaced, expired |
+| Authority | checked-in guidance, shared durable memory, personal memory, handoff, generated distractor |
+| Time | current status, prior state, future validity, stale handoff |
+| Graph | related, blocked-by, supersedes, depends-on, unrelated high-semantic distractor |
+| No answer | absent concepts, weak near-matches, misleading vocabulary, contradictory evidence |
+| Adversarial | prompt injection text, secret-like text, malformed metadata, huge fields, Unicode confusables |
+| Chunking | headings, long documents, repeated sections, exact boundary hits, duplicate chunks |
+| Multilingual | a separately scored set for each language Threadnote claims to support |
+
+### 0.2 Recall quality metrics
+
+Record aggregate and per-category metrics:
+
+- Recall@1, Recall@5, and Recall@10;
+- mean reciprocal rank;
+- nDCG@5 and nDCG@10;
+- no-answer precision, recall, and F1;
+- stale-hit rate;
+- forbidden/scope-leak rate;
+- authority-inversion rate;
+- explanation-reason contract coverage;
+- semantic uplift over lexical-only retrieval;
+- reranker uplift over hybrid retrieval;
+- average candidates read and context bytes/tokens returned;
+- model expansion invocation and fallback rates.
+
+Security and compatibility metrics use a zero-regression threshold. Quality gates use a frozen 3.x baseline and
+explicit non-inferiority margins per category. A strong overall score cannot hide a regression in no-answer, lifecycle,
+scope, code, or multilingual behavior.
+
+### 0.3 Performance suites
+
+Separate correctness tests from performance measurements.
+
+Microbenchmarks:
+
+- tokenization and BM25 lookup;
+- vector normalization, serialization, decode, exact scan, and top-k;
+- chunk fingerprinting and incremental index updates;
+- URI parsing and path resolution;
+- metadata parsing and atomic record encoding.
+
+End-to-end benchmarks:
+
+- empty, 1,000, 10,000, and 100,000-document index builds;
+- one-document and 1%, 10%, and full index updates;
+- hot lexical, hot vector, hybrid, and complete recall;
+- cold index decode and corruption rebuild;
+- first embedding model load, warm query embedding, and batch document embedding;
+- reranker and structured-generation cold/warm paths;
+- concurrent readers, one writer plus readers, and competing process behavior;
+- migration of representative 3.x homes and shares;
+- CLI startup, MCP startup, manager startup, shutdown, and resource disposal.
+
+Record p50, p95, p99, throughput, peak RSS, native/external memory, estimated VRAM, CPU time, event-loop delay, disk
+bytes, model/index load time, package install time, and tarball/dependency size. Store raw JSON with:
+
+- commit and dirty state;
+- Node, npm, OS, architecture, CPU, RAM, backend, and model manifest;
+- warmup/sample counts and fixture hashes;
+- benchmark schema and runner versions.
+
+Use `@effect/vitest` and Vitest for contracts, fault injection, deterministic time, and service-layer tests. Use a
+dedicated benchmark runner for performance. Vitest's benchmark feature is still experimental and its module runner can
+distort imported microbenchmarks, so Phase 0 should use `mitata` or an equivalent stable runner against built artifacts
+and retain a small explicit end-to-end orchestrator. See [Vitest's benchmark caveats](https://main.vitest.dev/guide/benchmarking).
+
+Shared-host CI is suitable for correctness and gross performance smoke gates, not tight latency regression
+thresholds. Nightly or release-candidate runs use pinned runner classes:
+
+- Linux x64 CPU;
+- macOS arm64 Metal;
+- Windows x64 CPU;
+- an additional Linux arm64 or macOS x64 install/runtime smoke path when infrastructure permits.
+
+### 0.4 Reliability, recovery, and security suites
+
+Add deterministic fault tests for:
+
+- interruption during model download, model load, embedding, generation, indexing, migration, and atomic write;
+- truncated or corrupted canonical files, manifests, indexes, vectors, and model files;
+- disk full, read-only directory, permission denial, missing home, and invalid path encoding;
+- symlink escape, traversal, case-fold collisions, Unicode normalization, and Windows reserved paths;
+- stale locks, live lock owners, concurrent readers/writers, process crash, and orphan temporary files;
+- model revision/dimension changes, mixed embedding spaces, interrupted rebuild, and rollback;
+- missing or incompatible native prebuilt, CPU-only fallback, GPU initialization failure, and insufficient memory;
+- offline install/recall, proxy/certificate failures, rate-limited downloads, and resumed downloads;
+- malicious resource content attempting prompt injection or schema escape;
+- secret scanner blocking before any remote provider or local index receives disallowed text.
+
+Use property-based tests for `ResourceId`, path containment, atomic state transitions, index round trips, and migration
+idempotence.
+
+### 0.5 Frozen 3.x baseline
+
+Before implementation changes:
+
+1. Pin the current Threadnote, OpenViking, fixture, and optional local-generation model revisions.
+2. Run lexical, OpenViking semantic, hybrid, expansion, selection, enrichment, sharing, migration-shaped, and
+ distribution tests.
+3. Capture quality, latency, memory, disk, package, and failure-mode artifacts.
+4. Review unexpected results and label them as intended behavior or known defects.
+5. Check in only compact, non-machine-specific baseline summaries and fixture metadata; retain raw CI artifacts
+ separately.
+6. Keep the legacy comparison job isolated so ordinary 4.0 development no longer requires Python after the baseline is
+ frozen.
+
+### Phase 0 exit criteria
+
+- Evaluation v2 and generated-scale fixtures exist and are documented.
+- Every metric has a definition, category breakdown, threshold policy, and JSON schema.
+- Baseline artifacts can be reproduced from a clean checkout.
+- Current 3.x behavior and known defects are frozen before replacement code lands.
+- The embedding and reranking model bake-off produces an ADR selecting model(s), chunking, prefixes, normalization,
+ dimensions, quantization, and hardware budgets.
+- PR correctness gates and scheduled performance jobs are active.
+
+## Phase 1: architecture seams
+
+Introduce contracts while the current implementation still runs:
+
+1. Add `ResourceId`, `ResourceStore`, `SeedStore`, `RecallIndex`, `VectorSearch`, `LocalModelCatalog`,
+ `LocalModelStore`, `LlamaCppEngine`, `Reranker`, and `StructuredGenerator`.
+2. Move OpenViking and the Python HTTP provider behind legacy adapter Layers.
+3. Make CLI, MCP, manager, sharing, seeding, memory, and recall depend on the new domain services.
+4. Centralize home-directory and storage-layout configuration.
+5. Expand architecture tests to forbid direct OV CLI, HTTP endpoint, Python process, raw filesystem, and native-addon
+ access outside approved adapters.
+6. Add shadow-read and shadow-index comparison hooks that cannot mutate canonical data.
+
+Exit criteria:
+
+- No domain workflow imports or shells out to OV directly.
+- Existing behavior and Phase 0 metrics remain unchanged.
+- Test Layers can run every domain workflow without a real OV server or native model.
+
+## Phase 2: native canonical store
+
+Implement the filesystem store under `~/.threadnote`:
+
+1. Specify the `~/.threadnote` directory layout and format versions.
+2. Map parsed resource IDs to validated paths; reject traversal, absolute components, symlink escape, and unsafe
+ platform names.
+3. Implement atomic create, compare-and-replace, remove, mkdir, stat, read, list, glob, and grep.
+4. Use temporary files, file and directory synchronization where supported, atomic rename, per-resource locks, and
+ compare-and-swap fingerprints.
+5. Preserve frontmatter, unknown fields, line endings where required, and existing memory URI identity.
+6. Replace OV seed ingestion with curated copy, ignore rules, secret scanning, content fingerprints, source authority,
+ deletions, and rename detection.
+7. Replace OV-backed share reads/writes/reindex operations while retaining Git locking and conflict behavior.
+8. Add recovery tooling for interrupted writes and orphan temporary files.
+
+During this phase, native storage runs in shadow mode first, then supports an explicitly enabled lexical-only mode.
+
+Exit criteria:
+
+- Every storage, seed, share, and concurrency contract passes on Linux, macOS, and Windows.
+- Shadow comparison finds no unexplained canonical-data divergence.
+- A fresh home can run the complete non-AI product without OV.
+
+## Phase 3: derived search and embeddings
+
+1. Define document and chunk identity, heading context, maximum lengths, overlap, normalization, and versioning.
+2. Make current lexical indexing independent of the OV directory layout.
+3. Add the role-aware model catalog and safe, resumable model store.
+4. Add the scoped `node-llama-cpp` engine with prebuilt-only policy.
+5. Implement Effect AI's provider-neutral embedding service over the native engine.
+6. Batch and resume document indexing; make query-time embedding independent of indexing progress.
+7. Write vectors to a checksummed, versioned sidecar and swap complete generations atomically.
+8. Add exact normalized cosine scan and deterministic top-k ordering.
+9. Feed semantic scores into the existing explainable hybrid ranker.
+10. Serve lexical results while vectors are unavailable, stale, rebuilding, interrupted, or incompatible.
+11. Add status, progress, inspect, rebuild, verify, and purge-derived-data commands.
+
+Database decision gate:
+
+- keep the sidecar when measured 100,000-document behavior and concurrent updates meet budgets;
+- otherwise prototype SQLite as a disposable derived index and rerun the same contract;
+- do not introduce SQLite merely for architectural symmetry.
+
+Exit criteria:
+
+- Semantic quality meets Phase 0 non-inferiority gates.
+- No query mixes vectors from different models, dimensions, normalizations, or chunker versions.
+- Interrupted rebuilds leave the previous generation usable.
+- Model absence and native runtime failure preserve deterministic lexical recall.
+
+## Phase 4: reranking and structured generation
+
+1. Implement a bounded native reranker service and compare it against the existing LLM selector.
+2. Implement `StructuredGenerator` over node-llama-cpp with JSON-schema grammar and Effect Schema validation.
+3. Migrate query expansion, candidate selection, memory enrichment, and consolidation.
+4. Preserve current privacy rules: remote providers never receive local candidate excerpts unless explicitly allowed;
+ local inference remains local.
+5. Add deterministic seeds where supported, hard token/context budgets, bounded concurrency, timeouts, interruption,
+ and fail-open behavior.
+6. Decide from Phase 0 evidence whether recall needs generation at all when embeddings and reranking are available.
+7. Remove the localhost token, port, PID, health server, Python environment lookup, and Python adapter lifecycle.
+8. Replace `local-ai start/stop` semantics with model/runtime status, preload, unload, verify, install, switch, and
+ remove operations. Preserve deprecated commands temporarily with precise migration output when useful.
+
+Exit criteria:
+
+- No shipped feature imports or executes Python.
+- Native inference resources are released under success, failure, timeout, and process shutdown.
+- Quality and safety gates pass with generation enabled, disabled, unavailable, and interrupted.
+
+## Phase 5: product and compatibility migration
+
+Migrate all remaining OpenViking-shaped surfaces:
+
+- CLI commands and output;
+- default and full MCP toolsets;
+- manager APIs and UI;
+- seed manifests and worksets;
+- personal and shared memory CRUD;
+- relationships, feedback, review candidates, and consolidation;
+- share publish, clone, sync, mapping, conflict, and repair;
+- doctor, status, start, stop, update, repair, uninstall, and diagnostics;
+- package scripts, config templates, launch configuration, docs, examples, and agent instructions.
+
+Raw OV parity tools require an explicit decision:
+
+- reimplement only generic capabilities that belong in Threadnote, such as read/list/search/glob/grep;
+- rename them under Threadnote contracts when compatibility permits;
+- deprecate OV-specific watch, parser, code-outline, code-expand, session-store, or compression behavior rather than
+ recreating unused platform features.
+
+Exit criteria:
+
+- Every supported public command/tool has a 4.0 contract test and migration note.
+- The default toolset and manager contain no hidden legacy dependency.
+- Unsupported parity features fail with an actionable deprecation, never a missing OV binary error.
+
+## Phase 6: data migration and cutover
+
+Provide an idempotent `threadnote migrate` flow:
+
+1. Inventory the legacy `~/.openviking` home without starting OV.
+2. Preflight free space, permissions, format compatibility, model files, shares, locks, and conflicts.
+3. Copy canonical data into a staging sibling of `~/.threadnote`; never construct the new home inside
+ `~/.openviking`.
+4. Validate file counts, hashes, metadata, relationships, references, and share mappings.
+5. Build derived indexes from canonical files.
+6. Atomically promote the validated staging home to `~/.threadnote`.
+7. Record a versioned migration receipt and preserve the old home read-only.
+8. Support repeated dry-runs, interrupted resume, already-migrated detection, and verified rollback.
+9. Never delete `~/.openviking` automatically. Offer a separately confirmed cleanup only after a successful cutover and
+ backup window.
+
+Run shadow recall on representative migrated homes and compare ranked results with the frozen baseline. Migration logs
+must not copy memory bodies, secrets, or customer data into CI artifacts or diagnostics.
+
+Exit criteria:
+
+- Migration is idempotent and crash-safe at every injected failure point.
+- Rollback restores 3.x operation without reversing or corrupting canonical data.
+- Existing `viking://` references resolve identically after cutover.
+
+## Phase 7: platform, security, and distribution hardening
+
+1. Move runtime packages to the correct dependency class and externalize native modules from esbuild.
+2. Pin `node-llama-cpp` and Effect packages exactly; upgrade them only through compatibility PRs.
+3. Test npm global install, local install, `npx`, tarball install, update, repair, and uninstall.
+4. Assert install and runtime paths do not invoke Python and do not silently compile llama.cpp.
+5. Verify native prebuilt selection and CPU fallback on the supported platform matrix.
+6. Add package-size, binary-load, model-license, checksum, SBOM/provenance, and third-party-notice gates.
+7. Threat-model resource paths, model supply chain, prompt injection, native crashes, memory exhaustion, migration,
+ sharing, and remote-provider opt-in.
+8. Fuzz parsers and migration formats and run long-lived MCP/manager soak tests.
+9. Add explicit diagnostics for native backend, model identity, index generation, memory estimate, and degraded lexical
+ mode without leaking local paths unnecessarily.
+10. Delete OV/Python production and CI code and prove absence with repository/package scans.
+
+Exit criteria:
+
+- Clean-install E2E passes on Linux, macOS, and Windows with Python absent from `PATH`.
+- Published-tarball E2E passes, not only source-checkout tests.
+- Security review and license review are complete.
+- The legacy-dependency absence scans pass against source, built output, tarball, workflows, and docs.
+
+## Phase 8: beta, release candidate, and stable rollout
+
+1. Publish 4.0 betas with migration dry-run as the default recommendation.
+2. Collect opt-in diagnostics limited to versions, timings, counts, and typed failures; never collect memory content.
+3. Run full baseline comparisons for each release candidate.
+4. Maintain a known-hardware/model result matrix and document degraded modes.
+5. Freeze formats before RC1. After RC1, format changes require migration tests from every beta format.
+6. Publish stable only after at least one successful beta migration and sustained MCP/manager soak run on each primary
+ platform class.
+7. Keep 3.x security fixes available during the migration window.
+
+## Cross-cutting test strategy
+
+### Unit and property tests
+
+- Pure parsing, ranking, path, chunking, serialization, metric, and migration state-machine tests.
+- Effect service tests using deterministic Layers and `@effect/vitest`.
+- Property tests for identifiers, path containment, index round trips, normalization, and migration idempotence.
+
+### Contract tests
+
+- Run each domain contract against fake, legacy, and native adapters where applicable.
+- Native adapter contract tests use a fake `LlamaCppEngine`; they do not require a model.
+- A small real-GGUF smoke suite validates native integration separately.
+
+### Integration tests
+
+- Temporary homes and repositories only.
+- Real atomic filesystem behavior, locks, index generations, migrations, and Git shares.
+- Explicit interruption and process-boundary tests.
+
+### End-to-end tests
+
+- Built tarball and CommonJS launchers.
+- CLI, stdio MCP, manager, upgrade, repair, migration, uninstall, and offline operation.
+- Separate model-enabled jobs with immutable cached GGUF files and verified checksums.
+
+### Release gates
+
+Every pull request:
+
+- lint, formatting, typecheck, build, bundle size, unit/property/contract/integration tests;
+- compact recall-quality suite;
+- no-Python/no-OV architecture scans;
+- Linux native-runtime smoke when relevant.
+
+Scheduled and release candidate:
+
+- full recall-quality and model bake-off suites;
+- 10k/100k performance and soak suites;
+- Linux, macOS, and Windows tarball E2E;
+- migration corpus and rollback;
+- security fuzzing and dependency/license review.
+
+## Work that must not be hidden in cleanup
+
+The following are first-class deliverables, not post-release polish:
+
+- model license and checksum management;
+- offline and proxy behavior;
+- download resume and disk-space preflight;
+- native runtime compatibility diagnostics;
+- cancellation and resource disposal;
+- corrupt index/model recovery;
+- migration rollback;
+- URI compatibility;
+- concurrent process behavior;
+- manager and MCP parity;
+- package and installer validation;
+- deletion of legacy code and documentation;
+- measurable quality, performance, and security release gates.
+
+## Initial ADR queue
+
+Phase 0 and Phase 1 should produce reviewed ADRs for:
+
+1. canonical `~/.threadnote` home and storage layout;
+2. `ResourceId` grammar and `viking://` compatibility;
+3. atomic write, lock, and compare-and-swap semantics;
+4. chunk identity and index generation format;
+5. vector sidecar versus SQLite decision criteria;
+6. embedding model, dimensions, prefixes, quantization, and license;
+7. reranker use and shortlist size;
+8. generation model and structured-output adapter;
+9. Effect AI isolation and native resource Layer;
+10. prebuilt-only native runtime and supported platform matrix;
+11. migration, rollback, and legacy-home retention;
+12. public MCP/CLI parity and deprecations;
+13. benchmark schemas, platform classes, thresholds, and baseline governance.
+
+## Immediate next work
+
+The first implementation pull request on `codex/4.0.0` should contain only Phase 0 foundations:
+
+1. evaluation v2 schemas and fixture validation;
+2. benchmark result/environment schemas;
+3. deterministic corpus and distractor generators;
+4. the expanded reviewed recall fixture;
+5. quality and failure-category reporting;
+6. built-artifact microbenchmark runner;
+7. frozen 3.x baseline capture commands;
+8. CI separation between correctness, performance smoke, and scheduled platform benchmarks;
+9. documentation for adding a query, document, model candidate, metric, or benchmark scenario.
+
+No OpenViking replacement implementation should merge before those measurement contracts can expose its regressions.
diff --git a/docs/adr/013-evaluation-and-benchmark-governance.md b/docs/adr/013-evaluation-and-benchmark-governance.md
new file mode 100644
index 0000000..35f177d
--- /dev/null
+++ b/docs/adr/013-evaluation-and-benchmark-governance.md
@@ -0,0 +1,24 @@
+# ADR 013: Evaluation and benchmark governance
+
+Status: accepted for 4.0
+Date: 2026-07-27
+
+## Decision
+
+Threadnote uses a score-free, reviewed recall-v2 corpus as its quality contract. Quality is gated globally and by
+category against frozen, fixture-hashed baselines. Safety metrics cannot regress. Built-artifact microbenchmarks use
+Mitata; an explicit process runner captures end-to-end latency and resource measurements. Performance runs execute in
+a separate scheduled platform workflow and remain informational until dedicated runner classes are available.
+
+Checked-in artifacts are compact, deterministic baseline summaries. Full query runs, raw timing samples, model files,
+and machine-specific measurements are retained as CI artifacts.
+
+## Consequences
+
+- A blended quality improvement cannot hide regressions in scope, lifecycle, no-answer, code, or multilingual recall.
+- Fixture changes are deliberate baseline changes and require review.
+- Shared CI detects gross failures but does not create noisy latency gates.
+- Embedding and reranker candidates use the same artifact contract, making uplift directly comparable.
+- The current 3.0.3 lexical baseline preserves known defects; it is not the 4.0 release target.
+
+Operational instructions and metric definitions live in `test/evaluation/README.md`.
diff --git a/docs/adr/014-native-storage-and-indexes.md b/docs/adr/014-native-storage-and-indexes.md
new file mode 100644
index 0000000..7a1684f
--- /dev/null
+++ b/docs/adr/014-native-storage-and-indexes.md
@@ -0,0 +1,43 @@
+# ADR 014: Native canonical storage and derived indexes
+
+Status: accepted for 4.0
+Date: 2026-07-27
+
+## Decision
+
+Threadnote owns canonical resource bytes under `~/.threadnote/data/viking/` and preserves stable `viking://`
+identifiers. The implementation is an Effect `ResourceStore` service over ordinary files. It validates portable
+segments and containment, rejects escaping links, serializes writers with heartbeat locks, supports compare-and-swap,
+and commits same-directory temporary files with atomic rename.
+
+Lexical and vector indexes are derived, disposable generations under `~/.threadnote/indexes/`. Vector sidecars use
+packed normalized `Float32` data, immutable model/chunker metadata, per-entry fingerprints, and a checksummed active
+pointer. Rebuilds reuse unchanged chunks, checkpoint batches, resume compatible staging, and activate only a complete
+generation.
+
+SQLite and a vector extension are not canonical dependencies. They add native distribution and migration surfaces
+without improving the append/read/replace workload enough to justify them. The packed exact-scan index is simpler and
+measured for the current bounded corpus. A future approximate index may replace only the derived search service.
+
+## Options evaluated
+
+| Option | Strengths | Why it is not the 4.0 default |
+| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Threadnote files + packed `Float32` sidecar | No extra runtime, canonical Markdown stays inspectable, exact results, trivial rebuild and atomic generation swap | Chosen. At 384 dimensions, 10,000 vectors occupy about 15.4 MB before metadata, inside the measured exact-scan budget. |
+| SQLite + [`sqlite-vec`](https://alexgarcia.xyz/sqlite-vec/) | Familiar transactions and SQL metadata joins; Node and precompiled extension packages exist | `sqlite-vec` is pre-v1 and currently uses brute-force vector search, so it adds an extension/SQLite binary matrix without changing the 4.0 scaling class. |
+| [`libSQL`](https://docs.turso.tech/libsql) | Production SQLite fork with a native vector type and DiskANN available for larger sets | A second native database runtime and schema migration surface are unnecessary while exact scan meets the 10k budget. It remains the strongest embedded candidate if measured scale or concurrent updates outgrow the sidecar. |
+| [`LanceDB`](https://docs.lancedb.com/quickstart) | In-process TypeScript client, columnar storage, mature vector-oriented query path | Its database and native columnar runtime are substantially broader than Threadnote's disposable index need; adopting it would not simplify canonical storage. |
+| [`PGlite`](https://github.com/electric-sql/pglite) + pgvector | Dependency-only WASM Postgres, rich SQL, pgvector support | A WASM Postgres engine, Postgres data-directory upgrades, and single-connection execution are excessive for one local derived index. |
+| External vector service | Independent scaling and mature ANN implementations | Reintroduces a daemon, port, lifecycle, and availability dependency—the exact class of failure 4.0 removes. |
+
+The decision is benchmark-reversible. Re-evaluate the derived index implementation when the checked-in 10k budget
+fails on a supported platform, a real corpus crosses the 100k boundary, exact scan becomes a material part of recall
+latency, or multi-process index updates cannot meet their reliability budget. The replacement must still leave
+canonical Markdown and `viking://` identifiers untouched.
+
+## Consequences
+
+- Canonical content is inspectable, recoverable, and independent of an index implementation.
+- Clean install has no database server or extra native addon.
+- Multi-process safety and crash consistency are explicit Threadnote contracts.
+- Index format changes can rebuild without migrating canonical data.
diff --git a/docs/adr/015-effect-ai-and-node-llama-cpp.md b/docs/adr/015-effect-ai-and-node-llama-cpp.md
new file mode 100644
index 0000000..1e5907f
--- /dev/null
+++ b/docs/adr/015-effect-ai-and-node-llama-cpp.md
@@ -0,0 +1,26 @@
+# ADR 015: Effect AI harness and node-llama-cpp
+
+Status: accepted for 4.0
+Date: 2026-07-27
+
+## Decision
+
+Optional local inference runs in process through `node-llama-cpp` 3.19.1. The adapter requests prebuilt binaries with
+runtime builds and binary downloads disabled. No domain module imports the addon directly.
+
+Effect’s unstable `EmbeddingModel` is the embedding harness. Threadnote owns small typed services for reranking and
+JSON-schema generation, composed behind `LocalModelRuntime`. The application Layer lazily creates one llama engine,
+caches model contexts by role/model/hash/path, serializes inference, and disposes child contexts before models and the
+engine.
+
+Model files are explicit opt-in downloads. Immutable manifests pin revision, SHA-256, byte size, role, dimensions or
+context, prompt prefixes, normalization, quantization, runtime, license, and minimum memory. Partial or unverified
+files are never loaded.
+
+## Consequences
+
+- Tests replace inference with fake Layers and never need a model or native module.
+- Unstable Effect APIs are isolated and can be migrated behind the Threadnote port.
+- Lexical recall remains the zero-download, fail-open path.
+- Embedding is not implemented as bespoke inference math inside Threadnote; Threadnote implements orchestration,
+ lifecycle, storage, and evaluation around a maintained llama.cpp binding.
diff --git a/docs/adr/016-home-migration-and-rollback.md b/docs/adr/016-home-migration-and-rollback.md
new file mode 100644
index 0000000..fd3aaeb
--- /dev/null
+++ b/docs/adr/016-home-migration-and-rollback.md
@@ -0,0 +1,22 @@
+# ADR 016: Owned home migration and rollback
+
+Status: accepted for 4.0
+Date: 2026-07-27
+
+## Decision
+
+The only 4.0 owned home is `~/.threadnote`. Upgrade imports the legacy 3.x home through an inventory/copy/verify/promote
+state machine. It never starts an old runtime and never mutates or deletes the source.
+
+The migrator rejects absolute or escaping links, excludes runtime-only artifacts, preflights free space with a bounded margin,
+records source metadata and hashes, resumes a compatible sibling staging directory, detects source mutation, verifies
+all staged hashes, writes a checksummed receipt, and atomically promotes the target. Legacy share Git worktrees are
+copied into `~/.threadnote/share/worktrees/`, their `.git` and gitdir paths are rewritten for the final home, and
+the canonical shared Markdown copy has no Git metadata. An unrelated target is an error.
+
+## Consequences
+
+- The untouched source is the rollback source; cleanup is always a separate, explicitly confirmed operation.
+- Re-running is safe after interruption or success.
+- Derived vector indexes are rebuilt after cutover instead of treated as canonical migration input.
+- Migration logic is the only production area allowed to recognize the legacy home name or legacy runtime filenames.
diff --git a/docs/agent-instructions.md b/docs/agent-instructions.md
index 4b6b8ba..9932dbc 100644
--- a/docs/agent-instructions.md
+++ b/docs/agent-instructions.md
@@ -1,77 +1,21 @@
-# Agent Instructions
+# Agent instructions
-Use Threadnote/OpenViking as shared local context and memory. Repo files remain authoritative: follow the nearest
-`AGENTS.md`, `CLAUDE.md`, or other checked-in guidance first.
+Use Threadnote as shared local context and memory. Repo files remain authoritative; follow the nearest checked-in
+`AGENTS.md`, `CLAUDE.md`, or equivalent guidance first.
-## Tools
+At the start of a non-trivial task, call `recall_context` with the project and absolute `callerCwd`, then treat relevant
+`viking://` URIs as pointers and read them. Store reusable decisions and contracts with `kind: durable`; store status,
+checks, blockers, and next steps with `kind: handoff`. Use stable `project` and `topic` identities, and update an
+existing memory with `replaceUri` instead of creating timestamped duplicates.
-Prefer Threadnote MCP tools with JSON arguments. Core: `recall_context`,
-`read_context`, `list_context`, `remember_context`, `review_session_context`, `apply_memory_candidates`,
-`share_publish`, and `threadnote_guide`. Use the `threadnote` CLI if a tool is unavailable; inspect syntax with
-`threadnote --help`. Advanced tools require `threadnote mcp-install --toolset full --apply` and a new
-session; guide capabilities require their tools.
+At closeout, store normal durable feature knowledge and handoffs directly without asking. Use
+`review_session_context` only for additional session-extracted candidates, and apply those only after explicit user
+approval. Never store secrets, credentials, customer data, or raw production logs.
-If Threadnote MCP/CLI reports OpenViking unavailable or unreachable, run `threadnote start` without asking and retry
-the failed operation once. If startup fails, run `threadnote doctor --dry-run`, report it, and continue independent
-work if possible; never loop.
+Use MCP tools when available and the `threadnote` CLI as fallback. If native storage or indexing fails, run
+`threadnote doctor --dry-run`, report the diagnostic, and continue independent work when possible. The 4.0 runtime has
+no daemon to start.
-## Recall
-
-At the start of a non-trivial task, recall the repo/branch, active handoffs, durable feature knowledge, seeded guidance,
-relevant skills, and user/team preferences. Name the repo/project in the query to search
-`viking://resources/repos/`. For "current repo" or "this branch", pass the absolute workspace path as
-`callerCwd`.
-
-For an active handoff, recall its durable feature knowledge before coding. Seeded repo resources are canonical;
-personal memories are supplemental. Treat returned `viking://` URIs as pointers: read the relevant file or list its
-directory. Skip proactive recall for tiny one-shot questions.
-
-## Remember
-
-Store user-requested information and durable workflow or feature facts useful to future agents when they do not belong
-in checked-in docs. Never store secrets, credentials, customer data, or raw production logs.
-
-Use `kind: durable` for reusable decisions, contracts, invariants, edge cases, and gotchas. Use `kind: handoff` for
-status and next steps. Set stable `project` and `topic` so each issue has one current durable memory and handoff. Update
-the same identity or `replaceUri`; avoid timestamped routine notes. Shared durable replacements update in place.
-Archived handoffs are provenance, not default working context.
-
-When several memories cover the same project/topic, first run a scoped `compact_context` or `threadnote compact`
-dry-run. Preserve unique facts and source URIs in one replacement. Forget only clearly redundant memories; archive
-useful provenance; keep disagreements or uncertain cases. Never run global cleanup or compact checked-in canonical
-docs or sensitive data.
-
-## Task closeout
-
-After meaningful work, store normal durable feature knowledge and handoffs directly without asking for approval:
-decisions, contracts, invariants, and edge cases in durable memory; status, checks, blockers, and next step in the
-handoff. Skip tiny answers, inconclusive exploration, and abandoned work.
-
-Use `review_session_context` only for additional session-extracted candidates not already captured by those routine
-writes. Include the outcome and evidence or a source session ID/commit. The tool never creates active memory. Present up
-to three suggestions, then call `apply_memory_candidates` with the review ID and revision. For replace/manual-review
-candidates, pass `operation: create`, or `operation: replace` plus the exact `replaceUri`. Set `approved: true` only
-after explicit approval. Silence is not approval; never copy the whole transcript. If nothing is recommended, do not
-prompt.
-
-`THREADNOTE_CANDIDATE_POLICY=suggest` is the default. `handoff-only` limits closeout proposals to unresolved work, and
-`off` disables session suggestions while preserving explicit `remember_context`.
-
-## Sharing
-
-Publish only safe durable memories useful to teammates. Never publish handoffs or preferences. Exclude machine-local
-paths, branch state, secrets, customer data, or raw logs. Before publishing, confirm with the user unless authorized.
-`share_publish` writes and pushes the scrubbed shared copy before removing the personal copy.
-
-Before a PR/review request, check `threadnote share list`; if shares exist, ask whether to publish a durable feature
-memory and which team. Shared skills/artifacts remain opt-in. Never overwrite local modifications with `force` without
-explicit approval. Resolve shared-memory conflicts only after the user chooses the shared, local, or merged result. If
-automatic sync reports dirty state or a git conflict, resolve it before `threadnote share sync`.
-
-## Handoff
-
-Before pausing, switching agents, or ending meaningful work with local changes, store a concise handoff directly without
-presenting it as a candidate or asking for approval. Include repo/branch, durable topic/URI, files, status, checks,
-blockers, and next step. Lifecycle hooks may still create a bounded emergency handoff during compaction. If multiple
-memories for the same project/topic were used or written, run a scoped compaction dry-run first. Exclude long diffs and
-sensitive data.
+Before publishing safe durable memory, confirm with the user. Never publish handoffs or preferences. Resolve dirty git
+or share conflicts before syncing, and never overwrite local modifications with force without explicit approval.
+Before pausing, switching agents, or ending meaningful work with local changes, store a concise handoff.
diff --git a/docs/effect.md b/docs/effect.md
index f799ac1..50dce81 100644
--- a/docs/effect.md
+++ b/docs/effect.md
@@ -1,208 +1,22 @@
# Effect architecture
-Threadnote uses Effect 4 for infrastructure, orchestration, lifecycle, and protocol boundaries. Pure memory algorithms,
-formatting, and React state remain plain TypeScript where an Effect service or typed error channel would not add value.
+Threadnote 4 uses Effect services as capability boundaries:
-The package currently pins `effect`, `@effect/platform-node`, `@effect/vitest`, and
-`@effect/ai-openai-compat` to the same exact Effect 4 beta. Threadnote intentionally uses `effect/unstable/*`; beta
-upgrades are explicit and must pass the protocol and bundle gates below.
+- `ResourceStore` owns canonical URI operations, containment, locking, atomic replacement, and compare-and-swap.
+- `LocalModelStore` owns resumable downloads, free-space checks, verification, and atomic model promotion.
+- `LocalModelRuntime` owns optional embedding, reranking, and structured generation.
+- `SystemInfo`, `HttpService`, `CommandExecutor`, and digest services isolate platform effects.
-## Runtime boundaries
+Domain code does not import `node-llama-cpp`. The single native adapter requests a prebuilt binary with downloads and
+builds disabled, and its layer scopes disposal in dependency order. `LocalModelRuntime` lazily builds one engine per
+application process, caches model sessions by role/model/hash/path, and serializes native inference.
-- `src/effect/command.ts` owns interruptible child processes, typed command failures, timeouts, output limits, and git
- environment isolation.
-- `src/effect/http.ts` owns HTTP status/text/JSON requests and typed transport/status failures.
-- `src/effect/time.ts` owns polling and retry schedules. Tests use `TestClock`, so they do not wait in real time.
-- `src/effect/openviking.ts` applies typed transient failures and scheduled retries to OpenViking resource removal.
-- `src/effect/file_lock.ts` provides Effect-native, token-owned local critical sections for bounded candidate and
- feedback stores, including retry and stale-lock recovery.
-- `src/effect/runtime.ts` only assembles the application layer. It does not own a `ManagedRuntime` or expose execution
- helpers.
-- Each executable builds one Effect, provides the application layer once, and calls `NodeRuntime.runMain` once. Library
- code never calls `runPromise` or folds an Effect into a Promise; Effect-native workflows compose upward to that entry
- boundary.
-- Promise-based filesystem and compatibility helpers are lifted with `Effect.tryPromise` at composition points. The
- manager's Node HTTP callback submits request Effects to a scoped `FiberSet` runtime created by its entry Effect, so
- in-flight requests are interrupted when the manager scope closes.
-- Application Promise lifting is centralized in `src/effect/errors.ts`; production modules use its `fromPromise` and
- `fromSync` adapters with an operation label rather than declaring local copies. The CLI has no generic legacy-Promise
- command bridge: every command composes an Effect-returning workflow.
-- Application output uses Effect's `Console` service. `src/effect/console.ts` carries the current service across the
- remaining Promise compatibility boundary and captures scoped output without mutating process-global handlers.
-- Graph manifest reads, seeding, MCP installation, hook installation, lifecycle commands, recall, memory migration,
- pack I/O, and command execution compose through the application layer. Pure graph parsing, edge resolution, and
- Markdown rendering remain plain TypeScript.
-- Recall feedback, candidate-review persistence, audit writes, session-closeout orchestration, and MCP candidate
- application compose as Effects using the shared filesystem and clock services. Deterministic BM25/ranking,
- confidence, explanation, candidate comparison, and memory document transforms remain pure TypeScript.
-- `src/effect/share.ts` is the Effect-facing adapter for the transaction-oriented sharing implementation. This keeps
- the CLI error channel and runtime boundary uniform while the lower-level rollback callbacks remain Promise-based.
-- Long-lived servers and Effect-owned temporary directories use `Scope`/`acquireRelease`, so interruption closes
- servers and removes staged files.
+Effect’s unstable `EmbeddingModel` service is the embedding harness. Threadnote-owned service ports cover reranking and
+JSON-schema generation because those contracts are product-specific. Tests replace all three with pure Layers, so
+normal unit and recall-quality tests never load a native module or model.
-Threadnote is an ESM package. The checked-in `.cjs` bin files are deliberately tiny CommonJS launchers that dynamically
-import the ESM bundles. This avoids TypeScript's CommonJS-to-ESM `require()` mismatch while retaining npm bin
-compatibility.
+The canonical store and lexical recall work without inference services. Missing, corrupt, incompatible, or interrupted
+optional inference fails open to deterministic recall and produces diagnostics. A complete vector generation is never
+replaced by a partial one.
-The CLI entry normalizes dash-prefixed string values and strings containing multiple `=` characters before invoking the
-Effect CLI parser. This preserves Commander-era argument behavior while Effect 4 is in beta; the integration suite
-protects the compatibility shim so it can be reevaluated on an upgrade.
-
-## MCP schemas and parity
-
-MCP tool inputs use Effect Schema as the single source for handler types, runtime decoding, constraints, descriptions,
-and emitted JSON Schema. Empty input structs are normalized to an explicit JSON Schema object because Effect 4 beta
-currently represents an empty struct as an object-or-array union.
-
-The stdio integration tests connect with the official MCP client and protect:
-
-- tool names and core/full toolsets;
-- server instructions and context-byte budget;
-- JSON Schema fields and numeric constraints;
-- runtime rejection of invalid inputs;
-- OpenViking forwarding and share behavior.
-
-## Optional Effect AI
-
-Threadnote can use `@effect/ai-openai-compat` for structured consolidation drafts and bounded recall query expansion.
-It is intentionally opt-in. After an applicable package update, Threadnote offers to install the recommended local
-model; declining dismisses that offer without enabling or downloading anything. The same setup is available directly:
-
-```bash
-threadnote local-ai install
-threadnote local-ai model switch
-threadnote local-ai status
-threadnote enrich-memories # preview personal and shared-memory backfill
-threadnote enrich-memories --apply --install-local-ai
-```
-
-With no `--model`, `local-ai install` downloads the recommended pinned official Gemma 4 E4B Q4_0 GGUF file (4.59 GB).
-It is currently the only verified model. Each download is verified by exact size and SHA-256, stored under
-`THREADNOTE_HOME/threadnote/models`, and retained when another verified model is selected. Threadnote writes the
-active model to a versioned `THREADNOTE_HOME/threadnote/local-ai.json`. Pass `--model-path ` to verify and reuse
-an existing GGUF copy for the selected model instead.
-The service binds only to `127.0.0.1:1934`. Installation also creates a mode-0600 access token. Health checks use a
-challenge-response proof before Threadnote sends any prompt, and the OpenAI-compatible endpoints require that token.
-
-The full enrichment command processes each eligible personal memory and configured shared durable memory locally, then
-stores up to eight compact `keywords:` headers without changing its URI, body, timestamp, or lifecycle metadata. It
-prioritizes active memories before historical personal records. Shared skills, repository guidance, and other
-non-memory files are excluded. Already-enriched memories are skipped, so an interrupted run can be resumed. Use
-`--force` to regenerate keywords and `--limit ` for a bounded trial. A large corpus can take a long time; the
-command prints `[n/N]` progress, generated keywords, failures, and a final summary instead of working silently. Shared
-updates use the repository and memory locks but stay as local Git changes until the user runs the printed
-`threadnote share sync --team ` command. A model response that cannot produce usable structured keywords leaves
-that memory unchanged and does not fail the backfill; provider availability, timeouts, filesystem, parsing,
-concurrent-edit, and storage failures remain retryable errors.
-
-The opt-in post-update action is introduced for `3.0.0` and is also advertised during its prerelease cycle. If the
-model is absent, accepting the enrichment action installs it first. New active personal memories written through the
-CLI or Threadnote MCP are enriched automatically when the configured provider is loopback-local; storage still
-succeeds unchanged if enrichment is unavailable or fails. Emergency pre-compact snapshots are never delayed for
-enrichment.
-
-Effect owns the service lifecycle: configuration, download orchestration, the lifecycle lock, detached process and PID
-record, health checks, logging, lazy startup, and safe stop/uninstall behavior. The bundled Python file is only a thin
-OpenAI-compatible HTTP adapter around the `llama_cpp` package already installed with OpenViking; it does not decide
-when or how the process runs. Configured local AI follows `threadnote start`, `stop`, `repair`, and `uninstall`, and a
-weak recall can lazily start a stopped service. A bounded readiness wait fails open, so model startup cannot prevent the
-deterministic recall result from returning.
-
-Stop verifies the authenticated launch ID, PID, model, and model path immediately before signaling the process. If a
-stale record or unhealthy endpoint cannot prove ownership, Threadnote refuses to signal that PID and leaves manual
-inspection to the user.
-
-Use the dedicated commands for explicit control:
-
-```bash
-threadnote local-ai disable # stops and disables local AI; preserves its model and configuration
-threadnote local-ai enable # enables the installed model; the next weak recall can start it lazily
-threadnote local-ai start
-threadnote local-ai stop
-threadnote local-ai status
-threadnote local-ai model switch # interactively select an installed model and press Enter
-threadnote local-ai uninstall # preserves the managed model
-threadnote local-ai uninstall --erase-model # also removes the managed model
-```
-
-`start` and `stop` control only the current service process. `enable` and `disable` persist whether Threadnote may use
-the installed provider for recall, enrichment, and consolidation. The generic switching flow is retained for future
-verified models: it safely stops the authenticated current service, changes the active model, and restarts it when
-local AI is enabled. If no verified managed model is present, the switch command exits successfully and prints the
-installation command.
-
-For recall, deterministic retrieval and ranking always run first. High-confidence results never call the model.
-For weaker results, an explicit loopback model first reranks at most 24 query-ranked local index candidates using
-bounded, scrubbed topic, title, and index excerpts. Selected topics become grounded rewrites: medium-confidence results
-evaluate one and low-confidence or no-answer results evaluate at most two, one search scope at a time. Mandatory
-OpenViking hits remain in deterministic ranking but do not crowd this grounded candidate window. Rewrites are
-schema-validated, deduplicated, cached by query/project/provider fingerprint, and limited to 512 characters.
-
-Expanded candidates are merged with the original candidates and exact matches, then reranked by the same deterministic
-ranker. The loopback model receives at most 24 final candidate IDs and may keep at most 8 directly relevant IDs; it
-cannot add candidates or change their deterministic order. Threadnote retains the first two non-lexical deterministic
-anchors so a small model cannot suppress the strongest ranked evidence. An empty confident selection produces no
-answer, while a timeout, malformed response, or unknown-ID-only response preserves the deterministic result. Each model
-stage uses deterministic sampling, times out after five seconds, and fails open. Remote endpoints never receive
-candidate summaries or run either filtering stage.
-
-Remote query expanders receive only the original query and inferred project. Explicit loopback endpoints additionally
-receive a bounded, scrubbed shortlist of local topic/identifier names with six-term index excerpts; rewrites that do
-not contain an exact shortlist term are discarded. This grounding data never goes to a non-loopback endpoint.
-
-During recall, the local model does not extract additional memory candidates or mutate canonical memories. Candidate
-extraction keeps its existing policy. Explicit enrichment only adds retrieval keywords to personal and configured
-shared durable memories; older Threadnote versions ignore those unknown repeated headers and continue reading the
-document body. Explicit manager consolidation may use the same provider to generate an Effect Schema-validated
-`{ "draft": string }` object, but generating a draft never deletes source memories; cleanup remains a separate
-user-approved operation.
-
-Environment variables remain an advanced override for Ollama, LM Studio, hosted APIs, or another
-OpenAI-compatible chat-completions endpoint:
-
-```bash
-export THREADNOTE_EFFECT_AI=1
-export THREADNOTE_EFFECT_AI_MODEL=
-export THREADNOTE_EFFECT_AI_API_KEY= # optional when the endpoint does not require one
-export THREADNOTE_EFFECT_AI_API_URL= # optional for the default OpenAI endpoint
-```
-
-An explicit environment provider takes precedence over persisted local AI. Set `THREADNOTE_EFFECT_AI=0` to disable the
-persisted provider for one process.
-
-Use an instruction-tuned model with at least a 2K context window. Small models can satisfy the JSON schema while still
-choosing superficial topic matches; the expander fails open, but recall quality depends on the local model.
-
-## Upgrading Effect
-
-Keep every Effect package on the same exact beta, then run:
-
-```bash
-npm run lint
-npm run prettier:check
-npm run typecheck
-npm run build
-npm run check:bundle-size
-npm test
-npm run pack:dry-run
-```
-
-Treat a change to unstable MCP/AI behavior as a compatibility migration: update the adapter and parity tests together.
-Do not bypass schema, tool-list, instruction, lifecycle, or bundle-size failures merely to accept a beta upgrade.
-
-## Local-bin end-to-end validation
-
-The dedicated distribution suite uses the built CommonJS launchers, a real pinned OpenViking CLI/server, a random local
-port, and a suite-scoped temporary datastore. It exercises CLI memory and share writes, manager APIs and shutdown,
-native OpenViking MCP plus Threadnote stdio MCP, optional Effect AI consolidation, and an installed npm tarball. Global
-teardown stops the server and recursively removes the temporary datastore.
-
-```bash
-npm run test:e2e:install-openviking # explicit opt-in; installs the version pinned in src/constants.ts with uv
-npm run test:e2e:local-bins
-```
-
-The E2E command refuses an OpenViking CLI/server version that differs from the Threadnote pin. CI runs the installer and
-suite in a dedicated job, so updating the pin automatically validates the new OpenViking version across the shipped
-entrypoints. If uv rejects a malformed prebuilt `llama-cpp-python` wheel, the installer retries from source with the same
-bounded-parallelism and macOS Metal settings as Threadnote's production installer.
+Architecture tests enforce that raw filesystem, process, HTTP, crypto, and native-addon access stays inside adapters.
diff --git a/docs/index.html b/docs/index.html
index 196262b..7f018dc 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1194,10 +1194,8 @@ Native memory remembers you. Threadnote connects your team's agents.
04 · The model
Persistence is the plumbing. Portability is the product.
- Threadnote connects supported agents to a local
- OpenViking store, then uses explicit Git
- publishing for team knowledge. The local lifecycle makes shared records current, scoped, and safe enough to
- reuse.
+ Threadnote connects supported agents to its own local Markdown store, then uses explicit Git publishing for
+ team knowledge. The local lifecycle makes shared records current, scoped, and safe enough to reuse.
@@ -1230,8 +1228,8 @@
Current knowledge, not a transcript
Local recall
Find the record, not the whole history
- OpenViking runs a local GGUF embedding model through llama.cpp to rank matches. Agents read
- only the selected viking:// records; the Markdown source stays on disk.
+ Threadnote can run a local GGUF embedding model in-process through node-llama-cpp to rank
+ matches. Agents read only the selected viking:// records; Markdown remains canonical.
@@ -1270,8 +1268,8 @@ Raw .md files
Threadnote
-
- Where it lives: local OpenViking store, cross-repo, cross-agent. Optionally published
- to a team git repo.
+ Where it lives:
~/.threadnote, cross-repo and cross-agent. Optionally
+ published to a team Git repo.
-
Best for: feature knowledge, handoffs, branch state, prod-on-call findings, skills,
@@ -1351,17 +1349,17 @@
One MCP adapter, one URI scheme, one local store.
-
+
-
+
- openviking-server
+ Threadnote native runtime
- 127.0.0.1:1933 · python · uv-managed
+ Effect services · node-llama-cpp · in-process
@@ -1374,10 +1372,10 @@ One MCP adapter, one URI scheme, one local store.
- ~/.openviking/data/viking/<account>/...
+ ~/.threadnote/data/viking/<account>/...
- vectordb (local semantic index)
+ indexes/vector (regenerable sidecar)
@@ -1415,7 +1413,7 @@
Local by default. Explicit when it isn't.
Writes stay on disk
Every remember / handoff writes under THREADNOTE_HOME (defaults
- to ~/.openviking). Nothing leaves the machine without explicit user action.
+ to ~/.threadnote). Nothing leaves the machine without explicit user action.
@@ -1471,8 +1469,7 @@
No half-published state
08 · Install
One command. Then point your agents at it.
- The installer wires up the npm package, the OpenViking Python environment, and the local server. After that,
- mcp-install per agent and you're done.
+ The installer wires up one npm package. After that, run mcp-install for each agent you use.
@@ -1482,14 +1479,10 @@
1. Install threadnote
$ curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
-Installed threadnote@1.3.2
+Installed threadnote@4.0.0
Already exists: ~/.local/bin/threadnote
-OpenViking installs into Python; neither uv nor pipx is
-on PATH so threadnote would fall back to `pip install
---user`, which fails on PEP 668 setups.
-Install uv now? [Y/n] Y
-Installing uv via Homebrew…
-OK openviking 0.4.10 ready · server healthy
+
Initialized ~/.threadnote
+
OK native store ready · no daemon required
On native Windows PowerShell, run
@@ -1514,9 +1507,8 @@
2. Wire up each agent (pick what you use)
Without --apply, each command prints a dry run so you can review before changing your agent
- configs. The default adapter exposes eight focused core tools; add --toolset full for
- memory maintenance, raw OpenViking, conflict, skill, and bundle tools. Run doctor anytime
- to verify.
+ configs. The default adapter exposes focused core tools; add --toolset full for memory
+ maintenance, conflict, skill, and bundle tools. Run doctor anytime to verify.
@@ -1546,7 +1538,7 @@ Generate a manifest
$ threadnote init-manifest \
--repo ~/src/web-app \
--repo ~/src/mobile-app
-Wrote ~/.openviking/seed-manifest.yaml (2 project(s))
+Wrote ~/.threadnote/seed-manifest.yaml (2 project(s))
$ threadnote seed --graph
Imported 47 curated path(s); skipped 6 secret-flagged.
@@ -1640,7 +1632,7 @@ Repair after a moved checkout
OK threadnote shim: ~/.local/bin/threadnote
OK codex / claude / cursor / copilot user instructions
OK manifest, ignore file, templates
-OK openviking health
+OK canonical store and derived indexes
Summary: 0 failure(s), 0 warning(s)
@@ -1731,13 +1723,13 @@
One-time setup
$ threadnote share init git@github.com:org/team-memories.git
-Cloned into ~/.openviking/data/.../memories/shared/default/
-Ingested 12 shared memory file(s) into OpenViking.
-Added .gitignore exclusions for OV directory summaries.
+
Cloned into ~/.threadnote/share/worktrees/default/
+
Ingested 12 shared memory file(s) into the native store.
+
Added .gitignore exclusions for derived summaries.
- Working tree lives inside the OV data tree so recall sees it. The git directory is moved aside via
- --separate-git-dir so OV never sees git internals.
+ The Git worktree is isolated from canonical storage. Threadnote validates and copies reviewed changes
+ into the local store; --separate-git-dir keeps Git internals out of recall.
@@ -1818,20 +1810,6 @@ A local web UI for the whole memory store.
manage shares — everything it does, the CLI and MCP do too.
-
-
Memory
@@ -1845,8 +1823,8 @@
Browse & edit the tree
Health
Doctor on open
- The default tab runs doctor automatically. Run Doctor, Start OpenViking, and Repair stream
- into a scrollable output pane — the view in the screenshot above.
+ The default tab runs doctor automatically. Doctor and Repair stream into a scrollable
+ output pane, including canonical-store, model, and index checks.
@@ -2017,7 +1995,7 @@
Try it on your machine in 90 seconds.
- threadnote · AGPL-3.0-or-later · built on OpenViking 0.4.10 · use ↑
+ threadnote · AGPL-3.0-or-later · self-contained Node.js runtime · use ↑
↓ / j k to navigate
diff --git a/docs/migration.md b/docs/migration.md
index 31ac666..5335932 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -1,258 +1,39 @@
-# Migration
+# 4.0 home migration
-This guide explains how to switch a developer workflow to `threadnote` without losing the repo-local instruction
-model that current agents rely on.
+Threadnote 4 owns `~/.threadnote`. The legacy 3.x home is input to a one-time, non-destructive migration and is not a
+runtime dependency.
-## Short Answer
-
-Do not remove `AGENTS.md`, `CLAUDE.md`, `.claude/`, or `.agents/` as part of the migration.
-
-Those files remain the versioned source of truth for repo-local behavior. OpenViking becomes a shared, searchable
-context and memory layer on top of them. It helps agents recall handoffs, skills, and curated guidance across tools,
-but it should not replace the files that fresh agents read directly from the working tree.
-
-## Authority Model
-
-- `AGENTS.md` and `CLAUDE.md`: canonical repo instructions. Keep these small, current, and checked in.
-- Nested `AGENTS.md` and `CLAUDE.md`: canonical module-specific overrides. Keep them next to the code they govern.
-- `.claude/commands` and `.claude/skills`: executable or tool-specific workflows. Keep them where Claude and other
- local tools can discover them.
-- `.agents/`: agent/plugin metadata or repo-local automation config. Keep it unless the owning tool no longer uses it.
-- OpenViking: durable memory, cross-agent handoffs, searchable snapshots of curated guidance, and seeded skill
- catalogs.
-
-When these sources disagree, the checked-in repo instruction file wins. Update the source file first, then refresh the
-OpenViking context.
-
-## DX Model
-
-Developers should not need to run `recall`, `remember`, or `handoff` as a normal habit.
-
-The intended workflow has three layers:
-
-- Agent-first: Codex, Claude, Copilot, or another MCP-enabled agent calls OpenViking tools when the task calls for
- shared context.
-- Short CLI fallback: humans and scripts can run `threadnote recall`, `threadnote remember`, or
- `threadnote handoff` from any repo.
-- Checkout-local command: `npm run threadnote -- ...` is the bootstrap and debugging path before the short command shim
- is installed.
-
-After MCP install, developers can use natural language:
-
-```text
-Recall the last handoff for this branch.
-Remember that this repo uses .
-Create a handoff for the next agent before you stop.
-```
-
-For better continuity, run `threadnote install` so it can add the agent-side guidance from `docs/agent-instructions.md`
-to user-level Codex, Claude, Cursor, and Copilot instruction files. That guidance tells agents to recall context at task
-start and, after meaningful work, directly store normal durable feature knowledge and handoff state. Only additional
-session-extracted candidates require the user to approve, edit, defer, or reject them.
-
-## Migration Steps
-
-Run install commands from any working directory. Run manifest commands from a repo root, or pass explicit `--repo`
-paths.
-
-1. Check prerequisites:
-
- ```bash
- curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | sh
- threadnote doctor --dry-run
- ```
-
- To force Bun or Deno:
-
- ```bash
- curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | THREADNOTE_RUNTIME=bun sh
- curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/install.sh | THREADNOTE_RUNTIME=deno sh
- ```
-
-2. Install or repair local OpenViking. This also installs the short `threadnote` command shim to `~/.local/bin` and
- upserts user-level agent instructions by default. The one-line installer already runs `threadnote install`; run these
- only when doing the manual install flow or previewing changes:
-
- ```bash
- threadnote install --dry-run
- threadnote install
- ```
-
- If `threadnote` is not found after install, add `~/.local/bin` to `PATH` or rerun install with
- `THREADNOTE_BIN_DIR=`.
-
-3. Create the developer-local manifest for the repos this machine actually uses:
-
- ```bash
- threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
- ```
-
- `--repo` can be repeated. Paths may be anywhere on the machine. If no `--repo` is provided, the current git repo is
- used. The manifest is written to `~/.openviking/seed-manifest.yaml` by default and is intentionally not checked in.
-
-4. Verify the local service:
-
- ```bash
- threadnote doctor --dry-run
- ```
-
-5. Inspect curated repo imports:
-
- ```bash
- threadnote seed --dry-run
- ```
-
-6. Seed curated repo guidance after reviewing the dry-run output:
-
- ```bash
- threadnote seed
- ```
-
- Seeded docs refresh on the next `threadnote seed`/`repair`, which re-runs the
- secret scan. To let OpenViking auto-refresh them between runs, opt in with
- `THREADNOTE_SEED_WATCH_INTERVAL= threadnote seed`. Watches attach
- only to original, non-redaction-prone files, since an OpenViking-managed
- refresh re-ingests the file without Threadnote's per-import secret scan.
-
-7. Inspect and seed shared skills:
-
- ```bash
- threadnote seed-skills --dry-run
- threadnote seed-skills
- ```
-
-8. Wire one agent at a time:
-
- ```bash
- threadnote mcp-install codex
- threadnote mcp-install codex --apply
- ```
-
- The default stdio adapter installs the focused core toolset. Add `--toolset full` when the agent needs MCP tools for
- memory maintenance, raw OpenViking operations, sharing conflicts, or shared skills and bundles. Start a fresh agent
- session after changing the toolset.
-
- Then repeat for Claude:
-
- ```bash
- threadnote mcp-install claude
- threadnote mcp-install claude --apply
- ```
-
- Claude installs at user scope by default so it works from every repo/worktree. Use `--scope local` only when a
- repo-specific Claude MCP entry is intentional.
-
- For Cursor:
-
- ```bash
- threadnote mcp-install cursor
- threadnote mcp-install cursor --apply
- ```
-
- Cursor installs by updating the global `~/.cursor/mcp.json` file.
-
- For GitHub Copilot in VS Code:
-
- ```bash
- threadnote mcp-install copilot
- threadnote mcp-install copilot --apply
- ```
-
- Copilot installs by updating the VS Code user-profile `mcp.json` file. If VS Code uses a custom profile path, set
- `THREADNOTE_COPILOT_MCP_CONFIG` to that `mcp.json` path before running `mcp-install copilot`.
-
- Later, if the checkout that installed the MCP adapter is deleted or moved, repair it from any fresh checkout:
-
- ```bash
- threadnote repair
- ```
-
-9. Validate recall:
-
- ```bash
- threadnote recall --query "repo testing guidance"
- ```
-
-## Daily Workflow
-
-At the start of a task, agents should still read the nearest `AGENTS.md` or `CLAUDE.md` files from the repo. OpenViking
-is the cross-session layer.
-
-Preferred developer behavior is conversational:
-
-```text
-Recall anything relevant for this branch before you start.
-Remember this feature decision for future agents: ...
-Create a handoff now.
+```sh
+threadnote migrate
+threadnote migrate --apply
```
-Preferred agent behavior is automatic after `threadnote install` has updated the user-level instruction files:
-
-- On non-trivial task start, search OpenViking for recent handoffs, durable feature memories for the branch/topic, and
- relevant repo guidance.
-- When the user says "remember", store the memory after checking that it contains no secret or customer data.
-- When continuing the same active issue, keep two current-state records when useful: a durable feature memory for design,
- decisions, interfaces, and gotchas, plus a handoff for status, tests, blockers, and next steps.
-- When valuable feature knowledge changes, update the durable feature memory with the same project/topic or
- `remember --replace ` instead of burying that knowledge only in a handoff.
-- When current status changes, update the active handoff with the same project/topic or `handoff --replace ` instead
- of creating another near-duplicate progress memory.
-- When recall surfaces clearly duplicate or stale memories, run `threadnote compact --project --topic
---dry-run` or `compact_context({"project":"","topic":"","dryRun":true})`. Keep cleanup scoped to the
- current project/topic; archive stale handoffs by default and forget only exact redundant duplicates.
-- Before pausing, switching agents, or finishing meaningful code changes, store a concise handoff with status, tests,
- blockers, and next steps.
-
-Manual CLI remains available for scripts and emergencies:
-
-```bash
-threadnote recall --query "last handoff for this branch"
-threadnote recall --query "durable feature knowledge for this branch"
-threadnote remember --kind durable --project example --topic active-feature --text "Feature knowledge..."
-threadnote remember --replace viking://user/example/memories/events/current.md --text "Updated durable engineering note..."
-threadnote compact --project example --topic active-feature --dry-run
-threadnote handoff --project example --topic active-feature --task "short task summary" --tests "checks run" --next-step "what the next agent should do"
+The migration:
+
+1. inventories canonical resources, memories, seed state, share metadata, and applicable settings;
+2. rejects absolute symlinks and relative symlinks or paths that escape the source;
+3. excludes server, interpreter-environment, PID, socket, cache, and transient files;
+4. checks target free space for source bytes plus a bounded safety margin;
+5. copies into a staging sibling of the final home;
+6. records source size, modification time, and SHA-256;
+7. verifies every staged hash and detects source changes during the copy;
+8. isolates legacy Git share worktrees under `share/worktrees/`, rewrites their Git pointers, and retains a separate
+ canonical Markdown copy;
+9. records and verifies the transformed staging-tree hash;
+10. writes a checksummed migration receipt;
+11. atomically promotes the staging directory.
+
+Interrupted staging is resumable. Re-running after success is idempotent. An unrelated existing target is never
+overwritten. The source remains byte-for-byte untouched and is never automatically removed.
+
+After promotion:
+
+```sh
+threadnote doctor
+threadnote recall "latest handoff"
+threadnote models list
+threadnote index status
```
-## Repo Paths
-
-The workflow is not tied to any fixed repo list. Repo discovery is manifest-driven:
-
-- `~/.openviking/seed-manifest.yaml`: developer-local default manifest, created by `threadnote init-manifest`.
-- `THREADNOTE_MANIFEST`: override for custom teams, experiments, or CI.
-- `--manifest `: one-off override for `seed` and `seed-skills`.
-- `config/seed-manifest.example.yaml`: checked-in example only.
-
-Use `threadnote init-manifest --repo ` whenever a developer adds a new repo they want included. The command
-derives a stable `viking://resources/repos/` URI and keeps the seed patterns conservative.
-
-## Refreshing Context
-
-OpenViking stores imported context as durable resources. For v1, treat `seed` as a first-ingest operation. When a
-seeded instruction file changes, update the checked-in source first, then refresh the relevant `viking://` resource.
-
-Current practical options:
-
-- Remove the old resource with `forget`, then re-run a scoped seed manifest.
-- Use `remember` for short corrections that should be available immediately.
-- Export/import packs only for moving a known-good local context between machines.
-
-Do not edit OpenViking directly and leave the repo instruction file stale.
-
-## What To Remove
-
-Remove nothing during the initial migration.
-
-After the workflow is proven, teams may delete or consolidate only content that has a clear owner-approved replacement.
-Good candidates are stale handoff notes, obsolete duplicate docs, or abandoned per-agent experiments. Bad candidates are
-canonical instructions, active commands, active skills, MCP config, or anything required by existing tools.
-
-## Cutover Checklist
-
-- `doctor --dry-run` reports a healthy OpenViking server and no stale recall index warnings.
-- `threadnote` works from a different repo or subdirectory.
-- `mcp-install` has been applied for the agent the developer actually uses.
-- `recall` returns seeded guidance.
-- A test `handoff` can be stored and recalled by another agent.
-- `AGENTS.md` and `CLAUDE.md` still describe the source-of-truth repo rules.
-- The team has agreed on which seeded paths are allowed and which sensitive paths stay excluded.
+Lexical recall is immediately available. Optional vector indexes are derived data and should be rebuilt with an
+explicitly installed and selected embedding model.
diff --git a/docs/rollout.md b/docs/rollout.md
index f15581d..9ae9a7c 100644
--- a/docs/rollout.md
+++ b/docs/rollout.md
@@ -1,24 +1,18 @@
-# Rollout
+# 4.0 rollout
-Start with a local-only pilot.
+Release gates are cumulative:
-## Pilot Steps
+1. Frozen 3.0.3 recall-v2 and M1 Max performance baselines remain immutable.
+2. Unit, integration, type, lint, formatting, coverage, build, and package-content checks pass.
+3. Global and per-category recall non-inferiority pass with no safety or contract regression.
+4. Clean install and core recall pass on Linux, macOS, and Windows without an interpreter or daemon.
+5. Migration fault-injection covers interruption, insufficient space, unsafe links, source mutation, unrelated target,
+ idempotence, and preserved rollback source.
+6. Model bake-off artifacts record exact revisions, hashes, hardware, latency, memory, and recall deltas.
+7. Package scans prove no legacy executable, server config, interpreter bootstrap, or raw native-addon consumer ships.
-1. Run `doctor --dry-run`.
-2. Run `install --dry-run`, review paths, then run `install`.
-3. For a solo pilot, use the default local embedding backend; for broader rollout, configure a company-approved
- embedding and summary provider in `~/.openviking/ov.conf`.
-4. Run `start` and confirm `doctor` reports a healthy server.
-5. Run `seed --dry-run` and inspect every planned import.
-6. Run `seed`.
-7. Run `seed-skills --dry-run`, then `seed-skills`.
-8. Install MCP for one agent, validate recall, then install the second.
+Semantic and reranker defaults are selected only from checked-in reviewed bake-off summaries. Models remain an explicit
+download even after selection; lexical recall is the zero-download default.
-## Acceptance Criteria
-
-- Install completes in under 10 minutes after prerequisites.
-- `doctor` reports clear actionable checks.
-- Codex, Claude, Cursor, or Copilot can store and recall a shared handoff after MCP setup.
-- Seeding curated guidance does not import known secret patterns.
-- Fresh agents can recall repo testing guidance and discover relevant skills.
-- `uninstall --dry-run` previews removal, and `uninstall` leaves memories intact unless `--erase-memories` is explicit.
+Rollback never deletes canonical data. Disable model selection or purge a derived index first. For a migration issue,
+point the previous release at the preserved legacy home while the 4.0 target is investigated.
diff --git a/docs/security.md b/docs/security.md
index 56d4ee5..81240c7 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -1,32 +1,20 @@
# Security
-`threadnote` treats OpenViking as durable local memory, so the default workflow is conservative.
+Threadnote stores local canonical content under `~/.threadnote` with private directories and files. Every `viking://`
+identifier is parsed into validated portable segments; traversal, ambiguous encodings, escaping links, and unsupported
+file types are rejected.
-## Do Not Ingest
+Writes use per-resource locks, compare-and-swap where replacement semantics require it, a same-directory temporary
+file, durable close, and atomic rename. Derived index generations are activated by a checksummed pointer only after
+the full generation exists.
-- Credentials, access tokens, API keys, certificates, private keys, or shell history.
-- Customer data, production data, HIPAA data, or production logs without explicit approval and scrubbing.
-- Local auth files such as `~/.codex/auth.json`, `~/.claude/.credentials.json`, Cursor account/session files, or VS Code
- Copilot account/session files.
-- Local settings files that may contain secrets unless they go through redaction.
+Model downloads are explicit and pinned by immutable repository revision, byte count, SHA-256, role, runtime version,
+and license. Partial downloads are never loaded. The llama adapter requests prebuilt binaries and refuses runtime
+compilation or implicit binary download.
-## Built-In Controls
+Share publishing scrubs known credential and machine-local path patterns before writing or pushing. Handoffs and
+preferences are not publishable. Publish order preserves the personal source until the shared canonical write,
+verification, git commit, and push succeed.
-- `.threadnoteignore` excludes common secret and build-output paths.
-- `.mcp.json`, `config.toml`, and settings JSON are redacted before import.
-- Files are skipped if common secret patterns remain after redaction.
-- `mcp-install` requires `--apply` before it changes Codex, Claude, Cursor, or Copilot config.
-- `install` updates user-level Codex, Claude, Cursor, and Copilot instruction files through managed Threadnote content.
- Existing personal instructions outside managed blocks are preserved.
-- `uninstall` preserves local memories by default. `--erase-memories` is required before deleting `THREADNOTE_HOME`.
-- Config files created under `THREADNOTE_HOME` are written with user-only permissions.
-
-## Rollout Requirements
-
-Before team-wide use, get explicit legal and security review for:
-
-- OpenViking licensing and operational use.
-- Approved embedding and summary model providers.
-- At-rest encryption settings.
-- Local API key requirements.
-- Forget/removal expectations for stale or sensitive context.
+The manager binds to loopback, uses a per-process bearer token, and never exposes a model or memory server. MCP uses
+stdio. Threadnote has no background daemon, listening storage port, or native HTTP MCP endpoint.
diff --git a/docs/share.md b/docs/share.md
index 80ecdc2..b01e2b8 100644
--- a/docs/share.md
+++ b/docs/share.md
@@ -1,470 +1,43 @@
-# Sharing context with teammates
+# Team sharing
-`threadnote share` lets a small team keep curated durable memories and agent
-artifacts in a git repository so every member's local agent can recall them.
-Personal handoffs, preferences, and unpublished durable notes stay local; only
-content you explicitly publish leaves your machine.
+Threadnote shares reviewed durable memories and agent artifacts through a user-provided Git repository. Canonical
+shared resources remain under `~/.threadnote/data/.../memories/shared//`; git metadata and checked-out files are
+isolated under `~/.threadnote/share/teams/` and `~/.threadnote/share/worktrees/`. Their exact paths are recorded in
+`~/.threadnote/share/teams.json`.
-## Model in one screen
-
-- A **team** is a configured shared repo. Each team has a name (default:
- `default`), a git remote, a local working tree, and a separate gitdir.
-- The working tree lives inside the OpenViking data tree at
- `~/.openviking/data/viking//user//memories/shared//`. That
- means files appearing in the worktree are also addressable as
- `viking://user//memories/shared//...` and show up in normal
- `recall`.
-- The gitdir lives outside the OV data tree at
- `~/.openviking/share/teams/.gitdir/` so OpenViking never sees git
- internals.
-- Team configuration is recorded in `~/.openviking/share/teams.json` (mode
- `0600`).
-
-## Workflow
-
-### One-time setup
-
-```bash
-# Create the repo first on GitHub/GitLab/etc. and copy its SSH URL.
+```sh
threadnote share init git@github.com:org/team-memories.git
-# Optional: add a second team
-threadnote share init --team friends git@github.com:you/friends-memories.git
-# Switch the default with init --set-default or by running publish/sync
-# with an explicit --team.
-```
-
-`share init` clones the remote into your local memory tree and ingests any
-existing shareable markdown into OpenViking.
-
-### See what's configured
-
-```bash
-threadnote share list
-threadnote share status # default team
-threadnote share status --team friends
-```
-
-### Share a memory
-
-```bash
-# 1. Identify the personal URI you want to publish (use recall/list as usual).
-# 2. Preview the would-be-published bytes before committing:
-threadnote share publish viking://user/you/memories/durable/projects/foo/bar.md --preview
-# 3. Publish:
-threadnote share publish viking://user/you/memories/durable/projects/foo/bar.md
-# Optional flags:
-# --preview Read + strip + scrub the memory and print the exact bytes
-# that would land in git; no writes, no commits, no pushes.
-# Use this before any publish to catch leaks by inspection.
-# --redact Replace soft-leak matches (local home paths) with
-# placeholders and continue. Credentials still block.
-# --team , --message "...", --no-push, --dry-run.
-```
-
-`share publish` writes the memory into the team's shared subtree, commits with
-the message `share: publish `, pushes, then removes the personal
-copy after the push succeeds. The memory's recall path becomes
-`viking://user/you/memories/shared//durable/projects/foo/bar.md`.
-
-Before writing, `share publish` strips `supersedes:` and `archived_from:`
-lines from the memory's header block. Those pointers only resolve on the
-publisher's machine — teammates pull via git and cannot dereference them — so
-keeping them would just leak local-only provenance into team git history.
-
-To update an existing shared durable memory, replace the shared URI directly:
-
-```bash
-threadnote remember \
- --replace viking://user/you/memories/shared/default/durable/projects/foo/bar.md \
- --project foo \
- --topic bar \
- --text "Updated shared knowledge."
-```
-
-For MCP, pass the same shared URI as `replaceUri` to `remember_context`.
-Threadnote rewrites the shared memory in place, strips local-only provenance
-headers, commits, and pushes the shared repo. You do not need to store a
-personal replacement and run `share publish` again.
-
-### Share a skill or command
-
-Shared agent artifacts live in the same team repo, but outside `durable/`:
-
-```text
-agent-artifacts/
- skills/codex//SKILL.md
- skills/claude//SKILL.md
- commands/claude/.md
-```
-
-Publish a local artifact:
-
-```bash
-threadnote share publish-artifact ~/.codex/skills/reviewer/SKILL.md
-threadnote share publish-artifact ~/.claude/skills/triage/SKILL.md
-threadnote share publish-artifact ~/.claude/commands/review-pr.md
+threadnote share status
+threadnote share sync
+threadnote share publish viking://user/me/memories/durable/projects/app/cache.md
```
-Path inference handles the common Codex and Claude locations. If the file is
-somewhere else, pass metadata explicitly:
+Only active durable memories are publishable. The scrubber blocks credentials, customer-like secrets, and residual
+machine-local paths; `--redact` applies only to supported soft patterns. Handoffs and preferences stay local.
-```bash
-threadnote share publish-artifact ./SKILL.md --agent codex --kind skill --name reviewer
-```
-
-Useful flags:
-
-```bash
-threadnote share publish-artifact --preview
-threadnote share publish-artifact --redact
-threadnote share publish-artifact --force
-threadnote share publish-artifact --no-push
-```
-
-`publish-artifact` runs the same scrubber as memory publish, writes the artifact
-under `agent-artifacts/`, ingests it into OpenViking under the shared team
-namespace, commits, and pushes. Existing artifacts with different content are
-not overwritten unless `--force` is passed.
-
-#### Multi-file skills (bundles)
-
-A skill is shared as its **whole directory**, not just `SKILL.md`. When companion
-files sit beside the `SKILL.md` (reference docs, scripts, templates), they travel
-with the skill:
-
-```text
-agent-artifacts/skills/codex//
- SKILL.md # recall anchor (OpenViking-ingested)
- scripts/run.ts # companion, carried in git
- reference.md
- .threadnote-bundle.json # generated member manifest (sha + binary flags)
-```
-
-Notes:
-
-- A lone `SKILL.md` with no companions publishes exactly as before — no manifest,
- one file.
-- Every **text** member runs through the scrubber, so a leaked credential or local
- path in a companion script blocks the publish just like it would in `SKILL.md`.
-- Only `SKILL.md` (and any sibling `.md`) is OpenViking-ingested for recall;
- companions ride in git and are materialized on install.
-- Runtime/scratch dirs and local junk are never bundled: `reviews/`, `repos/`,
- `node_modules/`, `.git/`, `.DS_Store`, `*.log`.
-- **Binary** members are blocked by default because the scrubber cannot inspect
- them; pass `--allow-binary` (CLI) / `allowBinary: true` (MCP) to include them.
- A credential detected in the bytes still blocks regardless.
-- A skill whose helpers live **outside** its own directory (a multi-skill
- constellation with shared code at the repo root) is shared as a **pack** — see
- below.
-
-#### Constellation packs
+Publish is transactional:
-When several skills share code that lives outside any single skill directory
-(e.g. repo-root `scripts/` and `lib/`), publish them together as a **pack** from
-a `threadnote-bundle.json` manifest at the repo root:
+1. re-read and scrub the personal source;
+2. create and verify the shared canonical resource;
+3. stage, commit, and optionally push the shared file;
+4. verify the personal source did not change;
+5. remove the personal copy.
-```json
-{
- "version": 1,
- "name": "review-pr-suite",
- "agent": "claude",
- "description": "PR/MR review constellation.",
- "skills": [".claude/skills/review-pr", ".claude/skills/pr-action"],
- "include": ["scripts", "lib", "package.json", "tsconfig.json"],
- "deps": {"runtime": ["bun"], "cli": ["gh", "glab", "jq"], "mcp": ["mcp__pal__clink"]},
- "pathRewrites": [{"from": "/Users/alex/code/reviewer"}]
-}
-```
-
-```bash
-threadnote share publish-bundle ./threadnote-bundle.json --preview
-threadnote share publish-bundle ./threadnote-bundle.json
-threadnote share install-artifacts --kind pack --name review-pr-suite --apply
-```
-
-```text
-share_bundle({"path":"~/src/reviewer/threadnote-bundle.json"})
-```
-
-How packs work:
+A failed canonical write, verification, commit, or push preserves the personal source. Artifact and pack publishing
+uses an undo journal so partial companion trees are removed and replaced content is restored.
-- Every declared skill plus the `include` paths are gathered, preserving the
- author's repo-relative layout, and written under
- `agent-artifacts/packs///files/...` next to a generated
- `.pack.md` recall index and a `.pack.json` manifest.
-- Install materializes the **whole tree under one root**
- (`~/.{codex,claude}/skills/threadnote-packs///` — a dedicated
- namespace so a pack and a same-named skill never share an install directory),
- preserving the source repo layout (so a skill declared at
- `.claude/skills/` lands at `/.claude/skills//SKILL.md` beside
- `/scripts/...`). This keeps **file-anchored** references working
- unchanged: relative imports (`../lib/types`) and `import.meta.dir`-relative
- paths resolve exactly as they did in the source repo.
-- **CWD-relative invocations do not auto-resolve.** A bare `bun run scripts/...`
- in a skill body is resolved against the agent's working directory, not the
- pack root, so anchor such paths to the token:
- `bun run ${THREADNOTE_PACK_ROOT}/scripts/vcs-detect.ts`. The token is expanded
- to the absolute install root in every text member at install.
-- Hardcoded absolute repo-root paths are rewritten to that
- `${THREADNOTE_PACK_ROOT}` token at publish (the manifest dir plus any declared
- `pathRewrites`, which must be absolute repo-root paths) and expanded back to
- the real install directory at install. A residual `/Users` or `/home` path
- that no rewrite covers trips the scrubber and blocks the publish. Other
- machine-local absolute paths (`/opt`, `/srv`, `/private`, Windows `C:\…`) are
- **not** auto-detected — declare them in `pathRewrites` or strip them, and
- `--preview` before publishing. Binary members included via `--allow-binary`
- are scanned for embedded home paths and declared roots but are otherwise
- shipped byte-for-byte with no rewrite.
-- `deps` are declared, not bundled: Threadnote installs files, not runtimes or
- MCP servers. After install it prints a loud "this pack will NOT run until these
- exist" notice listing the runtime/CLI/OS tools and any MCP servers to
- configure separately.
-
-Agents can do the same through MCP:
-
-```text
-share_skill({"path":"~/.codex/skills/reviewer/SKILL.md"})
-```
-
-Agents can also list and install shared artifacts through MCP:
-
-```text
-list_shared_skills({})
-install_shared_skill({"name":"reviewer","agent":"codex","kind":"skill"})
-```
+`share sync` is remote-authoritative for clean tracked files and records pending canonical-store replays when one item
+fails. Dirty state, divergent local content, or conflicting edits are surfaced for explicit resolution:
-`list_shared_skills` syncs configured shared repos before listing and reports
-one of:
-
-- `not_installed`
-- `current`
-- `update_available`
-- `local_modified`
-- `remote_changed_and_local_modified`
-
-### Install shared artifacts locally
-
-Shared artifacts are recallable after sync, but they are not installed into
-agent-native locations automatically. Installation is opt-in:
-
-```bash
-threadnote share install-artifacts # preview only
-threadnote share install-artifacts --apply # write files locally
-threadnote share install-artifacts --name reviewer --agent codex --kind skill --apply
-```
-
-Install targets are namespaced by Threadnote team to avoid colliding with
-personal files:
-
-```text
-~/.codex/skills/threadnote///SKILL.md
-~/.claude/skills/threadnote///SKILL.md
-~/.claude/commands/threadnote//.md
-```
-
-If a target already exists with different content, installation refuses to
-overwrite local modifications unless `--force` is passed. Remote updates are
-applied by running the same install command again; Threadnote tracks the last
-installed shared hash in a sidecar metadata file next to the installed
-artifact. Start a new agent session after installing if that agent snapshots
-skills or commands at startup.
-
-For a multi-file skill, the whole `~/.{codex,claude}/skills/threadnote///`
-tree is installed (companions included) and the install is atomic — it is staged
-in a temporary directory and swapped into place, so an interrupted install never
-leaves a half-written, mixed-version skill. Bundle status folds every member into
-one verdict: a local edit to any member plus an upstream change to a different
-member reports `remote_changed_and_local_modified` and refuses to overwrite
-without `--force`.
-
-### Keep teammates' updates current
-
-Threadnote does a periodic background `git fetch` for configured share teams.
-When an agent calls MCP `recall_context` / `read_context`, or the CLI
-`threadnote recall` / `threadnote read`, Threadnote checks whether a shared
-repo is behind. If it is, Threadnote rebases the clean worktree, reindexes the
-pulled markdown files into OpenViking, and then returns the requested
-recall/read result. Sync errors degrade to warnings so memory access still
-works with the best local data available.
-
-For automatic recall/read sync, the configured remote is the source of truth
-for shared memories. Managed OpenViking metadata is ignored when comparing
-memory content, and remote additions, updates, and deletions replace the local
-OpenViking shared cache. If tracked Threadnote-managed files are dirty and
-there are no unpublished commits, Threadnote restores them from Git before
-rebasing and reports the repair. Unpublished commits are never discarded.
-Untracked or unmanaged files are left untouched and block the automatic rebase
-with an explicit warning.
-
-An individual memory that still cannot be ingested, for example because the
-scrubber rejects it or OpenViking is unavailable, remains queued for retry but
-does not prevent later remote commits or other memories from syncing. The
-warning names the pending count while successful Git sync still completes.
-
-Manual sync remains useful when you want to publish local edits, clear a dirty
-shared worktree, resolve git conflicts, or force a sync immediately:
-
-```bash
-threadnote share sync # all configured teams
-threadnote share sync --team friends # one team
-threadnote share sync --no-push # pull only, all configured teams
-```
-
-`share sync` without `--team` runs this flow for every configured team. For each
-team it will auto-commit edits in Threadnote-managed share paths, fetch and
-rebase onto the configured upstream, reindex pulled markdown files into
-OpenViking (so `recall` finds them immediately), and push. Managed share paths
-are root guidance/metadata files (`README.md`, `AGENTS.md`, `CLAUDE.md`,
-`SKILL.md`, `.gitignore`) plus `durable/` and `agent-artifacts/`. If other dirty
-files remain after staging those paths, sync stops before rebase so you can
-commit, remove, or ignore them. Pass `--no-auto-commit` to refuse syncing when
-the worktree is dirty. Automatic recall/read sync never commits a dirty shared
-worktree: it repairs tracked shared-cache drift from Git, but preserves
-unpublished commits and untracked or unmanaged files for explicit
-`threadnote share sync`.
-
-### Take a memory back
-
-```bash
-threadnote share unpublish viking://user/you/memories/shared/default/durable/projects/foo/bar.md
-```
-
-The memory is rewritten back into your personal namespace and removed from the
-shared repo.
-
-### Stop sharing for a team
-
-```bash
-threadnote share remove --team friends # deletes worktree + gitdir
-threadnote share remove --team friends --keep-files
-threadnote share remove --team friends --preserve-local
-```
-
-`share remove --preserve-local` copies shared durable memories back into the
-personal durable tree before removing the team config. It does not delete remote
-git history. `share remove` without `--keep-files` deletes the local checkout.
-Push any unpushed commits first (`threadnote share sync` or
-`git -C push`), otherwise unpublished work is lost.
-
-### Rename or move a team remote
-
-```bash
-threadnote share rename --team friends --to platform
-threadnote share set-url --team platform git@github.com:org/platform-threadnote.git
-```
-
-`share rename` moves the local worktree/gitdir, updates `teams.json`, and
-reindexes the shared memory namespace under the new team name. `share set-url`
-updates the git `origin` URL and verifies it with `git fetch`.
-
-## Privacy & safety rules
-
-- Only memories you actively publish leave your machine. `share init` will
- refuse to clone over a non-empty worktree.
-- `share publish` runs a best-effort scrubber over the memory text. It refuses
- to publish if it matches any of:
- - PEM private key headers (`-----BEGIN ... PRIVATE KEY-----`)
- - OpenAI / Anthropic-style `sk-...` keys (16+ chars). Note: this also matches
- any URL slug or random string starting with `sk-`; if you hit a false
- positive on legitimate content, edit the memory to break the pattern.
- - GitHub classic tokens (`gh[pousr]_...`)
- - GitHub fine-grained PATs (`github_pat_...`)
- - GitLab PATs (`glpat-...`)
- - HTTP `Bearer ...` tokens (20+ chars)
- - Bare JWTs (three base64url segments starting `eyJ...`) — catches a leaked
- token even when the surrounding `Authorization: Bearer ` prefix has been
- stripped. JWE tokens in legitimate documentation can collide; edit the
- memory if the false positive is unavoidable.
- - AWS access keys (`AKIA...`)
- - Slack tokens (`xoxa`, `xoxb`, `xoxc`, `xoxd`, `xoxe`, `xoxp`, `xoxr`,
- `xoxs`, with optional `-N-` segment markers — covers bot, user,
- configuration, legacy cookie, refresh, app, and similar shapes)
-- `share publish` also blocks on soft-leak patterns that show up routinely in
- curated memories. These are redactable: pass `--redact` to replace each
- match with a generic placeholder and continue. Credentials always block
- regardless of `--redact`.
- - macOS home paths (`/Users//...`) → ``
- - linux home paths (`/home//...`) → ``
-- The scrubber complements but does not replace human review. Strip the value,
- preview with `--preview`, and then publish.
-- Only `durable/` memories and `agent-artifacts/` markdown files are
- shareable. `handoffs/`, `preferences/`, `incidents/`, and other lifecycle
- kinds stay local by construction — both the initial ingest (`share init`) and
- the sync-pull reindex (`share sync`) skip any file outside the shareable
- top-level directories.
-- `share publish` deletes the personal copy after publishing. If you want to
- keep both, copy the memory to a new URI first (`ov read` then
- `threadnote remember`).
-- `share publish` refuses to overwrite an existing shared memory at the same
- URI; use `threadnote remember --replace ` or
- `remember_context({replaceUri:""})` for updates, or pick a
- different topic name.
-
-## Conflict resolution
-
-`share sync` fetches the remote, then rebases onto the branch's configured
-upstream. When git can't merge cleanly:
-
-1. The pull command reports the conflict and leaves the worktree in a
- rebase-in-progress state.
-2. Resolve the conflicts manually in the worktree (it's a normal git checkout).
-3. Run `git rebase --continue` (or `--abort`) yourself.
-4. Re-run `threadnote share sync` to finish the reindex and push.
-
-Two publishes touching the same `.md` from different machines will
-collide; coordinate ownership per-topic, or use distinct topics.
-
-### Resolve pending shared memory conflicts
-
-If `share sync` cannot reindex a shared memory into OpenViking, it keeps that
-file in the pending reindex queue and prints resolver commands. This is
-different from a git rebase conflict. Ordinary local shared-cache divergence is
-resolved automatically from the remote file; pending entries are reserved for
-real ingest failures such as scrubber rejection, an unreadable file, or an
-OpenViking error. Pending entries do not block unrelated remote updates.
-
-Inspect pending conflicts:
-
-```bash
+```sh
threadnote share conflicts
-threadnote share conflicts --team default
-threadnote share conflict show default:durable/projects/foo/bar.md
+threadnote share conflict-show
+threadnote share conflict-resolve --take shared
```
-Resolve exactly one pending entry:
-
-```bash
-# Accept the shared git file into OpenViking. No git commit is made.
-threadnote share conflict resolve default:durable/projects/foo/bar.md --take shared
-
-# Publish the local OpenViking content back to the shared repo, then push.
-threadnote share conflict resolve default:durable/projects/foo/bar.md --take local
-
-# Write an explicit merge result to both OpenViking and the shared repo.
-threadnote share conflict resolve default:durable/projects/foo/bar.md --from-file merged.md
-```
-
-Each resolver writes a backup under
-`~/.openviking/share/conflict-backups//...` before mutating local
-state. `--take shared` clears the pending item after updating or deleting the
-OpenViking resource. `--take local` and `--from-file` run the same scrubber as
-publish, commit the shared file, push by default, update OpenViking, and then
-clear only the resolved pending item. Pass `--no-push` to leave the resolution
-commit local.
-
-MCP agents have equivalent tools: `share_conflicts`, `share_conflict_show`, and
-`share_conflict_resolve`. `share_conflict_resolve` accepts `take:"shared"`,
-`take:"local"`, or `mergedContent:"..."`.
-
-## Cross-machine identity notes
+Use `--take local` only after reviewing the scrubbed content, or provide an explicit merged file. Backups are written
+before conflict mutation.
-Each user clones into their own user-namespaced path. A memory authored on
-machine A as `viking://user/alice/memories/shared/team/durable/projects/foo/bar.md`
-shows up on machine B as
-`viking://user/bob/memories/shared/team/durable/projects/foo/bar.md`. The file
-content is identical. `supersedes:` / `archived_from:` lines are stripped at
-the publish boundary so cross-machine URI references don't pollute team git
-history; explicit `viking://` references inside the body will point at the
-author's
-namespace. For now, prefer narrative references ("see the foo memory under
-shared/team") over URI links in shared content.
+Skills, commands, and constellation packs are namespaced by agent and kind. Bundle manifests list every member and
+path rewrite. Unsafe traversal, binary content without explicit permission, embedded credentials, reserved tokens,
+and locally modified installs are blocked.
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index d4ff19a..bc459a6 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -1,292 +1,71 @@
# Troubleshooting
-## `openviking-server` Missing
+## Start and stop do not launch a service
-Run:
+Threadnote 4 owns no daemon. `threadnote start` verifies the on-demand runtime and `threadnote stop` is a compatibility
+no-op. Use `threadnote doctor` for storage, index, and model diagnostics.
-```bash
-threadnote install
-```
-
-The installer prefers `uv`, then `pipx`, then the platform Python launcher (`py`/`python` on Windows or `python3` on
-macOS and Linux). For `curl | sh` installs, the wrapper reattaches `threadnote install` to your terminal when possible
-so it can prompt to install `uv` and continue instead of falling straight through to the pip fallback. Native Windows
-uses `scripts/install.ps1`, preserves npm's `threadnote.cmd` launcher, and can install `uv` with its official PowerShell
-bootstrap.
-
-## `uv` Fails With `UnknownIssuer`
-
-Some corporate machines trust PyPI through certificates installed in the system keychain. Threadnote passes
-`--system-certs` when it uses `uv` so those system certificates are loaded. This flag requires uv 0.11.0 or newer;
-Threadnote selects a compatible `uv` elsewhere on `PATH` or tries to update an older installation before using it.
-
-If the automatic update cannot produce a compatible version, update uv and retry:
-
-```bash
-uv self update # standalone uv installation
-brew upgrade uv # Homebrew installation
-threadnote update
-```
-
-If an older install still fails with `invalid peer certificate: UnknownIssuer`, retry with:
-
-```bash
-UV_NATIVE_TLS=1 threadnote install
-```
-
-Or use a different Python installer:
-
-```bash
-threadnote install --package-manager pipx
-threadnote install --package-manager pip
-```
-
-## Model Download Fails With `CERTIFICATE_VERIFY_FAILED`
-
-On install or first start, OpenViking may download the local embedding model from Hugging Face. If
-`~/.openviking/logs/server.log` shows `SSLCertVerificationError`, `self-signed certificate in certificate chain`, or
-`Failed to download local embedding model`, repair the OpenViking Python environment and start again:
-
-```bash
-threadnote repair --package-manager uv
-threadnote doctor --dry-run
-```
-
-Threadnote installs `pip-system-certs` into the OpenViking environment so Python `requests` can use certificates trusted
-by the operating system.
-
-If an older Threadnote release tries to reinstall all of OpenViking and fails while fetching packages such as `openai`,
-install the certificate bridge directly into the existing OpenViking environment:
-
-```bash
-uv pip install --system-certs --python "$(dirname "$(realpath "$(which openviking-server)")")/python" pip-system-certs
-threadnote start
-```
-
-## Local Embedding Extra Missing
-
-The default OpenViking config uses the local embedding backend. If the server log says `llama-cpp-python` is missing,
-rerun:
+## Home or migration problems
-```bash
-threadnote install
-```
-
-The installer repairs this by installing `openviking[local-embed]`.
-
-## Server Health Fails
-
-Current Threadnote installs start the local server by default. If `doctor` reports
-`WARN openviking health: connect ECONNREFUSED 127.0.0.1:1933`, the local server is not running. Start it and recheck:
-
-```bash
-threadnote start
-threadnote doctor --dry-run
-```
-
-Check whether the server is running:
-
-```bash
-curl http://127.0.0.1:1933/health
-```
-
-For detached starts, logs are written to:
-
-```text
-~/.openviking/logs/server.log
-```
+The owned home defaults to `~/.threadnote`. Check for an accidental override:
-If `start` reports that OpenViking did not become healthy, first check whether it finished shortly after the timeout:
-
-```bash
-threadnote doctor --dry-run
-```
-
-If it still is not healthy, open that log. Certificate failures during the first embedding model download are covered
-above.
-
-## Semantic Queue Stuck / Memory Writes Hang
-
-Symptom: agents hang or `remember`/`handoff` get very slow, and `~/.openviking/logs/server.log` repeats:
-
-```
-RuntimeError: Failed to list memory directory viking://user/.../memories/.../.md: Directory not found
-```
-
-A memory _file_ got enqueued for directory-level semantic processing; older OpenViking releases listed it as a
-directory, failed, and re-enqueued the message forever. The entry is AGFS-persisted, so it survives a server restart.
-Check the queue — a non-zero `Errors`/`Requeued` on the `Semantic` row is the signature:
-
-```bash
-ov observer queue
-```
-
-This is fixed upstream in OpenViking 0.4.5. Update Threadnote so it upgrades the pinned OpenViking install and restarts
-the server:
-
-```bash
-threadnote update
-```
-
-If Threadnote is already current but OpenViking is still older than 0.4.5, force a reinstall of the pinned OpenViking
-tool:
-
-```bash
-threadnote install --force
-```
-
-## Port Already In Use
-
-The default bind address is `127.0.0.1:1933`. This does not conflict with projects serving `localhost:80`,
-`localhost:443`, or custom hostnames from `/etc/hosts`; those are different host and port bindings.
-
-If another process already uses port `1933`, pick a different port:
-
-```bash
-THREADNOTE_PORT=1934 threadnote start
-THREADNOTE_PORT=1934 threadnote mcp-install codex --apply
-```
-
-Keep the same port in the agent MCP configuration and in future `threadnote` invocations.
-
-## Seed Skips Files
-
-Skipped files usually matched a secret detector after redaction. Inspect the file manually and either remove the risky
-content or leave it out of the manifest.
-
-## `seed-skills --native` Fails With `[INTERNAL]`
-
-Native OpenViking skill ingestion generates skill overviews with the configured VLM provider. If the server log shows an
-OpenAI quota, rate-limit, or authentication error, run `seed-skills` without `--native`. The default mode stores
-`SKILL.md` files as searchable resources and does not require native skill overview generation.
-
-After changing `~/.openviking/ov.conf`, restart the server:
-
-```bash
-threadnote stop
-threadnote start
-```
-
-## Local AI Recall Is Stopped or Unhealthy
-
-Inspect the persisted model path and loopback service:
-
-```bash
-threadnote local-ai status
-threadnote local-ai enable
-threadnote local-ai start
+```sh
+echo "$THREADNOTE_HOME"
threadnote doctor
+threadnote migrate
```
-Startup logs are written to `THREADNOTE_HOME/logs/local-ai.log`. If the model file is missing or fails verification,
-or the private access token is missing or has unsafe permissions, rerun `threadnote local-ai install --force`.
-`threadnote doctor` reports whether local AI is absent, disabled, healthy, or misconfigured. The generic
-`threadnote local-ai model switch` command recognizes verified models only; if none are available, it prints the
-installation command instead of opening an empty selector. A
-local-model failure does not block recall: Threadnote returns its deterministic result without query expansion or
-candidate post-filtering. Stop
-also refuses to signal a recorded PID when the authenticated endpoint cannot prove the same launch identity; inspect
-that process manually instead of deleting the safety check.
+`migrate` is a dry run unless `--apply` is present. It never deletes the legacy source. An interrupted copy can be
+resumed; a promoted target has a checksummed receipt. If the target is unrelated or free space is insufficient,
+migration stops before promotion.
-For an interrupted memory backfill, rerun:
+## Semantic recall is unavailable
-```bash
-threadnote enrich-memories --apply
-```
+Lexical recall remains available. Inspect the explicit model and index state:
-Memories that already contain generated `keywords:` headers are skipped. The command continues past individual model
-or write failures, reports them in its summary, and exits unsuccessfully so a post-update action is not silently marked
-complete. After reviewing enriched shared memories, run each printed `threadnote share sync --team ` command to
-commit and publish those team changes.
-
-## Claude MCP Fails While Health Is OK
-
-Threadnote uses its bundled stdio MCP adapter by default, even when the installed OpenViking server exposes native
-`/mcp`. The adapter adds Threadnote-specific tools and behavior such as shared-memory sync, exact recall fallback,
-seeded-resource recall augmentation, and recall-index repair.
-
-The adapter exposes eight tools by default: `recall_context`, `read_context`, `list_context`, `remember_context`,
-`review_session_context`, `apply_memory_candidates`, `share_publish`, and `threadnote_guide`. Install with
-`--toolset full` to also expose memory maintenance, advanced sharing/artifact tools, compatibility aliases, and raw
-OpenViking parity tools with `ov_*` names for native behaviors such as code symbol navigation, watch management, raw
-search/read/list/store/remember, grep/glob, resource import, and forget.
-
-Use the default stdio adapter:
-
-```bash
-threadnote mcp-install claude --apply
-claude mcp list
+```sh
+threadnote models list
+threadnote models runtime
+threadnote models verify
+threadnote index status
+threadnote index verify
```
-Changing toolsets rewrites the same MCP entry. For the complete surface, run
-`threadnote mcp-install claude --toolset full --apply`, then start a fresh agent session.
-
-`mcp-install claude` writes user-scoped Claude config by default. This is intentional: local-scoped config only applies
-to one repo/project, and the `threadnote` shim runs the implementation from the checkout that installed it.
+Install and select an embedding model before rebuilding. Model downloads require HTTPS access to the manifest’s pinned
+repository revision. A checksum mismatch deletes the invalid partial file and never activates it.
-Only use `--native-http` when you intentionally want the raw OpenViking HTTP endpoint instead of Threadnote's adapter.
+The runtime requests prebuilt `node-llama-cpp` binaries only. If `models runtime` reports that no compatible prebuilt
+binary exists, use a supported Node/platform combination; Threadnote will not silently compile one.
-## Worktree Was Deleted
+## An index rebuild was interrupted
-Memories live in `~/.openviking/data`, so deleting a branch or worktree does not delete stored memories. The launcher
-configuration can still point at scripts inside the deleted worktree, though.
+Re-run `threadnote index rebuild`. Checkpoints contain a checksum and reuse unchanged URI+fingerprint chunks from both
+the active generation and an interrupted staging generation. Activation occurs only after the complete sidecar and
+pointer are durably written.
-From any fresh checkout, run:
-
-```bash
-threadnote repair
+```sh
+threadnote index verify
+threadnote index rebuild
```
-`repair` reinstalls the `threadnote` shim, repairs generated config files, starts OpenViking if needed, repairs stale
-recall indexes, and rewrites Codex/Claude/Cursor/Copilot MCP configs to point at the current checkout.
-
-## MCP Install Is Only Printing Commands
-
-This is expected. Run with `--apply` after reviewing the command:
+## MCP does not appear in the agent
-```bash
+```sh
threadnote mcp-install codex --apply
+threadnote doctor
```
-For Cursor:
-
-```bash
-threadnote mcp-install cursor --apply
-```
-
-This updates the global `~/.cursor/mcp.json` file. Restart Cursor or open a fresh agent session after changing MCP
-config.
-
-For GitHub Copilot in VS Code:
-
-```bash
-threadnote mcp-install copilot --apply
-```
-
-This updates the VS Code user-profile `mcp.json` file. Restart VS Code or run `MCP: List Servers` from the Command
-Palette after changing MCP config. If VS Code uses a custom profile path, set `THREADNOTE_COPILOT_MCP_CONFIG` to that
-`mcp.json` path before running the command.
-
-## Cursor MCP Tool Says Query Is Missing
-
-If Cursor shows an error like `expected string, received undefined` for Threadnote `search`, the MCP server started but
-Cursor called the tool without JSON arguments. Prefer the Threadnote-named tool and pass a query explicitly:
-
-```json
-{"query": "current repo latest handoff"}
-```
-
-Current Threadnote adapters expose `recall_context` for this flow. Older adapters expose `search`; both require the same
-`query` argument. Run `threadnote repair` after upgrading if Cursor still lists only stale tools.
+Then start a fresh agent session. Replace `codex` with the relevant client. Threadnote supports local stdio MCP only;
+there is no HTTP endpoint, bearer token, host, or port to configure.
-## Uninstall Without Losing Memories
+## Recall quality changed
-Run:
+Run the frozen release gate before changing ranking weights, chunking, model manifests, or fixture judgments:
-```bash
-threadnote uninstall --dry-run
-threadnote uninstall
+```sh
+npm run eval:recall:v2 -- \
+ --baseline test/evaluation/baselines/threadnote-3.0.3/recall-v2-lexical.json \
+ --fail-on-regression --fail-on-contract
```
-By default, uninstall removes Threadnote-managed shims, MCP config, launchd config, and user instruction blocks while
-preserving `THREADNOTE_HOME`. To delete local OpenViking data too, pass `--erase-memories`.
+Inspect global and per-category deltas. Safety metrics and failure counts cannot regress.
diff --git a/docs/web-manager.png b/docs/web-manager.png
deleted file mode 100644
index 5561bb6b858cac0f18b1c86feb0a28a4c597eee3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 821172
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