{
  "markdown": "# Chamber\n\nAsk questions about your own notes. Get answers that cite their sources — and a\ndaily check that tells you when a source has changed underneath a conclusion you\nalready trusted.\n\nZero runtime dependencies. Everything is `node:sqlite` and files on your disk.\nNo account, no cloud call unless you point it at one.\n\n## See it in two minutes\n\nRequires Node **23.6+** — Chamber runs TypeScript directly, with no build step.\n\n```bash\ngit clone <this repo> chamber && cd chamber\nnpm ci && node --experimental-strip-types src/cli.ts try\n```\n\nNo config, no database, no model, no network. It builds a throwaway workspace,\nruns the real code paths against it, and deletes it (`--keep` to look around).\n\n![chamber try](https://raw.githubusercontent.com/abm9111/chamber/main/assets/chamber-try.gif)\n\nThat recording is scripted from [`assets/demo.tape`](assets/demo.tape) rather\nthan hand-captured, so it is regenerated when the output changes instead of\nquietly showing a version of Chamber that no longer exists. Everything below is\nthe same command's actual output, trimmed:\n\n```\n$ chamber believe belief \"Customers may return any purchase within 30 days of delivery.\"\n  committed blf_ddcf4f3c9b2e81b8\n  an unsourced assertion is not refused — it mints citation debt.\n\n$ chamber debts\n  dbt_18bd1c1171cdbfcb  [pending]\n\n$ chamber pay-debt\n  proposed 2 source(s), 2 pinned; best=0.694\n\n$ chamber verify\n  blf_ddcf4f3c9b2e81b8  2/2 pins verified\n```\n\nThat is the ordinary state: a belief standing on evidence that still holds. Then\nsomeone edits the note it was built on — `30 days` becomes `14 days`:\n\n```\n$ chamber ingest ./notes\n  ingested 2 file(s) as 4 passage(s)\n\n$ chamber verify\n  blf_ddcf4f3c9b2e81b8  1/2 pins verified\n    hash_mismatch: refunds.md#p0\n```\n\nNobody asked it to re-examine that belief. The conclusion did not change; the\nground under it did, and the exit code is non-zero, so a scheduled job can act\non it. That is the whole product.\n\nFour more scenarios — a rolled-back ledger caught by an outside anchor, a\nsandbox that refuses rather than degrade, a hostile tool catalogue rejected —\nare in [`demos/`](demos/), and run in CI so they cannot drift from the code.\n\n## A dictionary for the words above\n\nEverything Chamber does is rows in one SQLite file. Each term in the\ntranscripts names a table or a hash:\n\n| Word | What it actually is |\n|------|---------------------|\n| **passage** | one chunk of one markdown file. `refunds.md#p0` is file path + chunk index. |\n| **belief** | a row in `belief`: one asserted sentence, linked to the passages it stands on. |\n| **pin** | a sha-256 of a cited passage's stored title, body and ref, taken at the moment of citation and kept in `belief_source`. |\n| **verify** | re-read every pinned passage, recompute the hash, compare. Any mismatch exits non-zero. No model involved. |\n| **citation debt** | a row in `citation_debt`, created when an assertion commits with no source. The same claim cannot commit again until the debt is paid. |\n| **pay-debt** | retrieval proposes passages for the indebted claim; accepting them pins them. |\n| **APORIA** | the verdict when no retrieved passage supports an answer. The reply is \"I don't know\", recorded as that. |\n| **gate** | a check and a write inside one SQLite transaction — both commit or neither does. |\n| **audit log** | append-only `audit_event`; each row's hash covers the previous row's hash, so editing history breaks every hash after it. |\n| **anchor** | the log's root hash stored outside the database, so truncating the log is detectable rather than silent. |\n| **the scheduler** | a launchd/systemd job running `ingest` + `verify`, notifying only on drift. |\n\nNone of it is hidden machinery: `sqlite3 ~/.local/share/chamber/chamber.sqlite\n'.tables'` shows the whole thing.\n\n## Answers that cite their sources\n\nWith a model configured, `chamber ask` judges every sentence on its own\ncitations. Against the same two sample notes, on a local 30B:\n\n```\n$ chamber ask \"summarise our refund policy\"\n\nCustomers may return any purchase within 30 days of delivery [2]. Refunds\nare issued to the original payment method, usually within five working days\nof the returned item arriving at the warehouse [2]. However, perishable goods\nand personalised items cannot be returned once dispatched [1].\n\n  [ALLOWED] Customers may return any purchase within 30 days of delivery [2]. Refu\n     sources: refunds.md#p0 — refunds › Refund policy, refunds.md#p1 — refunds › Refund policy › Exceptions\n```\n\nThe model is shown `[1]`…`[k]` and never a document id or a hash, so it cannot\nfabricate a citation even in principle — the numbers are resolved back to files\nafter the answer is written. A sentence that cites nothing is marked\n`UNSUPPORTED`: recorded, but not treated as load-bearing.\n\nAsking something the corpus cannot answer is the more important case:\n\n```\n$ chamber ask \"what should a customer do if they want to return a perishable\n               item after the office has closed?\"\n\nI don't know\n\n  [APORIA] I don't know\n```\n\nBoth notes are in the index and both are relevant. Neither answers the\nquestion, so nothing is composed from the pieces.\n\n## Pointing it at your own notes\n\n```bash\nnpm link                 # puts `chamber` on your PATH\nchamber init             # writes ~/.config/chamber/config.json\n```\n\nThen edit that config to add a notes folder and a model:\n\n```json\n{\n  \"database\": \"~/.local/share/chamber/chamber.sqlite\",\n  \"model\": { \"base\": \"http://127.0.0.1:8087/v1\", \"name\": \"your-model\", \"mode\": \"openai\" },\n  \"ingest\": [{ \"root\": \"~/Notes\", \"exclude\": [\"transcripts\", \"attachments\"] }]\n}\n```\n\n`model.base` may name any OpenAI-compatible endpoint. A loopback address needs\nno API key; anything else reads `CHAMBER_API_KEY` from the environment, never\nfrom the file.\n\n```bash\nchamber ingest           # index every configured root\nchamber ask \"...\"        # ask, with citations\nchamber verify           # re-check stored pins against the corpus\nchamber corpus           # what is actually in the index\n```\n\n**Set your excludes before the first ingest.** There is no default exclude list.\nPointed at a folder of exported chat logs, Chamber will happily index all of\nthem and answer from them — see `chamber corpus` and\n[`docs/KNOWN_LIMITATIONS.md`](docs/KNOWN_LIMITATIONS.md) entry 11.\n\n### Use it as a CI drift gate\n\nThe same verify loop works on a repo: claims in docs pinned to passages of\ncode or policy, `chamber verify --json` failing the build when the ground\nmoves. One line in a workflow — this repo ships the action:\n\n```yaml\n- uses: abm9111/chamber@v0.1.5\n```\n\n[`docs/CI_DRIFT_GATE.md`](docs/CI_DRIFT_GATE.md) is the one-page recipe;\n[`demos/06_ci_drift_gate.ts`](demos/06_ci_drift_gate.ts) is the runnable\ntranscript.\n\n### Run it daily\n\n`deploy/launchd/com.chamber.verify.plist` (macOS) and `deploy/systemd/`\n(Linux) run ingest and verify on a schedule, and raise a notification only when\nsomething drifted. A check that correctly reports nothing on most days is a\ncheck you stop reading, so it stays quiet until it isn't.\n\n### Render it in Obsidian\n\nThe companion plugin [Chamber Drift](https://github.com/abm9111/chamber-obsidian)\nrenders `verify --json`'s report as a vault sidebar panel and a per-note\nbanner — nothing more. It never verifies and never writes; Chamber does both,\non its own schedule, outside Obsidian. Setup, including the report-writing\none-liner and the Obsidian Sync caveat: [`docs/OBSIDIAN.md`](docs/OBSIDIAN.md).\n\n### Use it from an AI coding agent\n\n`src/mcp_server.ts` exposes three tools over MCP — `chamber_ask`,\n`chamber_verify`, `chamber_corpus` — so a host like Claude Code can query your\ncorpus and see the per-claim citation verdicts rather than just the prose.\n\nFrom the npm package, the server is one subcommand:\n\n```bash\nclaude mcp add -s user chamber \\\n  -e CHAMBER_PYTHON=/path/to/python-with-onnxruntime \\\n  -- npx -y @bu7umaid/chamber mcp\n```\n\nThat form works when the host's spawn environment can resolve a Node 23.6+\n`npx`. When it cannot — and MCP hosts often spawn with a minimal `PATH` — name\nthe interpreters absolutely:\n\n```bash\nclaude mcp add -s user chamber \\\n  -e CHAMBER_PYTHON=/path/to/python-with-onnxruntime \\\n  -- /absolute/path/to/node --experimental-strip-types /path/to/chamber/src/mcp_server.ts\n```\n\nBoth absolute paths are deliberate. A spawned MCP server does not inherit your\ninteractive shell's `PATH`: `node` may resolve to a version below the 23.6\nfloor, and `python3` to one without `onnxruntime` — which makes the embedder\nfall back to non-semantic hash vectors and every question answer \"nothing in\nthe corpus matches.\" Naming the interpreters is the only reliable fix. See\n[`docs/KNOWN_LIMITATIONS.md`](docs/KNOWN_LIMITATIONS.md) entry 15.\n\nThe server resolves config once, on its first tool call, and pins it for the\nlife of the process — so **reconnect the server after editing config**. Editing\n`model.base` while a host held the process open produced `ECONNREFUSED` against\nthe *old* address while the CLI answered fine from the same file, which reads\nas a broken config rather than a stale daemon. The resolved database, mode and\nbase are printed to stderr on first use so the host's MCP log can settle it.\n\nNothing on that surface can activate a skill, approve a pending write, or\ningest — the gates exist so a *human* passes through them, and handing a model\nthe approval side would invert them rather than weaken them.\n\n`chamber_ask` is not read-only, and the write is not just bookkeeping: every\nclaim goes through the commit gate, so a claim with verified citations is\nrecorded as a **belief with its pins** — which is exactly what `chamber verify`\nlater re-checks for drift. Unsourced assertions mint citation debt; spend is\nrecorded. This is the same behaviour as `chamber ask` on the command line. The\nguarantee is that the gate is not bypassed, not that nothing is written.\n\n## What a verified citation does and does not prove\n\nChamber proves a cited passage **is the passage it claims to be** — unmodified,\nstill present, still saying what the citation says it says.\n\nIt cannot tell you the claim follows from the passage. A model can cite a real\nsource and misread it, and every layer here will pass it. That is a stated\nnon-goal, it has been observed happening, and it is not solved.\n\nRead [`docs/KNOWN_LIMITATIONS.md`](docs/KNOWN_LIMITATIONS.md) before trusting\nany output. Eighteen limitations are documented there, including the two least\nflattering. The sandbox confines only where bubblewrap works — Linux with\nunprivileged user namespaces — and refuses to run anything anywhere else, which\nis safe but is not the same as working. And citation debt blocks a verbatim\nrepeat reliably, while the paraphrase leg over it is a heuristic: calibration\nfound no cosine threshold that separates a restatement from a contradiction. A\nnumeric and negation check now removes the worst of that — an operator\ncorrecting an indebted claim is no longer refused for restating it — but two of\nfive true paraphrases still slip through, and a contradiction that is neither\nnumeric nor negated still reads as a repeat.\n\n## The invariant\n\n> No assertion may become executable, citable, or load-bearing except through a\n> gate whose check and write commit in one transaction — anything else may\n> decay, park, or be defeated, but it may never silently pass.\n\n| Gate | Blocks when |\n|------|-------------|\n| `commitBelief` | assertion with open blocking citation debt; missing or unverifiable pins; a defeater used as a source; a belief-typed commit on the fast path |\n| `tryActivateSkill` | open holds; load-bearing stale beliefs; content ≠ last critic-cleared hash; capability manifest over-ask |\n\nBoth gates write into a hash-chained audit log — `entry_hash = sha256(prev_hash\n|| canonical JSON)` with an incremental Merkle tree — so altering a past\ndecision breaks every hash after it. Retraction types (`defeater`, `unknown`)\ncommit freely and never mint blocking debt.\n\nDefaults are refusals: memory and skill writes require approval, learned skills\nland in quarantine rather than applying silently, and a pending write that\nexpires is **not** an approved one.\n\n## Development\n\n```bash\nnpm test        # 308 tests\nnpm run typecheck\nnpm run probes  # adversarial probes; each one asserts a defect is absent\n```\n\n`npm run probes` passes today, and that statement is dated the moment it is\nwritten — run it rather than trust it. Two of these probes (`sandbox_escape`,\n`debt_paraphrase`) spent weeks red against real, open defects before their\nfixes landed, and they are wired in as gates precisely because they can go red\nagain. A gate that cannot fail reports safety it never checked.\n\n## Layout\n\n```text\nsrc/ask.ts              retrieval → prompt → per-claim citation gate\nsrc/mcp_server.ts       the read side over MCP: ask, verify, corpus\nsrc/commit_belief.ts    the belief gate; check and write in one transaction\nsrc/pins.ts             content pins and drift verification\nsrc/audit.ts            hash-chained log + incremental Merkle\nsrc/config.ts           settings: flag → env → config file → default\nsrc/db.ts               opens the database, loads every schema\nprobes/                 adversarial probes, run by npm run probes\ndemos/                  the four scenarios above, run in CI so they cannot rot\ndocs/KNOWN_LIMITATIONS.md   what does not work, and what it costs\n```\n\nMIT.\n",
  "bytes": 13405,
  "sha": "76192933a70c227440bcea3dfb1b8e9121ea347982252142066afb99a980503b",
  "repo_slug": "abm9111/chamber",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_abm9111_chamber_123eab6d/readme"
}