{
  "markdown": "# witness — a distillation engine that keeps the history of how things changed.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![npm](https://img.shields.io/npm/v/@witness-ai/opencode?logo=npm&label=%40witness-ai%2Fopencode)](https://www.npmjs.com/package/@witness-ai/opencode)\n![Go](https://img.shields.io/badge/Go-1.25-00ADD8?logo=go&logoColor=white)\n![Single binary](https://img.shields.io/badge/single%20binary-CGO__ENABLED%3D0-informational)\n![Runtimes](https://img.shields.io/badge/runtimes-Claude%20Code%20%C2%B7%20OpenCode-8A2BE2)\n\n**witness turns a stream of text into a dated record of how its subject changed.** Feed it your AI\ncoding sessions and it tracks how *you* think and work. Feed it market commentary, research notes, or\nany document stream and it tracks how *that* changed instead. One pure-Go binary, a local SQLite\narchive plus plain markdown, served to your agent over MCP.\n\nThe thing it is built around is **change over time, with provenance**. When an attribute's value\nchanges, witness does not overwrite it — it closes the old value with a date and opens a new one, and\nrecords which source records drove the shift. So the archive answers *\"how did this get here\"*, not\njust *\"what is true now\"*.\n\n> *\"Aah, you were at my side, all along.*\n> *My true mentor...*\n> *My guiding moonlight...\"*\n> — Ludwig, the Holy Blade\n\n## Two ways people use it\n\n**1. Watch your own growth as you work with AI.** Install it into Claude Code or OpenCode and it\ncaptures your sessions in the background — no prompting, no ritual. It distills how your thinking,\nworkstyle, habits, and blind spots shift over months, with evidence for every claim. This is the\nsetup witness ships configured out of the box.\n\nIt is **reflection-oriented, not clone-oriented**: the point is to let your agent understand you, and\nto leave you a re-readable record of how you thought and grew. It is a **pure tool, not a coach** —\nit captures, structures, and serves. Building a coach on top (\"you've done this three times…\") is\nleft to other projects that read its output.\n\n**2. Build a change-history over any text corpus.** Write a lens — a prompt describing what to notice\nand which dimensions to track — point `witness ingest` at NDJSON records, and the same four-stage\nengine runs. Nothing about the machinery is person-specific: raw records → observations → dated\nfacets → narrative.\n\nA worked example, run end to end while writing this section. A `regime` lens over market commentary,\nin an archive with **no** person lens enabled at all: 7 news records became 22 observations and 13\nfacets. Then the regime flipped hawkish→dovish, and the archive recorded it as history rather than\nreplacing it:\n\n```\ninflation/core_trend\n  \"Disinflation has broken rather than paused: core CPI reaccelerating…\"   valid_to 2026-06-…\n  \"Core is disinflating persistently rather than reaccelerating…\"          (current)\n```\n\nFive facets closed and reopened that way in one review. The generated brief even flagged that its own\nearlier sequencing thesis had been **falsified** by the new data — which is the whole point of keeping\nthe history instead of the latest snapshot.\n\n**Honest caveat:** the engine is general, but everything witness *ships* is person-shaped — the\nbuilt-in `default` lens, the example lens, and the summary prompts all say \"notice things about the\nperson\". For a non-person corpus you write your own lens (three files) and, if you want, override the\nsummary prompt with one file. There is no market-lens or research-lens preset yet; the machinery is\ndomain-agnostic, the batteries included are not.\n\n## Is this you?\n\nwitness is the answer if you've ever wanted to:\n\n- **Remember what you learned across your AI coding sessions** — Claude Code, OpenCode — instead of losing it when the context window resets.\n- Have a **second brain / AI memory** for your thinking, workstyle, and habits, queryable by your agent.\n- **Track how you grow as a developer over time** — the traps you fall into and climb out of — with provenance for every recorded change.\n- Keep a **journal / retrospective** of how you thought and changed, re-readable months later.\n- **Distill a corpus you care about** — market news, a knowledge base, research notes, meeting logs — into a dated history of how its state changed, not a pile of summaries. Feed it NDJSON with `witness ingest`.\n- Query any of the above **from your agent**, over MCP, instead of re-explaining context every session.\n\n**Contents:** [How it works](#how-it-works) · [Lenses](#lenses) · [Example](#example-one-moment-end-to-end) · [Reading the archive](#reading-the-archive) · [Commands](#commands) · [Install](#install) · [Configuration](#configuration) · [Your data](#your-data-is-yours)\n\n## How it works\n\nFour layers — one ground-truth, three derived and **regenerable** from it:\n\n| Layer | Kind | What it is |\n|-------|------|------------|\n| **raw (L0)** | ground truth | Every turn captured verbatim — from stable Claude Code hook fields (`UserPromptSubmit.prompt`, `Stop.last_assistant_message`) or OpenCode's local SQLite session DB (`message`/`part` text). Append-only, never LLM-touched. |\n| **observations (L1)** | derived | A cheap per-session worker mines atomic, evidence-anchored observations about *you*, tagged by lens. Append-only. |\n| **facets (L2)** | derived, bi-temporal | A periodic reviewer synthesizes observations into evolving *facets*, each keeping its **change history** (`valid_from`/`valid_to`) — so the archive answers \"how did I change,\" not just \"who am I now.\" Old values are never deleted. |\n| **profile (L4)** | derived narrative | A short, human-readable markdown summary distilled from the facets — one per lens plus a cross-lens `unified` portrait. Generated **on read**: `witness profile` (or MCP `get_profile`) rebuilds it only if the facets changed, so an unread profile costs nothing. A cached read is instant; a rebuild takes ~13s. |\n\nThe archive is **collect-only / pull-only**: witness captures and distills everywhere, but never\ninjects anything into a session. Nothing is pushed — you (or an agent) read the profile on demand.\nraw/observations/facets live in a single embedded SQLite database (`witness.db`); the profile is\nplain markdown under `profile/`.\n\n### Lenses\n\nEvery observation/facet carries a **lens** tag:\n\n- **`default`** — global, runs on every session, cross-domain. This is the part no single-domain\n  tracker can be: it sees that \"diagnoses gaps precisely\" fires in math *and* coding *and* career.\n- **registered lenses** (e.g. `math`) — domain-specific lenses you **register once** and **enable\n  globally**. `witness lens register math ./math/` adds the definition (a directory) to a central\n  registry; `witness lens enable math` makes it run on every session (alongside `default`). Lenses\n  are shared, not tied to any repo, so the same `math` lens covers all your math work.\n\n#### Writing a lens\n\nA lens is a **directory** of three files:\n\n```\nmath/\n  lens.json     settings: name, dimensions, optional per-lens models\n  extract.md    per-session — mines observations (the whole file is the prompt)\n  review.md     periodic — synthesizes observations into facets (the whole file is the prompt)\n```\n\n```json\n// math/lens.json\n{ \"name\": \"math\", \"dimensions\": [\"speed\", \"independence\", \"proof_rigor\", \"abstraction\", \"confusion_tolerance\"] }\n```\n\n```markdown\n<!-- math/extract.md -->\nYou are observing one session through a MATH-LEARNING lens. Notice things about the\nperson as a mathematician — how they reason, get stuck, and climb out…\nReturn ONLY a JSON array. Each element:\n[{ \"dimension\": \"proof_rigor\", \"observation\": \"…\", \"evidence\": \"…\", \"poignancy\": 6 }]\n```\n\nThe one rule to remember: each prompt file is used **verbatim as the system prompt** and *replaces*\nthe built-in `default` prompts — it doesn't extend them — so each must be **self-contained,\nincluding its output JSON schema** (the tool appends the transcript / observations as the user\nmessage, but injects no schema for you).\n\nTwo **complete, copy-paste-ready** lenses ship as starting points. Copy the directory and rewrite the\ndimensions and prose for your domain:\n\n- [`prompts/lens/example/`](prompts/lens/example) — a **person** lens (math learning), for tracking\n  someone as they work.\n- [`prompts/lens/corpus-example/`](prompts/lens/corpus-example) — a **non-person** lens (market\n  regime), for tracking a subject that is not you. Start here for research notes, a knowledge base,\n  incident reports, or any document stream; its README explains what has to change when the subject\n  stops being a person.\n\n```sh\ncp -R \"$CLAUDE_PLUGIN_ROOT/prompts/lens/example\" ./math   # edit the files, then:\nwitness lens register math ./math      # copies the definition into your store (a snapshot)\nwitness lens enable  math               # start running it on every session\n```\n\n`register` stores a **copy** — editing the original afterward has no effect until you re-register.\n`enable` is the separate switch that makes it actually run.\n\n**Per-lens models (optional).** By default every lens rides the default models (`witness config set\ntriage_model / distill_model`). A rare heavy lens can pin a stronger model just for itself —\nwithout paying for it on every session — by adding `extract_model` / `review_model` to its\n`lens.json`:\n\n```json\n{ \"name\": \"math\", \"dimensions\": [\"proof_rigor\"], \"extract_model\": \"claude-sonnet-5\" }\n```\n\nOmit a field (or leave it empty) to ride the default. Since `register` stores a snapshot, edit the\nregistered copy under `<witness-data-dir>/lenses/<name>/lens.json` — or edit the source and\nre-register. Verify what a lens resolved to with `witness lens show <name>`.\n\nThe source directory may live anywhere. As a recommended canonical location, witness keeps the\nregistered copy beside `config.toml` under `<witness-data-dir>/lenses/<name>/` (normally\n`~/.local/share/witness/lenses/<name>/`, or `$WITNESS_HOME/lenses/<name>/`). You can edit that\nregistered copy directly, but this location is a convention rather than a restriction on the\ndirectory passed to `lens register`.\n\n## Example: one moment, end to end\n\nSay a session contains this exchange (fictional):\n\n> **you:** the migration keeps failing on prod but passes locally — I'll just run it by hand and move on\n>\n> **you:** …wait, what's actually *different* about prod? let me diff the two schemas before I touch anything\n\nHere's what each layer makes of it.\n\n**raw (L0)** — captured verbatim, nothing interpreted:\n\n```\nuser  the migration keeps failing on prod but passes locally — I'll just run it by hand and move on\nuser  wait, what's actually different about prod? let me diff the two schemas before I touch anything\n```\n\n**observations (L1)** — the worker mines one atomic, evidence-anchored noticing:\n\n```\n[thinking] Caught the urge to hand-patch around a failure and redirected to isolating the\n           prod/local difference before acting.\n  evidence: \"run it by hand and move on\" → \"what's different about prod? diff before I touch anything\"\n  poignancy: 6    lens: default\n```\n\n**facets (L2)** — after several such moments the reviewer synthesizes an evolving attribute, and\n**keeps the history** (the whole point — it shows *change*, not just current state):\n\n```\ndefault · thinking · diagnoses_before_acting                        confidence 0.82\n  2026-05 → now       Catches the reflex to work around a failure and isolates the\n                      mechanism first; gates action on understanding the cause.\n  2026-02 → 2026-05   Tended to apply the first workaround that unblocked the task.   (superseded)\n```\n\n**profile (L4)** — the narrative you actually read (`witness profile`):\n\n> ## default\n>\n> You've been converging on a diagnose-first way of working. A few months ago the pattern was to\n> reach for whatever unblocked the task; now you routinely catch that urge and turn to isolating the\n> mechanism before you touch anything…\n\nNothing here is pushed into your sessions — you read it when you want it (`witness profile`), or an\nagent pulls the relevant facet on demand.\n\n### Writing your own summary prompt\n\nThe shipped `unified` prompt writes a **personal growth portrait**. If your archive is something else\n— market records, research notes, a project log — drop your own prompt in and it wins:\n\n```\n<data-root>/summarize/unified.md    your prompt (overrides the built-in one)\n<data-root>/summarize/lens.md       same, for the per-lens summaries\n```\n\n`witness doctor` prints the data root. The file is the whole interface: no command to run, no\nregistration step. Delete it to go back to the built-in prompt.\n\nThe same facets can then produce a completely different document — a terse risk memo instead of a\ncharacter portrait, say. Because the built-in prompt is still used when you have no override, a\nwitness upgrade that ships an improved default still reaches you; once you override, your file is\nnever touched by an upgrade.\n\n## Reading the archive\n\nHumans read the **narrative**; agents read the **structured** data. Over MCP:\n\n- `get_profile(lens)` — the narrative profile (prose); omit `lens` for the unified portrait.\n- `get_facets(lens)` — the current structured facets.\n- `search_observations(query, lens)` — local vector search over observations.\n- `record_observation(...)` — an in-session agent writes a decision-aware observation directly\n  (passed through verbatim), capturing context a later reviewer would miss.\n- `delete_observation(obs_id)` — prune a wrong observation.\n\n## Commands\n\nThe visible front door, exactly as `witness --help` groups it:\n\n| group | commands |\n|---|---|\n| Read your archive | `status` · `profile` · `facets` · `observations` · `ingest` |\n| Lenses | `lens` |\n| Configure | `config` |\n| Setup | `doctor` · `install` · `wire` · `unwire` |\n| Maintenance | `cleanup` · `export` |\n\nCapture, the worker, and the MCP server are **internal** entry points invoked by\nClaude Code/OpenCode, not typed by hand. `witness import` and `witness worker …` are hidden:\nthey exist for recovery and debugging, and everything they do also happens automatically.\n\n- `witness profile [lens]` — print the narrative profile (default: the unified portrait).\n- `witness facets [lens]` — print current structured facets (CLI equivalent of MCP `get_facets`).\n- `witness observations search <query> [--lens <lens>] [-k N]` — semantic search over observations.\n- `witness observations record --session <id> --dimension <name> --observation <text>` — stage an active observation and kick the worker.\n- `witness observations delete <obs_id>` — prune a wrong observation.\n- `witness worker review [--full]` — force an L2 review and regenerate L4 profiles from existing\n  observations. (Hidden, like the rest of `worker`: review normally runs on its own schedule —\n  `review_every` / `review_poignancy` — so reaching for this by hand is the exception. `--full`\n  also runs the emergent long-arc pass.)\n- `witness lens register|enable|disable|list` — manage lenses.\n- `witness lens backfill <name> [--fresh]` — re-mine one lens over the whole history and refresh its\n  facets; `--fresh` first drops the lens's observations + facets (for a changed prompt).\n- `witness lens load-default` — re-seed / restore the built-in \"default\" person-growth lens (it is\n  auto-seeded once on first use and fully deletable, so this is how you bring it back).\n- `witness import --agent opencode` — incrementally reconcile OpenCode's local session DB into L0\n  and kick background distillation without waiting.\n- `witness import --agent claude` — kick distillation for already-captured Claude Code hook data.\n- `witness status` — what has been captured, whether the worker is running, and how fresh the\n  distilled data is (`--json` for scripts).\n- `witness worker run` / `witness worker stop` — the operator escape hatch for the background\n  distillation worker (hidden from `--help`; normally it runs off editor hooks). `run` drains in\n  the foreground, `--detach` backgrounds it. It accepts `--since`/`--until` to select pending\n  sessions by their latest raw timestamp — for example `witness worker run --since 7d` distills\n  sessions updated in the last seven days. Bounds also accept RFC3339 timestamps or UTC dates\n  (`YYYY-MM-DD`) and do not discard sessions outside the selected range.\n- `witness cleanup` — interactively reclaim old raw transcripts (keeps observations + profile).\n- `witness export <path>` — write a consistent single-file snapshot of the **database** (safe to back\n  up / cloud-sync). Add `--all` and `<path>` becomes a **directory** holding the complete archive:\n  that snapshot plus `config.toml`, `lenses/` and `profile/`. Use `--all` for backups — see\n  [Your data is yours](#your-data-is-yours).\n- `witness install [--path <dir>]` — provision a new witness archive at the specified path (or the\n  default data root). Creates the directory structure and database schema. Typically called once per\n  machine; `install.sh` handles this for source-checkout users.\n- `witness wire <claude|opencode>` — wire the editor integration (hooks + MCP for Claude Code;\n  plugin + MCP for OpenCode). Source-checkout command; `install.sh` calls this for you.\n- `witness unwire <claude|opencode>` — remove the editor integration wiring. Your archive is untouched.\n- `witness ingest [--file <path>]` — accept structured records (notes, logs, market data) as NDJSON\n  and distill them into the archive. Reads from stdin or a file. See the record contract in\n  [`prompts/SCHEMA.md`](prompts/SCHEMA.md#records-in-the-ingest-contract).\n- `witness doctor` — health check (verifies the embedder runs and EN/ZH retrieval works).\n\n## Single binary, no runtime\n\nThe whole thing is **one self-contained Go binary** — no Python, no external services, no vector\nDB, no cloud key. Local multilingual (English **and** Chinese) embeddings run pure-Go via GoMLX\n(`CGO_ENABLED=0`, verified: matches ONNX Runtime exactly). Distillation defaults to your existing\nClaude Code auth via `claude -p`; set `runner = opencode` to use a private `opencode serve` runner instead.\n\n## Install\n\n```sh\n./install.sh claude    # Claude Code: build, fetch model (~448MB once), wire hooks + MCP\n./install.sh opencode  # OpenCode: build, fetch model, wire local plugin + MCP\n```\n\nThat's the whole thing — idempotent, safe to re-run after a `git pull`. The target\nis required: the script builds the binary, provisions the archive, and binds the matching\ndistillation runtime into `config.toml` (`runner = claude` or `runner = opencode`). It also\noffers to add a `witness` command to your PATH (for `witness profile`, `doctor`, `lens`,\n`import`, `status`, `cleanup`, `ingest`). Equivalent `make` targets exist (`make install`,\n`make install-opencode`, `make build`, `make doctor`, `make uninstall`,\n`make uninstall-opencode`, `make clean`). To remove the editor integration: `make uninstall`\nor `make uninstall-opencode` (strips wiring; your data is untouched).\n\n### Windows\n\nWindows uses a self-contained zip instead of the shell installer (there is no\nguaranteed shell to run the hook shim). Download `witness-windows-amd64.zip`\n(Intel/AMD) or `witness-windows-arm64.zip` from the releases page — each unpacks\nto a `witness\\` folder holding `witness.exe` and the embedding model. Then, from\ninside that folder in PowerShell:\n\n```powershell\n.\\witness.exe wire claude      # Claude Code\n.\\witness.exe wire opencode    # OpenCode\n```\n\nEither command copies the bundle into `%LOCALAPPDATA%\\witness`, adds it to your user\nPATH, and provisions the archive. The zip carries the prompt templates and the ~448MB\nmodel alongside the exe; the binary resolves both relative to itself. Running both is\nfine — the copy is idempotent, and they wire different editors.\n\nWhat each wires, and why they differ: Claude Code spawns the hook itself, so `wire\nclaude` writes **exec-form hooks** (`{command: witness.exe, args: [...]}`) into\n`settings.json`. OpenCode is the other way round — its *plugin* spawns witness, so\n`wire opencode` bakes the installed `witness.exe` path into the plugin and the MCP\nentry. Neither needs a shell or Git Bash.\n\nTo remove an editor integration: `witness.exe unwire claude` / `unwire opencode`\n(strips the hooks/plugin + MCP entry). The copied files and PATH entry are left in\nplace, since the other integration may still be using them.\n\n### OpenCode support\n\nOpenCode support has two pieces:\n\n- A plugin reconciles OpenCode's SQLite DB on startup and when a session goes idle, then asks the\n  laptop-friendly auto-start gate to distill when allowed. From-source installs write a\n  local plugin to `~/.config/opencode/plugins/witness.js`; published installs can use the npm plugin\n  `@witness-ai/opencode`. (That path is the same on Windows — OpenCode resolves its config as\n  `XDG_CONFIG_HOME` else `~/.config`, with no Windows-specific branch, so the plugin lands in\n  `%USERPROFILE%\\.config\\opencode\\plugins\\`.)\n- An OpenCode MCP entry named `witness` launches the same MCP server as Claude Code, exposing\n  `get_profile`, `get_facets`, `search_observations`, `record_observation`, and\n  `delete_observation`.\n\nThe npm package ships the OpenCode plugin, a `witness` CLI shim, prebuilt witness binaries, and prompts.\nThe config-only path is the default: add the plugin to `~/.config/opencode/opencode.json`, and OpenCode\ninstalls it automatically with Bun on startup. If `mcp.witness` is absent, the plugin auto-registers it\nfor you.\n\nThe npm package supports OpenCode integration and general CLI usage (`witness profile`, `ingest`, etc.).\nEditor wiring (`witness wire` / `unwire`) is a source-checkout command — npm users configure the plugin\ndirectly in `opencode.json` and the plugin auto-registers MCP. Archive provisioning and record ingestion\n(`witness install`, `witness ingest`) work normally with the npm package.\n\nThe npm distribution supports exactly these platforms:\n\n| Operating system | Architecture | npm platform package |\n| --- | --- | --- |\n| macOS | Apple Silicon (`darwin/arm64`) | `@witness-ai/opencode-darwin-arm64` |\n| Linux | x86-64 (`linux/x64`) | `@witness-ai/opencode-linux-x64` |\n\nmacOS Intel, Linux ARM, and Windows are not supported by the npm distribution. Each binary is\npublished as an optional platform package, so npm installs only the binary for the current machine.\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"plugin\": [\"@witness-ai/opencode\"]\n}\n```\n\nTo test the current prerelease without replacing `latest`, pin the plugin entry:\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"plugin\": [\"@witness-ai/opencode@beta\"]\n}\n```\n\nOptional: install it globally if you also want a `witness` command on your shell `PATH`:\n\n```sh\nnpm install -g @witness-ai/opencode\n```\n\nOptional: run it ad hoc without a global install:\n\n```sh\nnpm exec --yes --package=@witness-ai/opencode -- witness doctor\n```\n\nThe main npm package contains the plugin, CLI wrapper, and prompts; the matching optional platform package\ncontains one binary. The first embedding-model download is about 470MB. Installing the packages does not\nstart that download: the plugin starts it when OpenCode next runs. Keep OpenCode running until the first\ndownload finishes. The plugin owns the downloader, stops it on shutdown, and retries later with bounded\nbackoff.\nIf you already have your own `mcp.witness` config, the plugin leaves it untouched. The npm wrapper does\nnot support `witness wire` / `witness unwire`; those commands are for source-checkout editor wiring.\nCustom model mirrors must provide `WITNESS_MODEL_SHA256` and `WITNESS_TOKENIZER_SHA256` alongside\n`WITNESS_MODEL_BASE_URL`.\n\nAfter the first download, verify the model, OpenCode runner, archive, and queue:\n\n```sh\nnpm exec --yes --package=@witness-ai/opencode@beta -- witness doctor\nnpm exec --yes --package=@witness-ai/opencode@beta -- witness status\n```\n\n> **Upgrade note:** Older witness releases could leave an OpenCode session whose agent or title is\n> `witness-distill` when distillation was interrupted. Current releases no longer create or filter\n> those sessions in OpenCode's user database. Before the first import after upgrading, remove any\n> leftover `witness-distill` sessions with OpenCode's supported session-management tools so they are\n> not captured as normal archive data.\n\nThe npm package lives in [`npm/opencode`](npm/opencode). Stage prebuilt binaries and prompts before publishing:\n\n```sh\nmake npm-opencode-package\nnpm publish ./npm/platform/darwin-arm64 --access public --tag beta\nnpm publish ./npm/platform/linux-x64 --access public --tag beta\n(cd npm/opencode && npm publish --access public --ignore-scripts --tag beta)\n```\n\nConfigure npm Trusted Publishing separately for the main package and both platform packages before using\nthe release workflow. The npm package page renders [`npm/opencode/README.md`](npm/opencode/README.md);\nthe workflow verifies after publishing that npm identifies it as the package README and that the published\ntarball contains it.\n\nFor the first platform-package release only, publish both platform packages manually before creating the\nGitHub Release, then configure their Trusted Publishers on npm. npm requires a package to exist before its\npackage-level Trusted Publisher can be configured. The release workflow is idempotent: it skips an already\npublished package version, publishes any missing platform versions first, then publishes the main package.\n\nManual verification path:\n\n```sh\nwitness lens register math prompts/lens/example   # optional: register an extra lens\nwitness lens enable math\nwitness import --agent opencode    # reconciles ~/.local/share/opencode/opencode.db and returns\nwitness status                    # watch non-blocking distillation progress\nwitness worker review              # forces L2 facets + L4 markdown profiles (hidden cmd)\nwitness profile opencode           # per-lens L4 report\nwitness profile                    # unified L4 report\n```\n\n## Configuration\n\n`~/.local/share/witness/config.toml` (all optional; sensible defaults):\n\n```toml\nrunner           = \"claude\"   # \"claude\" (default) or \"opencode\"\ntriage_model     = \"\"         # MINING (L0 -> L1), once per session; \"\" = the runner's default\ndistill_model    = \"\"         # REVIEW (L1 -> L2/L4), batched; \"\" falls back to triage_model\nreview_every     = 5          # run the reviewer every N distilled sessions...\nreview_poignancy = 30         # ...or sooner once accumulated salience crosses this (0 = off)\nauto_distill     = true       # hooks/plugins may start model work automatically\nmine_concurrency = 4          # parallel per-session miners (<= 0 restores the default)\n```\n\nModel ids are passed through to whichever runner is bound, so use that runtime's spelling —\n`claude-haiku-4-5` or `claude-sonnet-5` for `runner = \"claude\"`, `openai/gpt-5.5`-style ids for\n`runner = \"opencode\"`. Leaving both empty is the safe default and what `witness install` writes;\nnote that an empty `triage_model` under the claude runner inherits your *ambient interactive*\nmodel, which may be heavier than you want for per-session mining, so pinning a light one is\nusually worth it.\n\nSet `auto_distill = false` for capture-only mode on battery-constrained machines, then run\n`witness worker run --detach` manually when plugged in. Automatic workers are short-lived: they load the\nembed model only while draining queued sessions, then exit.\n\nWhen `runner = opencode`, `triage_model` and `distill_model` should use OpenCode model names such\nas `openai/gpt-5.5`; empty values use your OpenCode defaults. Non-empty OpenCode model names are\nvalidated against `opencode models <provider>` before distillation, and `witness doctor` reports the\nsame check as `opencode models: OK` or an explicit invalid-model error.\n\nEnabled lenses are managed for you (`witness lens enable/disable <name>`) and appear as simple\nlines, each naming a registered lens that runs on every session:\n\n```toml\nlens = math\n```\n\nThere is no automatic retention knob: raw transcripts are kept until you deliberately reclaim\nthem with `witness cleanup` (which never touches your observations or profile).\n\n## Your data is yours\n\nEverything lives under `~/.local/share/witness/` (override with `WITNESS_HOME`; installs\npredating the rename keep using `~/.local/share/claude-witness/`, adopted automatically), is `0700`\n(the DB and profile files `0600`), and never leaves your machine. The repo ships the framework,\nschema, and prompts — **never anyone's archive.**\n\n**Troubleshooting.** Distillation runs in a detached worker, so failures land in\n`$WITNESS_HOME/witness.log` (JSON lines) rather than on your terminal. It records the worker's\nstartup steps, each mine's input size and duration, and each model call's outcome — enough to tell a\nslow model from a stalled one. For more detail, set `WITNESS_LOG_LEVEL=debug` (also accepts `warn` /\n`error`; anything unrecognized falls back to the `info` default, so a typo can never stop witness\ncapturing).\n\n**Backup / sync.** Use `witness export --all <dir>` — it writes a **complete** archive you can point\na syncer at. Do **not** sync the live data directory directly: the database runs in WAL mode (`.db` +\n`-wal` + `-shm`), and a syncer racing those files can corrupt it. Wire it up yourself, e.g. a\ncron/launchd job:\n\n```sh\nwitness export --all ~/Dropbox/witness-backup --force   # complete archive, safe to sync\n```\n\n`--all` matters because only one of the four things in your data directory is the database:\n\n| | in `--all` | in a plain `export` | recoverable without a backup? |\n|---|---|---|---|\n| `witness.db` — raw turns, observations, facets | yes | yes | no |\n| `config.toml` — enabled lenses, runner, models | yes | **no** | reconfigure by hand; until then distillation is silently off |\n| `lenses/` — your lens definitions | yes | **no** | **no** — prompt text is in no database. A bundled lens re-seeds; one you wrote is gone |\n| `profile/` — the narrative | yes | **no** | rebuilt by a review, *except* a hand-edited `unified.md` |\n\nLeft out on purpose: `witness.log` (diagnostics), `runtime/` (OpenCode's disposable private runtime),\nand the lock files. Either form runs safely while the worker is writing — no need to stop it.\n\n**Restoring needs no separate command.** An `--all` directory has the same layout as your data\ndirectory, so either point witness at it in place (`WITNESS_HOME=~/Dropbox/witness-backup`) or, with\nwitness stopped, copy its contents into your data dir. A plain database-only snapshot restores the\nsame way as `witness.db`, and `witness worker review` rebuilds the narrative from it.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 30636,
  "sha": "8773573d082eb3e0731481425e723c1ae5dcbbd6dda19ff73178c16ab121dda0",
  "repo_slug": "ingtian/witness",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_ingtian_witness_7cd87292/readme"
}