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 logo -

- # Threadnote -[![npm version](https://img.shields.io/npm/v/threadnote.svg)](https://www.npmjs.com/package/threadnote) [![CI](https://img.shields.io/github/actions/workflow/status/Kashkovsky/threadnote/ci.yml?branch=main&label=CI)](https://github.com/Kashkovsky/threadnote/actions/workflows/ci.yml) [![npm downloads](https://img.shields.io/npm/dm/threadnote.svg)](https://www.npmjs.com/package/threadnote) [![license](https://img.shields.io/npm/l/threadnote.svg)](./LICENSE) ![node version](https://img.shields.io/node/v/threadnote.svg) - -> 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