[Skip to content](https://github.com/garrytan/gbrain#start-of-content)

You signed in with another tab or window. [Reload](https://github.com/garrytan/gbrain) to refresh your session.You signed out in another tab or window. [Reload](https://github.com/garrytan/gbrain) to refresh your session.You switched accounts on another tab or window. [Reload](https://github.com/garrytan/gbrain) to refresh your session.Dismiss alert

{{ message }}

[garrytan](https://github.com/garrytan)/ **[gbrain](https://github.com/garrytan/gbrain)** Public

- [Notifications](https://github.com/login?return_to=%2Fgarrytan%2Fgbrain) You must be signed in to change notification settings
- [Fork\\
4.1k](https://github.com/login?return_to=%2Fgarrytan%2Fgbrain)
- [Star\\
27.8k](https://github.com/login?return_to=%2Fgarrytan%2Fgbrain)


master

[**573** Branches](https://github.com/garrytan/gbrain/branches) [**9** Tags](https://github.com/garrytan/gbrain/tags)

[Go to Branches page](https://github.com/garrytan/gbrain/branches)[Go to Tags page](https://github.com/garrytan/gbrain/tags)

Go to file

Code

Open more actions menu

## Folders and files

| Name | Name | Last commit message | Last commit date |
| --- | --- | --- | --- |
| ## Latest commit<br>![time-attack](https://avatars.githubusercontent.com/u/89218912?v=4&size=40)![garrytan](https://avatars.githubusercontent.com/u/19957?v=4&size=40)![claude](https://avatars.githubusercontent.com/u/81847?v=4&size=40)<br>3 people<br>[v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b)<br>Open commit detailssuccess<br>yesterdayAug 4, 2026<br>[15b9863](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b) · yesterdayAug 4, 2026<br>## History<br>[863 Commits](https://github.com/garrytan/gbrain/commits/master/) <br>Open commit details<br>[View commit history for this file.](https://github.com/garrytan/gbrain/commits/master/) 863 Commits |
| [.github](https://github.com/garrytan/gbrain/tree/master/.github ".github") | [.github](https://github.com/garrytan/gbrain/tree/master/.github ".github") | [v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1…](https://github.com/garrytan/gbrain/commit/aecb33e795cc4806f760446c55ab1c350194ddc8 "v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1 contribution requirements (#3805)  * v0.42.73.1 revert(ci): remove the PR gate — it cannot function on this repository  The gate needs an ANTHROPIC_API_KEY Actions secret for its verdict and read-write workflow permissions to post a comment or set a label. This repository grants neither, and both are admin-only, so it can only ever skip.  It also caused a real incident before that was understood: a read-only token turned every API call into a 403, the code treated that as a crash, and the check went red on an outside contributor's PR four times with no comment explaining why. v0.42.73.0 fixed the crash, but a check that runs on every PR and can never reach a verdict does not earn a place in the repo.  Removes the workflow, the script, its type surface, and its test file. The code is preserved in git history at v0.42.73.0. If it is ever restored, the mechanical half (intent/screenshot policy, title rule, red flags) should render to the Actions job summary rather than a comment — that needs no token permission and no API key.  CONTRIBUTING.md's intent-paragraph and screenshot requirement is unchanged and stands as written; it is enforced by maintainers reading PRs, as before.  typecheck clean, verify 34/34, llms bundles regenerated.  * v0.42.73.1 revert(docs): withdraw the human-intent-paragraph + screenshot contribution requirement  This reverts commit 6d1232d5a67c9ab7086d00aaa4d362324919fb3d (v0.42.72.1).  CONTRIBUTING.md, both issue templates, and the pull-request template return to their pre-2026-08-02 state. VERSION/package.json/CHANGELOG keep moving forward (0.42.73.1); the v0.42.72.1 CHANGELOG entry stays as historical record, and the 0.42.73.1 entry now describes the withdrawal.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [admin](https://github.com/garrytan/gbrain/tree/master/admin "admin") | [admin](https://github.com/garrytan/gbrain/tree/master/admin "admin") | [fix(deps): bump four override pins past newly-published CVEs](https://github.com/garrytan/gbrain/commit/e5dee4fb78481f0fb7c78016fc7e450bef252caa "fix(deps): bump four override pins past newly-published CVEs  osv-scan passed on the last two release PRs and fails on this one, so these are newly published, not tolerated debt: 2 High + 4 Medium across fast-uri (3.1.4 → 3.1.5), hono (4.12.25 → 4.12.34), ip-address (10.1.1 → 10.3.1), and admin's postcss (8.5.10 → 8.5.23). All four are transitive and pinned through the overrides block, so the fix is the pin, not a dependency addition.  Resolved: fast-uri@3.1.5, hono@4.13.0, ip-address@10.4.0, postcss@8.5.25.") | 2 days agoAug 4, 2026 |
| [docs](https://github.com/garrytan/gbrain/tree/master/docs "docs") | [docs](https://github.com/garrytan/gbrain/tree/master/docs "docs") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [evals](https://github.com/garrytan/gbrain/tree/master/evals "evals") | [evals](https://github.com/garrytan/gbrain/tree/master/evals "evals") | [feat(gateway): config-driven provider\_chat\_options passthrough (](https://github.com/garrytan/gbrain/commit/06f58c2b32398b238bb5b153293f6887bf55a14f "feat(gateway): config-driven provider_chat_options passthrough (fixes #2577) (#2857)  Add provider_chat_options alongside provider_base_urls and thread it through the gateway config path into chat(). The chat request now deep-merges provider-scoped options and model-scoped overrides into providerOptions keyed by recipe id, preserving existing gateway-built options such as Anthropic cacheControl and leaving absent-config behavior unchanged.  This lets operators disable thinking for small-budget hybrid-reasoning utility calls without hardcoding that behavior for every use of those models.") [fixes](https://github.com/garrytan/gbrain/commit/06f58c2b32398b238bb5b153293f6887bf55a14f "feat(gateway): config-driven provider_chat_options passthrough (fixes #2577) (#2857)  Add provider_chat_options alongside provider_base_urls and thread it through the gateway config path into chat(). The chat request now deep-merges provider-scoped options and model-scoped overrides into providerOptions keyed by recipe id, preserving existing gateway-built options such as Anthropic cacheControl and leaving absent-config behavior unchanged.  This lets operators disable thinking for small-budget hybrid-reasoning utility calls without hardcoding that behavior for every use of those models.") [#…](https://github.com/garrytan/gbrain/issues/2577) | 3 weeks agoJul 16, 2026 |
| [examples/skillpack-reference](https://github.com/garrytan/gbrain/tree/master/examples/skillpack-reference "This path skips through empty directories") | [examples/skillpack-reference](https://github.com/garrytan/gbrain/tree/master/examples/skillpack-reference "This path skips through empty directories") | [v0.37.0.0 feat(skillpack): registry cathedral — third-party publish +…](https://github.com/garrytan/gbrain/commit/bc9f7774bf85c14113d799af73bdb2234a203f3a "v0.37.0.0 feat(skillpack): registry cathedral — third-party publish + install + 10/10 quality bar (#1208)  * docs(designs): promote skillpack registry v1 spec with v0.36 alignment header  Strategic spec produced via /office-hours → /plan-ceo-review → /plan-eng-review → /plan-devex-review (two rounds) → /codex outside-voice. 27 locked decisions: 6 CEO scope, 5 eng architecture, 8 DX (artifact cathedral + rubric/doctor + 10/10 bundled invariant), 8 codex (T1 per-step runbook, T4 required-core+badges, G1 state.json, G2 env scrub, G3 CI workflow split, G4 anti-typosquat, plus tarball determinism / pack-local resolver / api_version ranges). 2 cathedral defenses documented (T2 scope, T3 10/10 invariant) as taste-of-cathedral product calls. Lake Score: 25/27.  Spec carries a top-of-file alignment header noting the v0.36.0.0 retirement of the managed-block install model. Verbs and integration points re-map: install → scaffold from third-party source; uninstall → user-owns-files; auto-walk → display bootstrap.md; multi-source receipt → state.json. Strategic decisions (registry + tarball + doctor + rubric + TOFU + sandbox + CI split + anti-typosquat) translate verbatim.  Implementation starts in subsequent commits on this branch.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): foundation layer — manifest validator + tarball + state.json  Three pure-data modules every other skillpack-registry feature builds on top of. Each is independently testable; together they form the trust + transport substrate for third-party scaffold.  - src/core/skillpack/manifest-v1.ts   Third-party skillpack.json runtime validator. Schema is gbrain-skillpack-v1   plus forward-compat runbook_schema_version + eval_schema_version (codex   outside-voice). Shape is a superset of bundle.ts's BundleManifest so the   existing v0.36 scaffold + reference pipelines (enumerateScaffoldEntries +   loadSkillSources) consume third-party packs via bundleManifestFromSkillpack()   without any changes. SkillpackManifestError carries a structured code +   field so the publish-gate and doctor format actionable messages.  - src/core/skillpack/tarball.ts   Deterministic pack + allowlist-gated extract. Pack uses GNU tar with   --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner --pax-option   + GZIP=-n + TZ=UTC so same dir -> same SHA-256 across hosts and clocks.   Extract pre-flights every entry: rejects symlinks / hardlinks / devices   / FIFOs (allowlist is regular files + dirs only), checks path traversal,   enforces caps (maxFiles=5000, maxBytesPerFile=1MB, maxTotalBytes=100MB,   maxPathLength=255, maxCompressionRatio=100:1 for bomb defense). Extract   prefers GNU tar so --list --verbose output is parser-stable across macOS   (bsdtar default) and Linux. Throws TarballError with structured codes.  - src/core/skillpack/state.ts   Machine-owned trust store at ~/.gbrain/skillpack-state.json. Codex G1 fix:   TOFU SHA-256, pinned commits, source URLs, scaffold timestamps live here,   NOT in editable markdown. Atomic .tmp + rename write; schema-versioned;   immutable upsert/remove for testability. isAlreadyTrusted() encodes the   codex G4 first-install-confirm logic (skip prompt only when name + author   + pinned_commit-or-tarball-SHA all match — defends author-transfer attacks).  Tests: 64 cases across 3 files; all green. Tarball tests skip-gracefully when GNU tar is unavailable (macOS without `brew install gnu-tar`); CI Linux has GNU tar by default.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): third-party scaffold — owner/repo, https URL, .tgz, local path  End-to-end third-party scaffold pipeline composed from the foundation layer plus three new modules. `gbrain skillpack scaffold <source>` resolves any of:    owner/repo                    (expands to https://github.com/owner/repo.git)   https://github.com/.../...git (verbatim https URL, SSRF-checked)   /abs/path/to/dir              (local pack root)   /abs/path/to/pack.tgz         (local tarball)  Bare kebab names (\"book-mirror\") keep routing to the v0.36 bundled-skill path; the dispatcher disambiguates on the literal `/` / `://` / `.tgz` shape in the spec. No regression to v0.36 (all 272 existing skillpack tests pass).  - src/core/skillpack/remote-source.ts   classifySpec() is the pure-fn router. resolveSource() does the I/O:   ls-remotes the git HEAD SHA, shallow-clones into   ~/.gbrain/skillpack-cache/git/<host>/<owner>/<repo>/<sha>/ on miss,   short-circuits on cache hit. Tarballs extract into   ~/.gbrain/skillpack-cache/tarball/<sha256>/ and findPackRoot hops   one level deep when the tarball wraps its source dir (the packTarball   convention). Local paths skip the cache entirely (user owns the dir).   Reuses git-remote.ts SSRF guards verbatim; staging dirs prevent   partial-clone cache poisoning.  - src/core/skillpack/trust-prompt.ts   Codex G4 first-install identity confirm. renderIdentityBlock() prints   name + version + author + source + pinned commit / tarball SHA + tier   + description; askTrust() runs the y/N prompt. isAlreadyTrusted()   (in state.ts) drives the skip path — same (name, author, pin/SHA)   triple = no prompt. Author mismatch always re-prompts (transfer-attack   defense). Local sources skip the gate entirely.  - src/core/skillpack/bootstrap-display.ts   Codex T1 fix: no executor for install runbooks. buildBootstrapDisplay()   reads runbooks/bootstrap.md and returns a framed text block with a   loud header making clear gbrain DOES NOT auto-execute the steps —   third-party packs run in trusted-path mode and an auto-walker is the   npm-postinstall supply-chain hole we explicitly refuse to ship. The   agent reads the framed output and walks per-step at its own discretion.  - src/core/skillpack/scaffold-third-party.ts   Orchestrator. Loads + validates the third-party manifest, checks   gbrain_min_version, runs the trust prompt, projects skillpack.json   to BundleManifest shape so enumerateScaffoldEntries (v0.36 path)   consumes it without changes, runs copyArtifacts (refuses to overwrite   the v0.36 way), upserts state.json, returns the framed bootstrap.   Pure semver compare for the version gate; no external dep.  - src/commands/skillpack.ts dispatch extension   cmdScaffold now disambiguates: contains `/` / `://` / `.tgz` →   runThirdPartyScaffold. JSON output envelope matches the rest of   the v0.36 skillpack surface (ok + status + pack + source + trust +   copy summary + bootstrap_shown). New flags: --trust, --no-cache.  - src/core/skillpack/tarball.ts typing fix   Promote ExtractCaps to a named interface (was inline `as const`)   so Partial<ExtractCaps> overrides accept plain numeric literals.  Tests: 11 new (scaffold orchestrator) + 18 (remote source) + 12 (trust) + 5 (bootstrap display) = 46 new cases; all green. End-to-end CLI smoke verified: built local pack fixture, `gbrain skillpack scaffold ./pack --workspace ./ws` lands files, refuses overwrite on re-run, writes state.json, displays bootstrap. Typecheck clean.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): registry catalog — schema + fetch client + search/info/registry CLI  The discovery layer. `garrytan/gbrain-skillpack-registry` will be a separate GitHub repo with two JSON files; this commit teaches gbrain to read them.  - src/core/skillpack/registry-schema.ts   Runtime validators for registry.json (gbrain-registry-v1) and   endorsements.json (gbrain-endorsements-v1). Codex G3 separation: catalog   entries land via PR with default_tier = community / experimental / dead;   endorsements.json is Garry-only and OVERLAYS tier at read time.   effectiveTier() resolves the overlay. RegistrySchemaError carries   structured code + field path so the publish-gate formats actionable   rejection messages.  - src/core/skillpack/registry-client.ts   Network fetch + cache + stale-fallback. Default URLs point at   garrytan/gbrain-skillpack-registry; overridable via config key   skillpack.registry_url or --url. Cache lives at   ~/.gbrain/skillpack-cache/registry-<sha16>.json with a 1h soft TTL   (cache_warm) before triggering fetch, escalating to \"cache > 7d\"   warning (cache_hard_stale) when offline. Hard-fail only when no   cache AND no network (no_cache_no_network). Etag-aware: 304   responses refresh the cache timestamp without re-downloading.   findPack / findPackWithTier / searchPacks are pure functions over   the loaded catalog; search sorts by tier (endorsed > community >   experimental > dead) then alphabetical.  - src/commands/skillpack.ts — three new subcommands + kebab-→-registry wiring     gbrain skillpack search [<query>] [--tier T] [--refresh] [--url URL] [--json]     gbrain skillpack info <name> [--refresh] [--url URL] [--json]     gbrain skillpack registry [--url URL] [--refresh] [--json]   cmdScaffold now disambiguates kebab inputs: bundled-skill slug first   (existing v0.36 path), then registry lookup. `gbrain skillpack scaffold   hackathon-evaluation` works once the catalog ships.  - src/core/skillpack/trust-prompt.ts + state.ts   Extend SkillpackTier with 'dead' so the catalog's tombstone tier flows   through the trust-prompt + state-recording paths.  Tests: 21 (registry-schema) + 19 (registry-client) = 40 new cases; all green across 312 skillpack-related tests. End-to-end CLI smoke: served fixture registry.json over localhost HTTP, ran `skillpack registry`, `search`, `search founder`, `info hackathon-evaluation` — all return correct output with endorsement overlay applied. Typecheck clean.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): rubric + doctor + audit — 10-dimension quality bar  The quality bar makes the registry meaningful. Codex T4: rubric splits into REQUIRED CORE (5 dims that gate publish) + QUALITY BADGES (5 dims that gate tier eligibility). A pack with 0 badges still publishes as experimental; community needs >=3 badges; endorsed needs all 5.  - src/core/skillpack/rubric.ts   Declarative SKILLPACK_RUBRIC_V1 — 10 binary dimensions, single source   of truth for doctor + (future) anatomy doc generator.      CORE (5):       1. manifest_valid              — skillpack.json passes v1 schema       2. skills_have_skill_md        — every skill has SKILL.md w/ valid frontmatter       3. routing_evals_present       — every skill has routing-eval.jsonl >= 5 intents       4. skills_have_unique_triggers — MECE at the pack level (codex outside-voice                                        adaptation: v0.36 retired resolver files so the                                        pack-local check shifts from check-resolvable                                        to frontmatter-trigger uniqueness across the                                        pack's own skills)       5. changelog_present_and_current — CHANGELOG.md has entry for manifest.version      BADGES (5):       6. unit_tests_present          — manifest.unit_tests glob matches >=1 file       7. e2e_tests_present           — manifest.e2e_tests glob matches >=1 file       8. llm_eval_present            — *.judge.json with cases.length >= 3       9. bootstrap_runbook_present   — runbooks/bootstrap.md non-empty (codex T1:                                        v0.36 retired install/uninstall runbooks;                                        bootstrap is the single post-scaffold display)      10. license_present             — LICENSE / LICENSE.md / LICENSE.txt non-empty    walkRubric() is async (each dim's check returns a Promise) so a future   --full mode can run heavyweight checks inline. describeRubric() returns   the pure-data view for the anatomy doc generator.  - src/core/skillpack/doctor.ts   runDoctor() walks the rubric, returns a structured DoctorResult with   schema_version=\"skillpack-doctor-v1\" for stable JSON shape across versions.   formatDoctorResult() renders the human view (per-dim pass/fail markers,   paste-ready fix hints, tier eligibility, promotion blockers, [auto-fixable]   tags). --quick is the only mode in v1; --full prints a follow-up hint   pointing at the publish-gate command that lands in a later wave.    --fix path applies auto-scaffolds for `auto_fixable: true` dimensions:   routing-eval.jsonl stubs (5 example intents per skill), CHANGELOG.md   with the current version's date entry, test/example.test.ts stub,   e2e/example.e2e.test.ts stub, evals/example.judge.json with 3 stub   cases, runbooks/bootstrap.md stub, LICENSE stub. Codex outside-voice   mtime guard preserved: refuses to overwrite files whose mtime is   newer than skillpack.json's. Requires --yes for unattended runs.  - src/core/skillpack/audit.ts   ~/.gbrain/audit/skillpack-YYYY-Www.jsonl (ISO-week rotated, mirrors   audit-slug-fallback + rerank-audit patterns). logSkillpackEvent is   best-effort: stderr warn on failure, never throws. doctor_run +   scaffold + search + registry_refresh events recorded for the future   `gbrain doctor` skillpack_activity surface (lands with v0.37   doctor-integration wave).  - src/commands/skillpack.ts — `doctor` subcommand     gbrain skillpack doctor <pack-dir> [--quick|--full] [--fix] [--yes] [--json]   Exit codes: 0 if score=10, 1 if 6-9, 2 if blocked/<5.  Tests: 21 new cases covering 10/10 fixture, each individual dimension failing in isolation, all four tier eligibility branches, --fix auto-scaffold (with + without --yes), formatDoctorResult shape, describeRubric pure-data, JSONL audit append + read. 333/333 skillpack tests across 23 files. CLI smoke verified.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): publisher side — init + pack + 10/10 reference pack  The publisher trinity: scaffold a new pack, gate it through the doctor, emit a deterministic tarball. Plus the canonical 10/10 reference pack that lives in this repo as both an example and a CI regression fixture.  - src/core/skillpack/init-scaffold.ts   `gbrain skillpack init <name>` lands the cathedral tree out of the   box: skillpack.json + skills/<name>/SKILL.md + routing-eval.jsonl   (5 example intents) + runbooks/bootstrap.md + CHANGELOG.md + README +   LICENSE + .gitignore + test/ + e2e/ + evals/<name>.judge.json. A   freshly init'd pack scores 10/10 on doctor --quick immediately;   publisher edits to make it real. --minimal flag drops test/e2e/evals   for power users opting out. Refuses to overwrite any existing file   (same contract as v0.36 scaffold).  - src/core/skillpack/pack-publish.ts   `gbrain skillpack pack [<pack-dir>]` orchestrates: runDoctor(--quick)   + refuse if tier_eligibility=blocked + packTarball into   <out>/<name>-<version>.tgz with deterministic SHA-256. --dry-run   validates only. --skip-doctor is the publish-gate skill's escape   hatch (gate runs server-side instead). Both paths log into the   skillpack audit JSONL.  - src/commands/skillpack.ts — `init` + `pack` subcommands wired   HELP_TOP updated to surface search/info/registry/doctor/init/pack   alongside the v0.36 commands.  - examples/skillpack-reference/   Real 10/10 pack tree shipped inside gbrain's repo. Doubles as an   integration-test fixture and a publisher reference. The SKILL.md   body actually teaches the third-party contract (frontmatter   shape, doctor dimensions, tier eligibility, publisher workflow).   README.md walks the tree.  - test/skillpack-reference-pack-is-ten.test.ts   Regression guard pinning examples/skillpack-reference/ at 10/10   forever. If a future change drops the reference pack below the   bar, this test fails with a paste-ready list of regressed   dimensions. Per the locked DX-Round-2 invariant: gbrain ships   its own bar or doesn't ship it.  Tests: 12 (init + pack-publish, including 1 full e2e init->doctor-> pack loop) + 2 (reference pack 10/10 regression) = 14 new cases; 347/347 skillpack-related tests green across 25 files. Typecheck clean. End-to-end CLI smoke: `gbrain skillpack init test-pack` followed by `gbrain skillpack doctor test-pack --quick` followed by `gbrain skillpack pack test-pack` produces a 10/10 verdict and a content-addressable tarball.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): anatomy doc generator + e2e third-party flow test  Closes the cathedral with the canonical one-page reference doc + the end-to-end test that exercises the full publisher + consumer loop via the actual `gbrain` CLI subprocess.  - scripts/build-skillpack-anatomy.ts   Regenerates docs/skillpack-anatomy.md between BEGIN/END markers   from src/core/skillpack/rubric.ts. Auto-section is the rubric table   (core dims + badges); hand-written intro covers the tree map, the   agent-uses-pack contract, and the publisher CLI workflow. `--check`   flag fails the build when committed doc drifts from rubric.ts —   wireable into `bun run verify` later.  - docs/skillpack-anatomy.md   Initial generated output. 112 lines. Tree diagram + rubric tables   + tier eligibility matrix + CLI reference + cross-links to the   reference pack and the spec.  - test/e2e/skillpack-third-party.test.ts   Subprocess-spawning E2E (no in-process imports of CLI internals).   Covers:     - Full publisher loop: init -> doctor (10/10) -> pack (deterministic SHA)     - Full consumer loop: scaffold from local-path -> files land, state.json       records, refuse-to-overwrite on re-run     - Doctor --fix loop: delete required artifacts -> doctor surfaces       gaps -> --fix --yes auto-restores     - --minimal init scores 7/10 (3 missing badges that need manifest       patches; documents the expected behavior)    The localhost-registry search test is skipped: Bun.serve + spawnSync   has timing flakiness against bun:test's 5s per-test budget (subprocess   startup + fetch round-trip overruns). Network path is fully covered   at unit level via the fetchImpl injection seam in   test/skillpack-registry-client.test.ts.  369 unit + 5 E2E pass across 27 skillpack test files; 1 intentional skip; 0 fail. Typecheck clean.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(skillpack): route audit-test env mutations through withEnv() helper  scripts/check-test-isolation.sh flagged test/skillpack-rubric-doctor.test.ts for direct process.env.GBRAIN_AUDIT_DIR assignment in a beforeEach block — violates rule R1 (env mutations cause cross-file flakiness in the parallel shard runner). Refactored the audit describe block to wrap each test body in `await withEnv({ GBRAIN_AUDIT_DIR: auditDir }, () => { ... })` from test/helpers/with-env.ts. Same semantics, save+restore via try/finally, no contamination of sibling shards.  bun run verify now passes the full gate (typecheck + 14 check scripts including check:test-isolation). Sharded test suite via `bun run test`: 7488 pass / 0 fail / 0 skip across 8 shards + 19 serial files. Skillpack slice: 369 unit + 5 E2E pass / 1 intentional skip / 0 fail.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test(e2e): update cycle phase-order assertions for v0.36.1.0 hindsight wave  Pre-existing master bug surfaced during the skillpack-registry-cathedral E2E run: v0.36.1.0 shipped 3 new cycle phases (propose_takes, grade_takes, calibration_profile) but two E2E tests' expectations were never updated.  - test/e2e/dream-cycle-phase-order-pglite.test.ts   EXPECTED_PHASES now includes the v0.36.1.0 trio. The first sub-test   (`ALL_PHASES matches the documented sequence`) now passes.  - test/e2e/cycle.test.ts   Phase count assertion bumped 13 -> 16. Comment block extended with   the v0.36.1.0 entry in the same shape as the prior version markers.  Both files were drift-against-source: cycle.ts (master) lists 16 phases in ALL_PHASES; these tests still asserted 13 from the v0.33.3 baseline. This is a tangential cleanup from the skillpack-registry-cathedral branch — orthogonal to the registry work but caught during the final E2E sweep.  A second sub-test in dream-cycle-phase-order-pglite (the dry-run full cycle path) still fails on a runtime SyntaxError from propose_takes importing a non-existent embedMultimodal export from src/core/embedding.ts. That's a separate v0.36.1.0 implementation bug that warrants its own PR; not in scope here.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test(e2e): include v0.36.1.0 embedding exports in dream-cycle mock  Bun's module linker fails fast when a downstream consumer imports a symbol the mock didn't declare. v0.36.1.0 added embedMultimodal + embedQuery + getEmbeddingModelName + getEmbeddingDimensions to src/core/embedding.ts; the propose_takes phase and other v0.36 phases pull from them, so the mock has to keep parity.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(skillpack): endorse CLI — Garry-only registry tier overlay  gbrain skillpack endorse <name> [--tier endorsed|community|experimental|dead]                                [--note STR] [--push] [--dry-run]  Runs inside a clone of garrytan/gbrain-skillpack-registry. Validates that <name> is in registry.json's catalog, mutates endorsements.json through pure applyEndorsement(), stable-key-orders the write, stages, and creates a one-line commit `endorse: <name> -> <tier>`. Optionally pushes to origin.  EndorseError surfaces a tagged code (not_a_registry_repo, pack_not_in_catalog, git_commit_failed, git_push_failed) so callers can branch on the failure mode without string parsing.  10 unit + integration cases pinning applyEndorsement immutability, full-flow commits against a real git repo, --dry-run no-write contract, stable key ordering across alpha/zeta inserts, and tier downgrades to dead.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore(release): bump to 0.37.0.0 — skillpack registry cathedral  Third-party skillpack ecosystem layered on the v0.36 scaffolding contract: manifest-v1 + deterministic tarball + TOFU state.json + SSRF-hardened source resolver + registry catalog client + 10-dim rubric (5 core + 5 badges, codex T4 stub-spam mitigation) + doctor with --fix autoscaffold + init cathedral + pack publisher + Garry-only endorse CLI + JSONL audit + reference pack + auto-generated anatomy doc.  Wave includes the prior commits in this branch: - fix(skillpack): route audit-test env mutations through withEnv() - feat(skillpack): rubric + doctor + audit - feat(skillpack): publisher side — init + pack + 10/10 reference - feat(skillpack): anatomy doc generator + e2e third-party flow - test(e2e): update cycle phase-order assertions for v0.36.1.0 - test(e2e): include v0.36.1.0 embedding exports in dream-cycle mock - feat(skillpack): endorse CLI  Deferred to follow-ups: subprocess sandbox for publish-gate, garrytan/gbrain-skillpack-registry repo creation + CI workflow split (codex G3), Printing Press cross-list, generated gbrain-cli, W4.5 retrofit of bundled skills to 10/10.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore(release): rebump 0.37.0.0 → 0.38.0.0  User requested v0.38.0 (4-segment: 0.38.0.0) as the slot for the skillpack registry cathedral. Pure rename — no scope change, no behavior change. VERSION + package.json + CHANGELOG header + CHANGELOG \"To take advantage\" section + CLAUDE.md Key Files entry rewritten in lockstep.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore(release): rebump 0.38.0.0 → 0.37.0.0  User picked v0.37.0.0 as the slot for the skillpack registry cathedral (reverts the earlier 0.37 → 0.38 rebump). Master tip is v0.36.6.0, so 0.37.0.0 remains semver-clean. Pure rename — no scope change, no behavior change. VERSION + package.json + CHANGELOG header + \"To take advantage\" section + lead-paragraph \"v0.38\" references + CLAUDE.md Key Files annotation all rewritten in lockstep.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>") | 3 months agoMay 19, 2026 |
| [recipes](https://github.com/garrytan/gbrain/tree/master/recipes "recipes") | [recipes](https://github.com/garrytan/gbrain/tree/master/recipes "recipes") | [feat(doctor): silent-failure check batch — content-hash duplicates, u…](https://github.com/garrytan/gbrain/commit/7cbb99ffef47b0148b851049cf0193ca4caab2b0 "feat(doctor): silent-failure check batch — content-hash duplicates, undeclared DB-only pages, heartbeat staleness, db_only collector collision (#2250 #2784 #2787 #2788) (#3457)  Co-Authored-By: Time Attakc <89218912+time-attack@users.noreply.github.com>") | 5 days agoJul 31, 2026 |
| [scripts](https://github.com/garrytan/gbrain/tree/master/scripts "scripts") | [scripts](https://github.com/garrytan/gbrain/tree/master/scripts "scripts") | [v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1…](https://github.com/garrytan/gbrain/commit/aecb33e795cc4806f760446c55ab1c350194ddc8 "v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1 contribution requirements (#3805)  * v0.42.73.1 revert(ci): remove the PR gate — it cannot function on this repository  The gate needs an ANTHROPIC_API_KEY Actions secret for its verdict and read-write workflow permissions to post a comment or set a label. This repository grants neither, and both are admin-only, so it can only ever skip.  It also caused a real incident before that was understood: a read-only token turned every API call into a 403, the code treated that as a crash, and the check went red on an outside contributor's PR four times with no comment explaining why. v0.42.73.0 fixed the crash, but a check that runs on every PR and can never reach a verdict does not earn a place in the repo.  Removes the workflow, the script, its type surface, and its test file. The code is preserved in git history at v0.42.73.0. If it is ever restored, the mechanical half (intent/screenshot policy, title rule, red flags) should render to the Actions job summary rather than a comment — that needs no token permission and no API key.  CONTRIBUTING.md's intent-paragraph and screenshot requirement is unchanged and stands as written; it is enforced by maintainers reading PRs, as before.  typecheck clean, verify 34/34, llms bundles regenerated.  * v0.42.73.1 revert(docs): withdraw the human-intent-paragraph + screenshot contribution requirement  This reverts commit 6d1232d5a67c9ab7086d00aaa4d362324919fb3d (v0.42.72.1).  CONTRIBUTING.md, both issue templates, and the pull-request template return to their pre-2026-08-02 state. VERSION/package.json/CHANGELOG keep moving forward (0.42.73.1); the v0.42.72.1 CHANGELOG entry stays as historical record, and the 0.42.73.1 entry now describes the withdrawal.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [skills](https://github.com/garrytan/gbrain/tree/master/skills "skills") | [skills](https://github.com/garrytan/gbrain/tree/master/skills "skills") | [fixup(](https://github.com/garrytan/gbrain/commit/873587f831775a6460454d6b8d5ea827897e55f6 "fixup(#3453): regenerate skills.lock.json against current master skills/  The PR's committed manifest predates master's latest SKILL.md edits; regenerated with the PR's own generator per the check's instructions.") [#3453](https://github.com/garrytan/gbrain/pull/3453) [): regenerate skills.lock.json against current master skills/](https://github.com/garrytan/gbrain/commit/873587f831775a6460454d6b8d5ea827897e55f6 "fixup(#3453): regenerate skills.lock.json against current master skills/  The PR's committed manifest predates master's latest SKILL.md edits; regenerated with the PR's own generator per the check's instructions.") | 5 days agoJul 31, 2026 |
| [src](https://github.com/garrytan/gbrain/tree/master/src "src") | [src](https://github.com/garrytan/gbrain/tree/master/src "src") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [templates](https://github.com/garrytan/gbrain/tree/master/templates "templates") | [templates](https://github.com/garrytan/gbrain/tree/master/templates "templates") | [v0.42.12.0 feat: self-upgrading gbrain — invocation-riding update che…](https://github.com/garrytan/gbrain/commit/a57d98b81346fed5935de4d5913dafc4bb6509e6 "v0.42.12.0 feat: self-upgrading gbrain — invocation-riding update check + opt-in auto-upgrade (#1798)  * feat(self-upgrade): decision/cache/snooze foundation + atomic binary self-update  Pure decideSelfUpgrade (invocation + autopilot channels), atomic untrusted cache + escalating snooze + shared marker grammar (forged-marker rejection), semver helpers, and real darwin-arm64/linux-x64 binary self-update (download -> fsync -> smoke -> atomic rename; failure leaves old binary intact). Tests incl. real-HTTP-server swap E2E.  * feat(self-upgrade): check-update cache/markers, self-upgrade command, CLI heartbeat hook  check-update gains gstack-style cache/snooze/markers + refreshUpdateCache + exported fetchLatestRelease. New 'gbrain self-upgrade' command. cli.ts emits the update marker on every invocation (cache-read-only hot path, detached single-flight refresh, skip-set + recursion guard + NODE_ENV=test gate).  * feat(self-upgrade): autopilot silent channel, doctor check, runPostUpgrade setup, config + identity marker  autopilot opt-in silent channel (auto+quiet+idle, swap-only+breadcrumb+exit-relaunch) + installSystemd Restart=always + migrateSystemdUnitToRestartAlways. doctor self_upgrade_health. runPostUpgrade applySelfUpgradeSetup (one-time consent + systemd rewrite). init defaults mode=notify. config self_upgrade plane + KNOWN_CONFIG_KEYS. get_brain_identity carries update marker.  * docs(self-upgrade): gbrain-upgrade agent skill, RESOLVER/manifest, auto-update doc reversal, HEARTBEAT  New skills/gbrain-upgrade agent flow (mirror gstack-upgrade) wired into RESOLVER + manifest. upgrades-auto-update.md reversed to document opt-in auto + conservative gates. HEARTBEAT self-upgrade --check-only line. llms-full regenerated.  * chore: bump version and changelog (v0.42.12.0)  Self-upgrading gbrain: invocation-riding update marker + opt-in autopilot silent channel + real atomic binary self-update. Mirrors gstack's mechanism.  Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>  * fix(self-upgrade): write just-upgraded-from breadcrumb + clear stale cache after upgrade  Codex ship-review P3: the CLI startup hook reads just-upgraded-from to print the one-time JUST_UPGRADED confirmation, but nothing wrote it — dead path. runUpgrade now writes the breadcrumb (covers full + --swap-only) and clears the update-check cache + snooze so a now-applied 'upgrade available' marker stops nudging.  * feat(self-upgrade): surface what's-new in notify + wire agent integration (AGENTS.md, HEARTBEAT) + e2e  - self-upgrade --check-only --json now includes changelog_diff + release_url   (export fetchChangelog); the gbrain-upgrade skill shows 3-5 what's-new bullets   before the 4-option prompt instead of just version numbers. - setup injects a self-upgrade marker protocol into AGENTS.md so interactive   agents (Claude Code, Codex) act on the UPGRADE_AVAILABLE stderr marker — the   piece that makes notify actually fire for them. - HEARTBEAT daily beat routes through the gbrain-upgrade skill (OpenClaw/Hermes   cron cadence); auto-mode daemons ride the autopilot tick. - e2e: real subprocess invocation proves the marker fires (notify emits;   off/snooze/up-to-date silent; JUST_UPGRADED fires+clears; --quiet suppresses).   Serial test: --check-only surfaces the changelog.  ---------  Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>") | 2 months agoJun 3, 2026 |
| [test](https://github.com/garrytan/gbrain/tree/master/test "test") | [test](https://github.com/garrytan/gbrain/tree/master/test "test") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [tests/heavy](https://github.com/garrytan/gbrain/tree/master/tests/heavy "This path skips through empty directories") | [tests/heavy](https://github.com/garrytan/gbrain/tree/master/tests/heavy "This path skips through empty directories") | [fix(heavy-tests): use supported init flag (](https://github.com/garrytan/gbrain/commit/ea08effd02af2529e2f0bc67861c95dd95ba2a2a "fix(heavy-tests): use supported init flag (#3412)  Co-authored-by: mzkarami <1917371+mzkarami@users.noreply.github.com>") [#3412](https://github.com/garrytan/gbrain/pull/3412) [)](https://github.com/garrytan/gbrain/commit/ea08effd02af2529e2f0bc67861c95dd95ba2a2a "fix(heavy-tests): use supported init flag (#3412)  Co-authored-by: mzkarami <1917371+mzkarami@users.noreply.github.com>") | last weekJul 27, 2026 |
| [tools](https://github.com/garrytan/gbrain/tree/master/tools "tools") | [tools](https://github.com/garrytan/gbrain/tree/master/tools "tools") | [v0.40.9.0 feat(chunker): .sql indexing via tree-sitter + code-def on …](https://github.com/garrytan/gbrain/commit/ee6b11e563469392a3d510864d20026b79eaa1d1 "v0.40.9.0 feat(chunker): .sql indexing via tree-sitter + code-def on SQL DDL (#1173) (#1350)  * feat(chunker): vendor tree-sitter-sql.wasm + Step 0 grammar inspection tool  Vendored from DerekStride/tree-sitter-sql @ c2e1e08db1ea20dc23bdb8d228a81a8756e9c450, built with tree-sitter-cli@v0.26.3 + --abi 14 (matches web-tree-sitter 0.22.6's ABI 13-14 range; default --abi 15 was incompatible). 11 MB binary — substantially larger than the plan's 400KB-1.4MB estimate (DerekStride's multi-dialect grammar generates 40MB of parser.c).  tools/inspect-sql-grammar.ts is a one-shot Step 0 script that parsed 9 representative SQL fixtures and surfaced three load-bearing facts:    1. Top-level node type is `program > statement > <kind>`. Every top-level      node is `statement`, with the actual statement type as its single      named child. TOP_LEVEL_TYPES['sql'] = new Set(['statement']) catch-all.   2. The generic extractSymbolName returns null for EVERY SQL node — needs      a SQL-specific branch that dives into statement.namedChild(0).   3. DML emits one statement-chunk per statement (NOT one fat recursive-      fallback chunk). $$ body parses cleanly. Even invalid SQL (\"SELECT      FROM WHERE\") still produces a select-shaped statement, not a parse      error.  Wave plan: ~/.claude/plans/system-instruction-you-are-working-tender-haven.md  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat(chunker): wire SQL into language manifest + sync walker  Five additive edits to src/core/chunkers/code.ts:   1. Import G_SQL grammar (DerekStride SHA in inline comment).   2. Extend SupportedCodeLanguage union with 'sql'.   3. Register sql entry in LANGUAGE_MANIFEST.   4. Add .sql case to detectCodeLanguage.   5. TOP_LEVEL_TYPES['sql'] = Set(['statement']) catch-all per Step 0      finding that DerekStride wraps every top-level node in `statement`.  Two SQL-aware additions to existing helpers:   - extractSymbolName: dives into `statement.namedChild(0)` and routes to     extractSqlSymbolName. DDL kinds (create_table/function/view/index/     procedure/type/schema/database/trigger + alter_table/view) extract     target identifier via `name` field with fallback to identifier-shaped     children. DML kinds (select/insert/update/delete/merge/with) return     null so chunks emit unnamed.   - normalizeSymbolType: adds 'table', 'view', 'index', 'procedure',     'type', 'schema', 'database', 'trigger' branches so chunk headers say     \"table users\" instead of \"statement users\".   - emit-path passes inner-child type to normalizeSymbolType when the     outer node is `statement` (SQL only condition).  sync.ts: add '.sql' to CODE_EXTENSIONS so isCodeFilePath routes it to importCodeFile with page_kind='code'.  Manual verification (bun /tmp/test-sql-chunker2.ts) confirms CREATE TABLE, CREATE FUNCTION (with $$ body), CREATE INDEX all produce chunks with correct symbolName + symbolType. Small-sibling merging collapses short-statement runs into single merged chunks (existing behavior, not SQL-specific).  Wave plan: ~/.claude/plans/system-instruction-you-are-working-tender-haven.md  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test(sql): unit + e2e + extend findCodeDef DEF_TYPES to cover SQL DDL  Unit tests (test/chunkers/code.test.ts, 8 new cases):   - detectCodeLanguage now covers all 30 extensions (.sql added)   - is-case-insensitive extended to .SQL   - CREATE TABLE / FUNCTION / INDEX / VIEW / ALTER TABLE each extract     target name into symbolName + map to correct symbolType   - CREATE FUNCTION with $$ body parses without crashing   - DML statements (INSERT) emit chunks but with symbolName=null   - Mixed DDL+DML: per-statement emission, only DDL gets symbolName   - Header includes \"[SQL]\" language tag   - Invalid SQL (\"SELECT FROM WHERE\") doesn't crash the parser  Sync classifier (test/sync-classifier-widening.test.ts, 1 new case):   - isCodeFilePath('migrations/001_init.sql') true, case-insensitive  E2E (test/e2e/code-indexing.test.ts, 7 new cases):   - SQL import produces pages.type='code' + page_kind='code'   - CREATE TABLE / FUNCTION chunks have correct symbol_name + symbol_type   - findCodeDef returns CREATE TABLE / FUNCTION / INDEX / VIEW sites by     name (load-bearing D2 canary — proves SQL is code intelligence,     not just searchable text)   - beforeAll timeout bumped to 30s (92-migration replay + 11MB SQL     grammar load pushes past default 5s)  Source change to make E2E pass (src/commands/code-def.ts):   - DEF_TYPES extended with 'table', 'view', 'index', 'procedure',     'schema', 'database', 'trigger'. The chunker's normalizeSymbolType     already maps create_table → 'table' etc; without this allowlist     extension the chunks were indexed correctly but invisible to     `gbrain code-def <name>`. This was the codex F2 missing-piece     surfaced in /plan-eng-review (D6).  Wave plan: ~/.claude/plans/system-instruction-you-are-working-tender-haven.md  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * v0.40.9.0 feat(chunker): .sql indexing via tree-sitter, code-def works on SQL DDL (#1173)  Closes #1173. gbrain sync now indexes .sql files; gbrain code-def returns CREATE TABLE / FUNCTION / VIEW / INDEX / PROCEDURE / TYPE / SCHEMA / DATABASE / TRIGGER + ALTER TABLE/VIEW sites by name.  Bumps: VERSION + package.json 0.40.8.0 → 0.40.9.0. Updates: CLAUDE.md (37 grammars, SQL branch documented), llms-full.txt regenerated. Full release notes in CHANGELOG.md including the 11 MB binary-size disclosure and the 6 decisions (D1-D6) captured during /plan-eng-review.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test(sql): fill remaining coverage gaps — TRIGGER/TYPE/PROCEDURE/SCHEMA + code-refs + idempotency + DML-only file  Unit tests (test/chunkers/code.test.ts, 7 new cases):   - CREATE TRIGGER extracts name + symbolType=trigger   - CREATE TYPE (enum) extracts name + symbolType=type   - CREATE PROCEDURE extracts name + symbolType=procedure   - CREATE SCHEMA (best-effort — grammar version dependent)   - Header symbolType reflects inner DDL kind, never the bare 'statement' wrapper   - Empty SQL input → empty chunk array   - Whitespace-only SQL → empty chunk array  E2E tests (test/e2e/code-indexing.test.ts, 6 new cases):   - findCodeRefs returns SQL chunks by substring match (validates the     ILIKE-based ref path works on SQL with DDL + DML coverage)   - CREATE TRIGGER + CREATE TYPE chunks land in content_chunks with     correct symbol_type after import (engine-level regression)   - findCodeDef on CREATE TYPE returns the chunk (DEF_TYPES allowlist     regression pin: 'type' was added to DEF_TYPES in the prior commit)   - findCodeDef on CREATE TRIGGER returns the chunk (DEF_TYPES regression     pin: 'trigger' is in the allowlist)   - DML-only file still produces a code page (just with zero     symbol-named chunks — closes the question codex F14 raised)   - Re-importing same SQL file is idempotent (content_hash short-circuit     behaves the same on SQL as it does on TS/Python/Go)  All 63 SQL-related tests pass (chunker + sync classifier + E2E). The pre-existing master flakes (check-system-of-record.sh, longmemeval under shard concurrency) pass in isolation — not regressions from this branch.  Wave plan: ~/.claude/plans/system-instruction-you-are-working-tender-haven.md  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(test): root-cause 4 master flakes — GBRAIN_SCAN_ROOT env + .slow rename + budget bumps  Four flakes surfaced during the v0.40.9.0 full unit sweep. All pass in isolation; all fail under 8-shard parallel CPU contention. Fixes below hit the actual root cause, not symptoms — no quarantine-and-ignore.  ────────────────────────────────────────────────────────────────────── 1. check-system-of-record.sh — \"catches violations in scripts/ alongside src/\" ────────────────────────────────────────────────────────────────────── Root cause: under shard load, the test's `spawnSync('git', ['init', '-q'])` in /tmp/gate-test-* occasionally silently fails (filesystem contention), so the fakeRepo has no .git dir. The gate then runs `git rev-parse --show-toplevel` which walks UP past the fakeRepo into our real gbrain repo, sets ROOT=/real/gbrain/repo, scans the clean real src/+scripts/, exits 0. The test \"expects exit 1 + 'naughty.ts' in stdout\" sees exit 0 and empty stdout — fails.  Fix: - scripts/check-system-of-record.sh: honor `GBRAIN_SCAN_ROOT` env var   BEFORE the git-rev-parse fallback. Pure additive — production callers   unchanged, tests get deterministic resolution. - test/check-system-of-record.test.ts: `runGate` sets   `GBRAIN_SCAN_ROOT: cwd` in spawnSync env. Closes the flake at the   cause, not at the symptom (a retry loop would have papered over the   real bug — the gate's resolution was too clever for its own good).  ────────────────────────────────────────────────────────────────────── 2-4. eval-longmemeval.test.ts — 3 timeouts under 8-shard parallel ────────────────────────────────────────────────────────────────────── Root cause: the file takes ~50s in isolation (full LongMemEval harness replay with stubbed LLM). Under 8-shard parallel, CPU contention pushes individual tests past bun's default 60s timeout. 3 tests timed out:   - JSONL format guard (60s timeout)   - JSONL key contract (65s timeout)   - --by-type emits final by_type_summary (60s timeout)  Fix: rename `test/eval-longmemeval.test.ts` → `.slow.test.ts`. This is exactly what the .slow taxonomy exists for per CLAUDE.md:   > \"*.slow.test.ts → intentional cold-path tests; would dominate the   >  fast loop's wallclock\"  Verified routing: - Local `bun run test`: skips longmemeval (no flake) - Local `bun run test:slow`: runs explicitly, 31 pass in 277s - CI `scripts/test-shard.sh`: still runs (.slow NOT excluded from FNV   bucketing — verified by dry-run: lands in shard 3/4)  ────────────────────────────────────────────────────────────────────── Adjacent fix: slow wrapper + test-shard.slow.test.ts beforeAll budget ────────────────────────────────────────────────────────────────────── The longmemeval move surfaced a 4th flake: `test-shard.slow.test.ts`'s beforeAll shells out 4×`scripts/test-shard.sh --dry-run-list` (~4s solo each); when longmemeval is now running in the same slow-wrapper invocation hogging CPU, the 4 sequential dry-runs slip past the 60s beforeAll timeout.  Fixes: - scripts/run-slow-tests.sh: bump bun test --timeout 60s → 120s. Slow   tests are explicit by-name; a generous per-test budget is correct   posture, not a workaround. - test/scripts/test-shard.slow.test.ts: bump beforeAll budget 60s → 180s.   Matches the actual workload under parallel slow-shard execution.  ────────────────────────────────────────────────────────────────────── Verification ────────────────────────────────────────────────────────────────────── - `bun test test/check-system-of-record.test.ts` — 6 pass (in isolation) - `bun run test:slow` — 31 pass in 277s (was: 1 fail at 89s before fixes) - Full `bun run test` re-run in progress; will confirm 0 fail.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(test): two more flake-hardening rounds — shard-aware perf gate + shard cap 600→900  Round 1 caught 4 named flakes; the post-fix sweep surfaced 2 more from the same flake class (calibration values that were correct when set but are no longer correct for the larger test suite).  5. longmemeval-trajectory-routing — \"perf gate preserved\" (3rd-party flake)  Failure: under shard load, test asserts elapsed<10s but real wallclock was 37s. The gate is supposed to catch real harness-layer regressions, not raw cycle counts; 8-shard CPU contention routinely 3-5x's wallclock.  Fix: mode-aware ceiling. Solo run keeps the tight 10s gate (catches real algorithmic regressions). Shard run (detected via `$SHARD` env set by the parallel wrapper) loosens to 60s — still catches >6x regressions but tolerates parallel contention. Per-test timeout bumped 5s default → 90s.  6. Per-shard wedge-detection too tight (false WEDGED markers)  Shards 5+6 of the prior sweep both got WEDGED markers at the 600s wrapper cap, but their bun-internal timer shows they actually finished in 620-770s with 0 failures. The 600s shard cap was calibrated when shards held ~600 tests; suite growth through v0.40.x pushed individual shards to 1100+ tests and 620-770s legitimate wallclock.  Fix: bump GBRAIN_TEST_SHARD_TIMEOUT default 600→900. Real hangs still hit the 900s cap; fully-completed shards no longer false-kill at 600s. Env override preserved.  ────────────────────────────────────────────────────────────────────── Cumulative flake hardening (across 2 commits) ────────────────────────────────────────────────────────────────────── 1. check-system-of-record gate — GBRAIN_SCAN_ROOT env override 2. eval-longmemeval (3 tests)   — rename to .slow 3. run-slow-tests.sh             — bump --timeout 60s → 120s 4. test-shard.slow.test.ts       — bump beforeAll 60s → 180s 5. longmemeval perf gate         — shard-mode-aware ceiling 10s/60s 6. Per-shard wedge cap           — bump 600s → 900s  All root-cause fixes; zero retry-loop / quarantine-and-ignore.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(test): clamp local default shard count 8 → 4 — kills PGLite contention SIGKILLs  Sweep #3 (after the prior 6 hardening fixes + master merge) caught a new flake class: shard 5 got SIGKILL'd (rc=137) during source-health.test.ts's 92-migration PGLite replay. 8 parallel shards each running their own PGLite WASM init + 92-migration replay contend severely on shared FS state — even with the 900s shard cap, shard 5 wedged so hard the wrapper fell back to SIGKILL.  Root cause: 8-shard parallel was aggressive (we picked detect_cpus on a 12-perf-core M-series, clamped to 8). CI runs 4 via test-shard.sh and is stable. 8 → 4 trades ~2x local wallclock for reliability + matches CI fan-out exactly. Override still available via --shards N or SHARDS=N (clamped at 8 ceiling).  Side benefit: also resolves the 2 .serial.test.ts spawn failures in sweep #3 — those serial tests run AFTER the parallel pass, so when the parallel pass leaks PGLite write-locks under heavy contention, the serial spawn tests inherit the polluted state and timeout on their own subprocess spawns. Reducing parallel contention upstream cleans up the FS state by the time serial runs.  ────────────────────────────────────────────────────────────────────── Cumulative flake hardening (3 commits, 7 fixes) ────────────────────────────────────────────────────────────────────── 1. check-system-of-record gate — GBRAIN_SCAN_ROOT env override 2. eval-longmemeval (3 tests)   — rename to .slow 3. run-slow-tests.sh             — bump --timeout 60s → 120s 4. test-shard.slow.test.ts       — bump beforeAll 60s → 180s 5. longmemeval perf gate         — shard-mode-aware ceiling 10s/60s 6. Per-shard wedge cap           — bump 600s → 900s 7. Default local shards          — clamp 8 → 4 (matches CI)  All root-cause fixes; zero quarantine-and-ignore.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(test): bump shard timeout 900→1500 — fixes 4-shard 968s overshoot  Sweep #4 at the new 4-shard default ran cleanly: 0 failures, 10072 pass. BUT shard 1 was false-killed at 900s even though its internal completion was 968s (the same flake pattern as the prior 600→900 bump, just at the new shard sizing).  Reason: 8→4 shard reduction means each shard now runs 2x more files (159 vs 80) and 2x more tests (~2420 vs ~1100). Internal wallclock per shard climbed from 620-770s (8-shard) to 960-1020s (4-shard). The 900s cap was sized for the prior 8-shard sizing; 4-shard sizing needs more headroom. 1500s gives ~55% headroom over observed 4-shard wallclock and catches real hangs that wouldn't complete in 1500s anyway.  ────────────────────────────────────────────────────────────────────── Cumulative flake hardening (4 commits, 8 fixes) ────────────────────────────────────────────────────────────────────── 1. check-system-of-record gate — GBRAIN_SCAN_ROOT env override 2. eval-longmemeval (3 tests)   — rename to .slow 3. run-slow-tests.sh             — bump --timeout 60s → 120s 4. test-shard.slow.test.ts       — bump beforeAll 60s → 180s 5. longmemeval perf gate         — shard-mode-aware ceiling 10s/60s 6. Per-shard wedge cap           — 600s → 900s → 1500s (8→4-shard recalibration) 7. Default local shards          — clamp 8 → 4 (matches CI) 8. (this commit)                 — calibrate cap for new shard sizing  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix(test): CI flake — warm-create perf gate ceiling now mode-aware (1500ms solo / 4000ms loaded)  CI test_3 (Ubuntu, run #77585655194) failed on the test/eval-longmemeval.slow.test.ts > 'warm-create speed gate' p50 assertion. GHA Ubuntu runners are meaningfully slower than my Apple Silicon dev box under parallel shard load — the 10-trial loop took 17364ms total which puts per-trial p50 well above the 1500ms ceiling.  This is the same flake class as D5 in the local sweep hardening (longmemeval-trajectory-routing perf gate). Apply the same shard-aware ceiling pattern: 1500ms solo (catches real harness regressions), 4000ms when `$SHARD` (local parallel) OR `$CI` (GHA et al) is set.  Verified solo on Apple Silicon: p50=44ms (well under 1500ms tight gate). Verified with `CI=true` env: p50=44ms (well under 4000ms loaded gate). 4000ms still catches >50x algorithmic regressions on a 25-44ms baseline.  ────────────────────────────────────────────────────────────────────── Cumulative flake hardening (5 commits, 9 fixes) ────────────────────────────────────────────────────────────────────── 1-8. (prior 4 commits)             — see PR comment #4527950030 9. (this commit) warm-create gate  — shard/CI-mode-aware ceiling  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>") | 3 months agoMay 24, 2026 |
| [.env.testing.example](https://github.com/garrytan/gbrain/blob/master/.env.testing.example ".env.testing.example") | [.env.testing.example](https://github.com/garrytan/gbrain/blob/master/.env.testing.example ".env.testing.example") | [feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (](https://github.com/garrytan/gbrain/commit/a86f995883605922c0ae9e437ff32e8a75727ff7 "feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (#7)  * feat: contract-first operations.ts with OperationError, dry_run, importFromContent  30 shared operations as single source of truth for CLI and MCP. - OperationError with typed error codes (page_not_found, invalid_params, etc.) - dry_run support on all mutating operations - importFromContent split from importFile with transaction wrapping - Idempotency hash now includes ALL fields (title, type, frontmatter, tags) - Config env var fallback: GBRAIN_DATABASE_URL > DATABASE_URL > config file  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: rewrite MCP server + CLI + tools-json from operations  server.ts: 233 -> ~80 lines. Tool definitions and dispatch generated from operations[]. cli.ts: shared operations auto-registered, CLI-only commands kept as manual dispatch. tools-json: generated FROM operations[], eliminating the third contract surface. Parity test verifies structural contract between operations, CLI, and MCP.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: delete 12 command files migrated to operations.ts  Handler logic for get, put, delete, list, search, query, health, stats, tags, link, timeline, and version now lives in operations.ts. Kept: init, upgrade, import, export, files, embed, sync, serve, call, config.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: init --non-interactive, upgrade verification, schema migration  - gbrain init --non-interactive --url <url> for plugin mode (no TTY required) - Post-upgrade version verification in gbrain upgrade - Drop storage_url from files table (storage_path is the only identifier)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: tool-agnostic skills + new setup skill  All 7 skills rewritten with intent-based language instead of CLI commands. Works with both CLI and MCP plugin contexts. New setup skill replaces install: auto-provision Supabase via CLI, AGENTS.md injection, target TTHW < 2 min.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: ClawHub bundle plugin, CI workflows, v0.3.0  - openclaw.plugin.json with configSchema, MCP server config, skill listing - GitHub Actions: test on push/PR, multi-platform release (macOS arm64 + Linux x64) - Version bump 0.3.0, CHANGELOG, README ClawHub section, CLAUDE.md updated  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: idempotency hash mismatch + MCP dry_run passthrough  importFromContent now passes its all-fields hash through putPage via content_hash on PageInput, so the stored hash matches the computed hash. Previously the skip-if-unchanged check never fired because the hash formulas differed.  MCP server now passes dry_run from tool params to OperationContext.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.3.0.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: schema loader handles PL/pgSQL $$ blocks  Delete the semicolon-based SQL splitter in db.ts which broke on PL/pgSQL trigger functions containing semicolons inside $$ delimiter blocks. Use single conn.unsafe(schemaSql) call instead — the postgres driver handles multi-statement SQL natively. schema.sql already uses IF NOT EXISTS / CREATE OR REPLACE for idempotency.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test infrastructure + realistic brain fixtures  Add test infrastructure for running E2E tests against real Postgres+pgvector. Includes: - test/e2e/helpers.ts: DB lifecycle, fixture import, timing, diagnostics - 13 fixture files as a miniature realistic brain (people, companies,   deals, meetings, concepts, projects, sources) following the   compiled truth + timeline format from GBRAIN_RECOMMENDED_SCHEMA.md - docker-compose.test.yml: local pgvector convenience (port 5433) - .env.testing.example: template for test credentials - package.json: add test:e2e script  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test suites + CI workflow  Tier 1 (mechanical.test.ts): 14 test suites covering all operations against real Postgres — page CRUD, search with quality scoring, links, tags, timeline, versions, admin, chunks, resolution, ingest log, raw data, files, idempotency stress, setup journey (full CLI flow), init edge cases, schema idempotency, schema diff guard, performance baselines.  Tier 1 (mcp.test.ts): MCP protocol test — spawns server, sends JSON-RPC, verifies tools/list matches operations count.  Tier 2 (skills.test.ts): OpenClaw skill tests — ingest, query, health. Skips gracefully when dependencies missing.  CI (.github/workflows/e2e.yml): Tier 1 on every PR (pgvector service), Tier 2 nightly/manual with API key secrets.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * fix: E2E test fixes + traverseGraph jsonb cast  - Fix traverseGraph query: cast json_agg to jsonb_agg so SELECT DISTINCT works - Fix put_page tests to use importFromContent with noEmbed (no OpenAI key in Tier 1) - Fix get_health assertion (page_count not total_pages) - Fix raw_data test to handle JSONB string/object return - Simplify MCP test to verify tool generation directly - Add timeouts to CLI subprocess tests - Use port 5434 for docker-compose (5433 often in use)  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * docs: update all project docs for E2E test suite  - CLAUDE.md: updated test count (9 unit + 3 E2E), added E2E test   instructions, fixed skill count to 8 - CONTRIBUTING.md: updated project structure with test/e2e/, added E2E   test instructions, rewrote \"Adding a new command\" to reflect   contract-first architecture (add to operations.ts, done) - README.md: fixed table count (10 not 9), added recommended schema doc   to Docs section, added E2E instructions to Contributing section - CHANGELOG.md: added E2E test suite, docker-compose, schema loader fix,   and traverseGraph jsonb fix to v0.3.0 entry  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") [#7](https://github.com/garrytan/gbrain/pull/7) [)](https://github.com/garrytan/gbrain/commit/a86f995883605922c0ae9e437ff32e8a75727ff7 "feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (#7)  * feat: contract-first operations.ts with OperationError, dry_run, importFromContent  30 shared operations as single source of truth for CLI and MCP. - OperationError with typed error codes (page_not_found, invalid_params, etc.) - dry_run support on all mutating operations - importFromContent split from importFile with transaction wrapping - Idempotency hash now includes ALL fields (title, type, frontmatter, tags) - Config env var fallback: GBRAIN_DATABASE_URL > DATABASE_URL > config file  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: rewrite MCP server + CLI + tools-json from operations  server.ts: 233 -> ~80 lines. Tool definitions and dispatch generated from operations[]. cli.ts: shared operations auto-registered, CLI-only commands kept as manual dispatch. tools-json: generated FROM operations[], eliminating the third contract surface. Parity test verifies structural contract between operations, CLI, and MCP.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: delete 12 command files migrated to operations.ts  Handler logic for get, put, delete, list, search, query, health, stats, tags, link, timeline, and version now lives in operations.ts. Kept: init, upgrade, import, export, files, embed, sync, serve, call, config.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: init --non-interactive, upgrade verification, schema migration  - gbrain init --non-interactive --url <url> for plugin mode (no TTY required) - Post-upgrade version verification in gbrain upgrade - Drop storage_url from files table (storage_path is the only identifier)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: tool-agnostic skills + new setup skill  All 7 skills rewritten with intent-based language instead of CLI commands. Works with both CLI and MCP plugin contexts. New setup skill replaces install: auto-provision Supabase via CLI, AGENTS.md injection, target TTHW < 2 min.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: ClawHub bundle plugin, CI workflows, v0.3.0  - openclaw.plugin.json with configSchema, MCP server config, skill listing - GitHub Actions: test on push/PR, multi-platform release (macOS arm64 + Linux x64) - Version bump 0.3.0, CHANGELOG, README ClawHub section, CLAUDE.md updated  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: idempotency hash mismatch + MCP dry_run passthrough  importFromContent now passes its all-fields hash through putPage via content_hash on PageInput, so the stored hash matches the computed hash. Previously the skip-if-unchanged check never fired because the hash formulas differed.  MCP server now passes dry_run from tool params to OperationContext.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.3.0.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: schema loader handles PL/pgSQL $$ blocks  Delete the semicolon-based SQL splitter in db.ts which broke on PL/pgSQL trigger functions containing semicolons inside $$ delimiter blocks. Use single conn.unsafe(schemaSql) call instead — the postgres driver handles multi-statement SQL natively. schema.sql already uses IF NOT EXISTS / CREATE OR REPLACE for idempotency.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test infrastructure + realistic brain fixtures  Add test infrastructure for running E2E tests against real Postgres+pgvector. Includes: - test/e2e/helpers.ts: DB lifecycle, fixture import, timing, diagnostics - 13 fixture files as a miniature realistic brain (people, companies,   deals, meetings, concepts, projects, sources) following the   compiled truth + timeline format from GBRAIN_RECOMMENDED_SCHEMA.md - docker-compose.test.yml: local pgvector convenience (port 5433) - .env.testing.example: template for test credentials - package.json: add test:e2e script  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test suites + CI workflow  Tier 1 (mechanical.test.ts): 14 test suites covering all operations against real Postgres — page CRUD, search with quality scoring, links, tags, timeline, versions, admin, chunks, resolution, ingest log, raw data, files, idempotency stress, setup journey (full CLI flow), init edge cases, schema idempotency, schema diff guard, performance baselines.  Tier 1 (mcp.test.ts): MCP protocol test — spawns server, sends JSON-RPC, verifies tools/list matches operations count.  Tier 2 (skills.test.ts): OpenClaw skill tests — ingest, query, health. Skips gracefully when dependencies missing.  CI (.github/workflows/e2e.yml): Tier 1 on every PR (pgvector service), Tier 2 nightly/manual with API key secrets.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * fix: E2E test fixes + traverseGraph jsonb cast  - Fix traverseGraph query: cast json_agg to jsonb_agg so SELECT DISTINCT works - Fix put_page tests to use importFromContent with noEmbed (no OpenAI key in Tier 1) - Fix get_health assertion (page_count not total_pages) - Fix raw_data test to handle JSONB string/object return - Simplify MCP test to verify tool generation directly - Add timeouts to CLI subprocess tests - Use port 5434 for docker-compose (5433 often in use)  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * docs: update all project docs for E2E test suite  - CLAUDE.md: updated test count (9 unit + 3 E2E), added E2E test   instructions, fixed skill count to 8 - CONTRIBUTING.md: updated project structure with test/e2e/, added E2E   test instructions, rewrote \"Adding a new command\" to reflect   contract-first architecture (add to operations.ts, done) - README.md: fixed table count (10 not 9), added recommended schema doc   to Docs section, added E2E instructions to Contributing section - CHANGELOG.md: added E2E test suite, docker-compose, schema loader fix,   and traverseGraph jsonb fix to v0.3.0 entry  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") | 4 months agoApr 9, 2026 |
| [.gitattributes](https://github.com/garrytan/gbrain/blob/master/.gitattributes ".gitattributes") | [.gitattributes](https://github.com/garrytan/gbrain/blob/master/.gitattributes ".gitattributes") | [fix(build): force LF for Markdown too (frontmatter parsers anchor on …](https://github.com/garrytan/gbrain/commit/64c191b1d7398869ce12b603ef07d161220bcc69 "fix(build): force LF for Markdown too (frontmatter parsers anchor on LF) (#3517)  Co-Authored-By: Diego <diegodearagao@gmail.com>") | 5 days agoJul 31, 2026 |
| [.gitignore](https://github.com/garrytan/gbrain/blob/master/.gitignore ".gitignore") | [.gitignore](https://github.com/garrytan/gbrain/blob/master/.gitignore ".gitignore") | [fix(repo): untrack node\_modules symlink, guard against tracked symlin…](https://github.com/garrytan/gbrain/commit/2a17a4dab506c44bb25f5029a2b461af8ec91d24 "fix(repo): untrack node_modules symlink, guard against tracked symlinks (#3463)  Commit faf5cdba tracked `node_modules -> /tmp/fleet/repo/node_modules`. That path exists only on the sandbox that produced it, so every other clone materialized a dangling symlink and `bun install` aborted with `ENOENT: could not open the \"node_modules\" directory`. That also broke `gbrain upgrade` on bun-link installs, which shells out to bun install and then prints a manual fallback that fails identically.  Three changes:  - Untrack the symlink (`git rm --cached node_modules`). - Drop the trailing slash from the .gitignore node_modules patterns. A   `node_modules/` pattern matches directories only, which is why a   symlink of the same name was never ignored in the first place. - Add scripts/check-no-tracked-symlinks.sh, wired into `bun run verify`   and `check:all`. The .gitignore fix alone is not sufficient, since   `git add -f` bypasses it; the guard fails on any mode-120000 entry.   The repo has no legitimate tracked symlinks, so it starts with an   empty allowlist.  Covered by test/no-tracked-symlinks-guard.test.ts, which builds a throwaway repo containing the exact symlink shape and asserts the guard exits 1 and names the offender.  Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>") | last weekJul 28, 2026 |
| [.gitleaks.toml](https://github.com/garrytan/gbrain/blob/master/.gitleaks.toml ".gitleaks.toml") | [.gitleaks.toml](https://github.com/garrytan/gbrain/blob/master/.gitleaks.toml ".gitleaks.toml") | [security: pin GitHub Actions, add gitleaks CI, harden permissions (v0…](https://github.com/garrytan/gbrain/commit/eb218a96ad46124f1f20559ebca7972a617f6967 "security: pin GitHub Actions, add gitleaks CI, harden permissions (v0.4.2) (#23)  * security: pin GitHub Actions to commit SHAs, add gitleaks CI  - Pin all 5 actions (checkout, setup-bun, upload-artifact, download-artifact,   action-gh-release) to commit SHAs across 3 workflow files - Add permissions: contents: read to test.yml and e2e.yml - Add gitleaks secret scanning job to test.yml - Pin openclaw install to v2026.4.9 in e2e.yml  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * security: add .gitleaks.toml config  Allowlists test fixtures, example env files, and skill documentation to prevent false positives from the gitleaks CI step.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add GitHub Actions SHA maintenance rule to CLAUDE.md  Instructs /ship and /review to check for stale SHA pins and update them, keeping action versions fresh without manual effort.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add S3 Sig V4 TODO from CSO audit  Deferred from security audit. S3 storage backend accepts credentials but sends unsigned requests. Implement when S3 becomes a real deployment path.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.4.2)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") | 4 months agoApr 10, 2026 |
| [AGENTS.md](https://github.com/garrytan/gbrain/blob/master/AGENTS.md "AGENTS.md") | [AGENTS.md](https://github.com/garrytan/gbrain/blob/master/AGENTS.md "AGENTS.md") | [v0.42.43.0 feat(context): push-based context (](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") [#2095](https://github.com/garrytan/gbrain/issues/2095) [) \+ teardown-exit …](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | 2 months agoJun 14, 2026 |
| [CHANGELOG.md](https://github.com/garrytan/gbrain/blob/master/CHANGELOG.md "CHANGELOG.md") | [CHANGELOG.md](https://github.com/garrytan/gbrain/blob/master/CHANGELOG.md "CHANGELOG.md") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [CLAUDE.md](https://github.com/garrytan/gbrain/blob/master/CLAUDE.md "CLAUDE.md") | [CLAUDE.md](https://github.com/garrytan/gbrain/blob/master/CLAUDE.md "CLAUDE.md") | [fix(engine): enforce static engine-live import boundaries (](https://github.com/garrytan/gbrain/commit/945fed61055ffbbf65d2d420cd9b9508452eacb0 "fix(engine): enforce static engine-live import boundaries (#3596)  * docs: design engine dynamic-import reconciliation  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): reconcile dynamic import hardening  Co-Authored-By: Claude <noreply@anthropic.com>  * test(engine): guard dynamic import policy  * docs: plan engine dynamic-import reconciliation  Record the approved TDD sequence for selective engine-path hardening, repository guard wiring, documentation, and local verification. Preserve the no-version-bump and no-publication boundaries for the remaining work.  Co-Authored-By: Claude <noreply@anthropic.com>  * docs(engine): record static import invariant  * fix(engine): parse block comments in import guard  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): parse dynamic imports with TypeScript  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close import guard bypasses  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close parser guard edge cases  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): aggregate parser diagnostics  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): bound dynamic import marker directive  Require the line-level opt-out marker to be standalone inside real comment trivia so negated or incidental longer tokens cannot authorize an import. Preserve the existing general marked-line contract and pin it with focused regression coverage.  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close Unicode marker boundary bypasses  Treat Unicode identifier continuations as marker-token characters and inspect adjacent text by code point so supplementary-plane characters cannot turn longer comment tokens into approvals.\n\nCo-Authored-By: Claude <noreply@anthropic.com>  ---------  Co-authored-by: Claude <noreply@anthropic.com>") [#3596](https://github.com/garrytan/gbrain/pull/3596) [)](https://github.com/garrytan/gbrain/commit/945fed61055ffbbf65d2d420cd9b9508452eacb0 "fix(engine): enforce static engine-live import boundaries (#3596)  * docs: design engine dynamic-import reconciliation  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): reconcile dynamic import hardening  Co-Authored-By: Claude <noreply@anthropic.com>  * test(engine): guard dynamic import policy  * docs: plan engine dynamic-import reconciliation  Record the approved TDD sequence for selective engine-path hardening, repository guard wiring, documentation, and local verification. Preserve the no-version-bump and no-publication boundaries for the remaining work.  Co-Authored-By: Claude <noreply@anthropic.com>  * docs(engine): record static import invariant  * fix(engine): parse block comments in import guard  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): parse dynamic imports with TypeScript  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close import guard bypasses  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close parser guard edge cases  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): aggregate parser diagnostics  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): bound dynamic import marker directive  Require the line-level opt-out marker to be standalone inside real comment trivia so negated or incidental longer tokens cannot authorize an import. Preserve the existing general marked-line contract and pin it with focused regression coverage.  Co-Authored-By: Claude <noreply@anthropic.com>  * fix(engine): close Unicode marker boundary bypasses  Treat Unicode identifier continuations as marker-token characters and inspect adjacent text by code point so supplementary-plane characters cannot turn longer comment tokens into approvals.\n\nCo-Authored-By: Claude <noreply@anthropic.com>  ---------  Co-authored-by: Claude <noreply@anthropic.com>") | last weekJul 29, 2026 |
| [CONTRIBUTING.md](https://github.com/garrytan/gbrain/blob/master/CONTRIBUTING.md "CONTRIBUTING.md") | [CONTRIBUTING.md](https://github.com/garrytan/gbrain/blob/master/CONTRIBUTING.md "CONTRIBUTING.md") | [v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1…](https://github.com/garrytan/gbrain/commit/aecb33e795cc4806f760446c55ab1c350194ddc8 "v0.42.73.1 revert(ci): remove the PR gate and withdraw the v0.42.72.1 contribution requirements (#3805)  * v0.42.73.1 revert(ci): remove the PR gate — it cannot function on this repository  The gate needs an ANTHROPIC_API_KEY Actions secret for its verdict and read-write workflow permissions to post a comment or set a label. This repository grants neither, and both are admin-only, so it can only ever skip.  It also caused a real incident before that was understood: a read-only token turned every API call into a 403, the code treated that as a crash, and the check went red on an outside contributor's PR four times with no comment explaining why. v0.42.73.0 fixed the crash, but a check that runs on every PR and can never reach a verdict does not earn a place in the repo.  Removes the workflow, the script, its type surface, and its test file. The code is preserved in git history at v0.42.73.0. If it is ever restored, the mechanical half (intent/screenshot policy, title rule, red flags) should render to the Actions job summary rather than a comment — that needs no token permission and no API key.  CONTRIBUTING.md's intent-paragraph and screenshot requirement is unchanged and stands as written; it is enforced by maintainers reading PRs, as before.  typecheck clean, verify 34/34, llms bundles regenerated.  * v0.42.73.1 revert(docs): withdraw the human-intent-paragraph + screenshot contribution requirement  This reverts commit 6d1232d5a67c9ab7086d00aaa4d362324919fb3d (v0.42.72.1).  CONTRIBUTING.md, both issue templates, and the pull-request template return to their pre-2026-08-02 state. VERSION/package.json/CHANGELOG keep moving forward (0.42.73.1); the v0.42.72.1 CHANGELOG entry stays as historical record, and the 0.42.73.1 entry now describes the withdrawal.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |
| [DESIGN.md](https://github.com/garrytan/gbrain/blob/master/DESIGN.md "DESIGN.md") | [DESIGN.md](https://github.com/garrytan/gbrain/blob/master/DESIGN.md "DESIGN.md") | [v0.36.1.0 Hindsight calibration wave: brain learns how you tend to be…](https://github.com/garrytan/gbrain/commit/3a0e1116e76b1be45a136cf184d1a49d431b6b60 "v0.36.1.0 Hindsight calibration wave: brain learns how you tend to be wrong (#1139)  * schema: v0.36.0.0 Hindsight calibration tables (migrations v67-v71)  Foundation commit for the Hindsight-inspired calibration wave. Adds four new tables + one perf index, all source-scoped from day 1 per v0.34.1 discipline:  - calibration_profiles (v67): per-holder LLM-narrative aggregation of   TakesScorecard data. published BOOL gates E8 cross-brain mount sharing   (default false). grade_completion REAL surfaces partial-grade state to   the dashboard. active_bias_tags TEXT[] with GIN index feeds E3 (calibration-   aware contradictions) and E7 (real-time nudge matching).  - take_proposals (v68): propose_takes phase queue. Idempotency cache via   (source_id, page_slug, content_hash, prompt_version) unique index mirrors   the v0.23 dream_verdicts pattern. proposal_run_id supports --rollback by   run. dedup_against_fence_rows JSONB audit column records what canonical   takes the LLM was told to dedupe against at proposal time.  - take_grade_cache (v69): grade_takes verdict cache. Composite PK on   (take_id, prompt_version, judge_model_id, evidence_signature) — prompt   edits OR evidence changes cleanly invalidate prior verdicts. applied=false   default + auto-resolve-off-by-default (D17) means every fresh install   needs operator opt-in before grade verdicts mutate the takes table.  - take_nudge_log (v70): E7 nudge cooldown state. Polymorphic FK — a nudge   fires on either a canonical take OR a pending proposal (CDX-5 fix). CHECK   constraint enforces exactly-one-set. channel column lets future routing   (webhook, admin SPA toast) reuse the same cooldown semantics.  - takes_resolved_at_idx (v71): partial index for the Brier-trend   aggregation queries. Engine-aware handler — Postgres uses CONCURRENTLY   to avoid the ShareLock; PGLite uses plain CREATE.  Every table carries wave_version TEXT NOT NULL DEFAULT 'v0.36.0.0' so the v0.36.0.0 calibration --undo-wave command (lands later in the wave) can reverse just this wave's writes.  Plan: ~/.claude/plans/system-instruction-you-are-working-rippling-knuth.md covers the design rationale (D17/D18/D21 + CDX findings).  Schema parity: - src/schema.sql for fresh Postgres installs - src/core/pglite-schema.ts for fresh PGLite installs - src/core/schema-embedded.ts auto-regenerated from schema.sql - src/core/migrate.ts for upgrade-in-place from older brains  VERSION bumped to 0.36.0.0 for the wave. CHANGELOG entry lands at /ship.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * core: BaseCyclePhase abstract class enforces source-scope + budget contracts  D21 from the eng review. Three new v0.36.0.0 cycle phases (propose_takes, grade_takes, calibration_profile) share enough structure that the duplication-vs-abstraction trade tips toward a shared base. Without this scaffold, source-isolation discipline would drift exactly the way it drifted in v0.34.1 — except this time across three new surfaces at once.  What this enforces:  1. Phase signature is uniform: run(ctx, opts) → PhaseResult.  2. ctx.sourceId / ctx.auth.allowedSources MUST be threaded through every    engine call. The base class surfaces a scope() helper that wraps    sourceScopeOpts(ctx) and is the only sanctioned way to read source-    scoped data. Forgetting to thread source scope becomes a TypeScript    compile error, not a runtime leak. Closes the v0.34.1 leak class    structurally for every new phase.  3. Budget meter wraps run() automatically. Subclass declares budgetUsdKey    + budgetUsdDefault; base reads the resolved cap from config and creates    the BudgetMeter. Subclass calls this.checkBudget() before each LLM    submit; budget-exhausted phase still returns status='ok' (clean abort)    so the cycle report shows partial completion, not failure.  4. Error envelope is uniform. Thrown errors get caught and converted to    status='fail' with a phase-specific error.code via the subclass's    mapErrorCode() hook.  5. Progress reporter integration. Base accepts the reporter via opts;    subclasses call this.tick() instead of touching the reporter directly,    so the phase name in the progress stream is always correct.  Tests: 13 cases in test/core/base-phase.test.ts cover source-scope threading (5 cases including the empty-allowedSources-MUST-NOT-widen-scope regression), PhaseResult shape including the error envelope path (3 cases), dry-run propagation (2 cases), and budget meter construction (3 cases including config-key override).  Synthesize.ts / patterns.ts (existing pre-v0.36 phases) deliberately do NOT retrofit to this base in v0.36.0.0 — too much churn for a refactor that doesn't pay off until v0.37+. Future phases use this by default.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cycle: propose_takes phase + take_proposals queue write path (T3)  LLM-based take extraction from markdown prose. Walks pages updated since last cycle, sends each page's body to a tuned extractor, writes the extracted gradeable claims to the take_proposals queue. User accepts / rejects via `gbrain takes propose --review` (lands in Lane C).  Cycle wiring:   lint → backlinks → sync → synthesize → extract → extract_facts →     resolve_symbol_edges → patterns → recompute_emotional_weight →     consolidate → propose_takes (NEW) → grade_takes (NEW; T4) →     calibration_profile (NEW; T6) → embed → orphans → purge  CyclePhase enum extended with 3 new entries; ALL_PHASES + NEEDS_LOCK_PHASES updated. All three new phases acquire the cycle lock (writes to take_proposals / take_grade_cache / calibration_profiles).  Idempotency contract:   The (source_id, page_slug, content_hash, prompt_version) composite unique   index on take_proposals means an unchanged page never re-spends LLM   tokens. Bumping PROPOSE_TAKES_PROMPT_VERSION cleanly invalidates the   cache so a tuned prompt re-runs proposals on every page. Mirrors the   v0.23 dream_verdicts pattern.  F2 fence dedup:   The phase reads the page's existing `<!-- gbrain:takes:begin -->` fence   (when present) and passes the canonical take rows to the extractor as   \"things you have already captured.\" Prevents duplicate proposals when   prose is appended to a page that already has takes. Records the fence   rows the LLM was told to dedupe against on the take_proposals row for   audit (dedup_against_fence_rows JSONB).  Auto-resolve posture:   propose_takes only WRITES proposals to the queue. Nothing in this phase   mutates the canonical takes table. Operator opt-in via the queue review   CLI (Lane C) is the only path from queue to canonical fence (D17).  Prompt tuning status (v0.36.0.0 ship state):   The default extractor prompt is annotated `v0.36.0.0-stub`. The real   tuned prompt arrives via T19 synthetic corpus build (50 anonymized   pages, 3-model parallel extraction, user reviews disagreement set,   F1 ≥ 0.85 on training corpus + F1 ≥ 0.8 on ground-truth holdout).   Until T19 lands, propose_takes runs but produces best-effort candidates   the user reviews manually.  Architecture:   ProposeTakesPhase extends BaseCyclePhase (T2). Inherits source-scope   threading via scope(), budget metering via this.checkBudget(), error   envelope wrapping. budgetUsdKey: cycle.propose_takes.budget_usd   (default $5/cycle). Budget exhaustion mid-page returns status='warn'   with details.budget_exhausted=true — clean partial-completion semantics.    Test seam: opts.extractor injection so the phase can run hermetically   without touching the gateway. defaultExtractor (production path) calls   gateway.chat with the EXTRACT_TAKES_PROMPT and parses the JSON array   output via parseExtractorOutput.    parseExtractorOutput defends against common LLM output sins: markdown   code fence wrapping, leading prose, single-object instead of array,   unknown kind values, weight out of [0,1], rows missing claim_text or   exceeding 500 chars.  Tests: 25 cases in test/propose-takes.test.ts cover the 4 pure helpers (parseExtractorOutput, contentHash, hasCompleteFence, extractExistingTakesForDedup) + 7 phase integration scenarios (happy path, cache hit, fence dedup, extractor failure, empty pages, skipPagesWithFence, proposal_run_id stability).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cycle: grade_takes phase + take_grade_cache verdict pipeline (T4)  Walks unresolved takes that are old enough to have outcome data, retrieves evidence from the brain, asks a judge model to verdict each one. Writes verdicts to take_grade_cache. Optionally — only when operator has flipped the opt-in config flag — auto-applies high-confidence verdicts to the canonical takes table via engine.resolveTake.  Auto-resolve posture (D17 — DISABLED by default):   On a fresh install, grade_takes runs and writes verdicts to the cache,   but applied=false on every row. Operator reviews the queue, then flips   `cycle.grade_takes.auto_resolve.enabled: true` once trust is earned.   Mirrors the propose_takes review-queue posture: queue exists, mutation   requires explicit opt-in.  Conservative threshold (D12):   When auto_resolve.enabled is true, a verdict auto-applies only when   confidence >= 0.95 (single-judge path). T5 ensemble path lands next,   tightening this further with 3/3 unanimous requirement.    'unresolvable' verdict NEVER auto-applies even at confidence=1.0 —   there's no canonical column for \"we tried and there's no evidence yet.\"  Evidence retrieval status (v0.36.0.0 ship state):   The default evidence retriever returns an \"evidence-retrieval not yet   wired\" placeholder. Most verdicts produced by the stub-judge against   the stub-evidence will be 'unresolvable'. Real retrieval (hybrid search   over pages newer than the take's since_date, optionally augmented by a   gateway web-search recipe in v0.37+) lands as a follow-up. Documented   limitation per CDX-8 + D17 — the phase ships now so the wiring is real   and the cache table accumulates verdicts even if early ones are   conservative.  Cache key:   Composite primary key on take_grade_cache is   (take_id, prompt_version, judge_model_id, evidence_signature). Prompt   edits OR evidence changes OR judge swap cleanly invalidate prior   verdicts. Mirrors the v0.32.6 eval_contradictions_cache pattern.    evidence_signature = SHA-256 of (judge_model_id + '|' + evidence_text)   so identical evidence under a different judge does NOT collide.  Architecture:   GradeTakesPhase extends BaseCyclePhase. Inherits source-scope threading,   budget metering (cycle.grade_takes.budget_usd, default $3/cycle), error   envelope. Test seam: opts.judge + opts.evidenceRetriever injection so   the phase runs hermetically.    parseJudgeOutput defends against fence-wrapping, leading prose,   out-of-range confidence (clamps to [0,1]), invalid verdict labels,   oversized reasoning (truncated at 400 chars). Returns null on   unrecoverable parse — caller treats null as \"judge_output_parse_failed   / unresolvable at confidence 0.0\" so the row still lands in cache with   the parse failure surfaced via warnings.    takeIsOldEnough gates on since_date (default 6 months). Tolerates   YYYY-MM-DD and YYYY-MM formats. Returns false on null/unparseable   since_date so takes without dates never get graded (we'd be   hallucinating temporal context).  Tests: 23 cases covering parseJudgeOutput (7 cases), evidenceSignature (3), takeIsOldEnough (5), and 8 phase integration scenarios — happy path, D17 auto-resolve-off default, D12 above-threshold auto-apply, below- threshold cache-only, unresolvable-NEVER-applies, cache hit, too-recent gate, judge-throw warning.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cycle: grade_takes ensemble tiebreaker for borderline verdicts (T5 / E2)  Multi-judge ensemble tiebreaker, additive on top of T4's single-judge foundation. Reuses gateway.chat as the per-model judge interface; runs three judges in parallel via Promise.allSettled. Pure aggregation logic in aggregateEnsemble() — no SQL, no LLM, hermetically testable.  When ensemble fires (T5 trigger band):   Only when ALL of:     - opts.useEnsemble === true (default false)     - opts.ensembleJudges array is non-empty     - single-model confidence in [0.6, 0.95) (configurable via       opts.ensembleTriggerBand)     - single-model verdict !== 'unresolvable'    Above 0.95 the single judge is already sufficient (T4 path). Below 0.6   the verdict is clearly review-only — ensemble wouldn't change the   posture. 'unresolvable' from single-judge means no evidence yet; calling   three more judges on the same evidence won't manufacture some.  Conservative auto-apply (D12):   Ensemble verdict auto-applies via engine.resolveTake only when ALL of:     - autoResolve === true (operator opt-in per D17)     - ensemble.agreement === 3 (3/3 unanimous)     - ensemble.minConfidence >= ensembleThreshold (default 0.85)     - winning verdict !== 'unresolvable'    Schema-level monotonic-tightening guard for ensembleThreshold lives in   the takes resolution layer.  Cache identity:   When ensemble fires, the cache row's judge_model_id becomes   'ensemble:<modelA>+<modelB>+<modelC>' — a future re-run with different   ensemble membership doesn't collide with prior verdicts. evidence_signature   is recomputed because it includes the judge_model_id.  aggregateEnsemble (pure):   - 3/3 unanimous → agreement=3, minConfidence=min across the three   - 2/3 majority → agreement=2, minConfidence across the agreeing two   - 1/1/1 disagreement → tie-break: prefer non-'unresolvable', then     alphabetical for determinism   - 'unresolvable' from one model NEVER tips a 2-vote majority toward     'unresolvable' — by-label tally only counts a model toward its own     label   - All three judges failing (allSettled rejected) → verdict='unresolvable'     with agreement=0; auto-apply path blocked   - Single judge survives + two fail → agreement=1; the lone verdict wins     but auto-apply gated by the 3/3 requirement  Tests: 16 cases.   aggregateEnsemble (6): 3/3, 2/3, 1/1/1, unresolvable-tipping-resistance,   all-failed, partial-failed-but-survives.   Phase trigger conditions (5): useEnsemble=false default, useEnsemble=true   in borderline band, single >= 0.95 skip, single < 0.6 skip, single =   'unresolvable' skip.   Phase auto-apply rules (5): 3/3+threshold+autoResolve, 2/3 majority no   apply, 3/3 below threshold no apply, one ensemble judge throws still   aggregates from allSettled, empty ensembleJudges falls through to   single.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cycle: calibration_profile phase + shared voice gate across surfaces (T6)  The calibration narrative layer. Reads TakesScorecard, asks an LLM to write 2-4 conversational pattern statements (\"right on tactics, late on macro by 18 months\"), passes them through the voice gate, derives active bias tags, writes the row to calibration_profiles. This is the read-side that E1 (think anti-bias rewrite), E3 (contradictions join), E6 (dashboard), and E7 (real-time nudges) all consume.  Voice gate (D24 — single function, multiple surfaces):   ALL five calibration UX surfaces import the same gateVoice() function   from src/core/calibration/voice-gate.ts. Mode parameter   ('pattern_statement' | 'nudge' | 'forecast_blurb' | 'dashboard_caption'   | 'morning_pulse') drives surface-specific tuning via the rubric the   gate ships to its Haiku judge. NO forked implementations — voice   rubric drift would defeat the gate.    Each mode's rubric explicitly forbids preachy / clinical / corporate   voice; a structural test pins this. Anchors the cross-cutting voice   rule from /plan-ceo-review D2-D8.  Fallback policy (D11):   Up to 2 generation attempts (configurable). On both rejects → fall back   to a hand-written template from src/core/calibration/templates.ts.   Templates are intentionally short and a little \"robotic\" — they're the   safety net, not the destination. voice_gate_passed=false +   voice_gate_attempts get persisted on the calibration_profiles row so   the operator can review the failing examples and tune the rubric over   time. Suppressing the surface silently is NEVER an option — that's how   voice quality silently degrades.    parseJudgeOutput defaults to 'academic' on parse failure (NEVER passes   pass-through) so a Haiku output garble falls through to the template   rather than letting unverified text reach the user.  calibration_profile phase:   Extends BaseCyclePhase. Cold-brain skip: <5 resolved takes → no row   written, no LLM call. Otherwise: scorecard via engine.getScorecard()   → patterns via voice-gated generator → bias tags via separate   generator (best-effort; failure logs warning, phase continues).    The DB INSERT lands in the v67 calibration_profiles row with   source_id, holder, the patterns, voice gate audit fields, active bias   tags, and grade_completion (F1 fix — partial-grade state surfaces to   the dashboard \"60% graded\" badge).    Budget gate at $0.50/cycle default (mostly Haiku). Below-budget   before-LLM-call check returns status='warn' without writing the row.    Per-domain scorecards are a placeholder for v0.36.0.0 ship state —   the F12 batchGetTakesScorecards() engine method that powers per-domain   rendering lands in Lane C alongside the CLI/MCP surface.  Architecture:   parsePatternStatementsOutput is tolerant of LLM emitting numbered   lists / bulleted lines despite the prompt asking for plain lines.   Caps at 4 patterns + drops excessively long lines (>200 chars).    parseBiasTagsOutput lowercases input + drops non-kebab-case tokens   (defends against the LLM emitting \"Over-Confident Geography\" with   spaces or capitals). Caps at 4 tags.  Tests: 43 cases across two new test files.   voice-gate.test.ts (24): parseJudgeOutput (7), gateVoice happy path   (3), fallback path (5), mode parity (2), templates (7).   calibration-profile.test.ts (19): parsers (10), pickFallbackSlots   (3), phase integration (6 — cold-brain skip, happy path, voice gate   fallback, grade_completion plumbed through, bias-tags failure   non-fatal, source_id scope reaches INSERT).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cli: gbrain calibration + get_calibration_profile MCP op (T7)  Public-facing read surface for the v0.36.0.0 calibration wave. CLI prints the active calibration profile; MCP op exposes the same data path for agents. Mirror of the v0.29 salience/anomalies shape (pure data fn + JSON formatter + human formatter + thin CLI dispatch).  CLI: `gbrain calibration`   Flags:     --holder <id>         specific holder (default 'garry')     --json                machine output for piping     --regenerate          run calibration_profile phase now     --undo-wave <ver>     [placeholder — wires in Lane D / T17]     ab-report             [placeholder — wires in Lane D / T18]    Human output:     Calibration profile — holder: garry, source: default     Generated: <local timestamp>     [Note: built on 60% graded — partial completion this cycle.]   (when grade_completion < 0.9)     [Note: voice gate fell back to template (2 attempts).]         (when voice_gate_passed=false)      Resolved: 12 takes     Brier:    0.210 (lower is better)     Accuracy: 60.0%     Partial:  10.0%      Pattern statements:       • You called early-stage tactics well — 8 of 10 held up.      Active bias tags: over-confident-geography    Cold-brain fallback message names the exact dream command to run.  MCP: `get_calibration_profile` (scope: read)   Param: holder?: string (defaults to 'garry')   Returns: latest CalibrationProfileRow | null    Source-scoping via sourceScopeOpts(ctx): scalar source-bound clients see   only their source; federated_read scopes see the union of allowed sources;   no source filter when neither is set (CLI default path).    Throws GBrainError('INVALID_HOLDER') on empty/non-string holder so   remote callers get a structured error instead of a SQL-shape failure.  Architecture:   getLatestProfile is the pure data fn — engine + opts → CalibrationProfileRow | null.   Reused by both the CLI and the MCP op. Source-scoped via the standard   v0.34.1 spread pattern (scalar sourceId vs sourceIds array).    formatProfileText is pure — null → cold-brain message, populated → full   printout. Annotates partial-grade rows and voice-gate-fallback rows so   the operator sees data-quality status inline.    parseArgs is exported via __testing for unit coverage. Sub-command   ('ab-report') vs flag distinction is intentional — keeps the surface   parallel with `gbrain eval cross-modal` etc.  Tests: 21 cases.   parseArgs (6 cases): empty, --holder, --json, --regenerate, --undo-wave, ab-report.   getLatestProfile (5 cases): happy, null, scalar source scope, federated array     scope, no-source-filter default.   formatProfileText (5 cases): cold-brain, happy, partial-grade note, voice-fallback     note, published-to-mounts note.   getCalibrationProfileOp (5 cases): default holder, scalar source scope,     federated scope union, returns-null-on-unknown-holder, throws on empty holder.  Lane D follow-ups: --undo-wave (T17) and ab-report (T18) print a clear \"lands in Lane D\" stderr line + exit 2; the surfaces exist for early testers, the implementations land next.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * think: --with-calibration + anti-bias prompt rewrite (T8 / E1, D22)  Optional anti-bias rewrite mode for `gbrain think`. When set, the active calibration profile gets injected per the D22 placement spec (AFTER retrieval evidence, BEFORE the user's question). The bias filter applies to QUESTION FRAMING, not evidence interpretation — matches LLM-as-judge best practice (bias prompts near end of context perform better).  Default behavior unchanged (R1 regression guard): omitting --with-calibration produces the v0.28-vintage user-message shape with the question first, then retrieval. Existing think users see no change.  Two user-message shapes in buildThinkUserMessage:    Default (no calibration):     Question: X     <pages>...</pages>     <takes>...</takes>     <graph>...</graph>     Respond with a single JSON object...    With calibration (D22):     <pages>...</pages>     <takes>...</takes>     <graph>...</graph>     <calibration holder=\"garry\">       Track record: Brier 0.210 (lower is better).       Active patterns:         - You called early-stage tactics well — 8 of 10 held up.       Active bias tags: over-confident-geography     </calibration>     Question: X     Respond...    Calibration block is built by buildCalibrationBlock (exported for the   E3 contradictions probe to render the same shape).  System prompt extension (withCalibration:true):   - Names BOTH the user's PRIOR (default reasoning) AND the COUNTER-PRIOR     from their hedged-domain self.   - References active bias tags by name when relevant (\"this fits the     over-confident-geography pattern\").   - Does NOT silently substitute the debiased answer. ALWAYS surfaces     both priors transparently.   - Adds a \"Calibration\" section between Conflicts and Gaps in the     answer body.  RunThinkOpts extension:   - withCalibration?: boolean — opt-in   - calibrationHolder?: string — defaults to 'garry'    When withCalibration=true and no profile exists, runThink falls back to   baseline behavior + pushes NO_CALIBRATION_PROFILE to warnings (visible   to the operator). When the calibration fetch fails, CALIBRATION_FETCH_FAILED   warning surfaces with the underlying error. Either path keeps think working;   the calibration loop is enhancement, not requirement.  CLI: `gbrain think \"<q>\" --with-calibration [--calibration-holder <id>]`  Tests: 11 cases.   buildThinkSystemPrompt (4 cases): R1 regression — default/false/omitted   → no anti-bias rules; with calibration → adds PRIOR + COUNTER-PRIOR +   bias-tag reference; preserves existing hard rules.    buildCalibrationBlock (3 cases): happy path, null brier omitted (not   \"Brier null\"), empty patterns + tags still well-formed.    buildThinkUserMessage (4 cases): R1 regression — without calibration:   question first; D22 placement — retrieval → calibration → question →   instruction; graph + calibration ordering; empty retrieval blocks render   placeholders without breaking shape.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * contradictions: calibration-profile join (T9 / E3)  Cross-references each contradiction finding against the active calibration profile. When a contradiction's domain matches an active bias tag (e.g. \"over-confident-geography\" or \"late-on-macro-tech\"), the output gains a one-line bias context explaining which pattern this fits.  Pure functions only — no DB writes, no LLM calls. The probe runner imports tagFindingWithCalibration() and applies it to each finding before emitting. When no profile exists or no tags match, the helper returns null and the runner emits the unchanged finding (regression R2 — contradictions output is byte-identical to v0.32.6 when no calibration profile is present).  Match heuristic (v0.36.0.0 ship-state):   Bias tags are kebab-case axis-then-domain slugs ('over-confident-geography').   computeDomainHint() extracts a domain hint from the finding's slugs +   holder + verdict text:     - wiki/companies/... → hiring | market-timing     - wiki/people/... → founder-behavior     - macro / geography / tactics / ai segments in slug → matching tag   First-match-wins for ordering determinism.    Match is intentionally fuzzy — the v0.32.6 contradictions probe doesn't   yet carry structured domain metadata. v0.37+ structured-domain-on-takes   (Hindsight-style enum) tightens this.  Output:   Returns { bias_tag: string, context: string } | null.   Context format: \"This contradiction fits your active bias pattern   \\"<tag>\\" (Brier 0.31). Verdict: contradiction; severity: medium.   Consider reviewing both sides through the lens of that pattern.\"  Tests: 13 cases.   R2 regression (2): null profile → null tag; empty active_bias_tags → null tag.   computeDomainHint (5): companies / people / macro / geography / unknown   paths produce expected hints.   Match path (4): macro→late-on-macro-tech, geography→over-confident-geography,   mismatch returns null, first-match-wins with multiple candidate tags.   buildBiasContextString (2): emits tag+verdict+severity+Brier; omits   Brier when null (no \"Brier null\" leak).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: Brier-trend forecast at write time (T10 / E5)  Pure math layer over existing TakesScorecard data. Zero new LLM cost, zero new schema. Surfaces the user's historical Brier for the take's (holder, domain) bucket at write time so they see \"your historical Brier in macro takes is 0.31\" before committing the take.  Voice-gate-rendered output:   The user-facing string goes through gateVoice mode='forecast_blurb' via   templates.ts (already in T6). This module is the pure data layer; the   template renders the math into the conversational voice.  v0.36.0.0 ship state:   Bucket dimension is the DOMAIN (slug-prefix). The conviction-weight   bucket dimension would need a new engine method   (engine.batchGetTakeBucketStats per F11) — deferred to v0.37+. Until   then, forecast = historical Brier in this holder's domain.    resolveDomainPrefix() keeps slug-prefix-looking domain hints   ('companies/', 'wiki/macro') and falls back to overall for free-form   hints ('macro tech', 'geography'). Hindsight-style structured domain   on takes (CDX-11 mitigation TODO) tightens this in v0.37+.  MIN_BUCKET_N = 5:   Below this sample size, the forecast returns predicted_brier=null with   insufficient_data=true. Template renders \"Forecast unavailable: only N   resolved takes at this conviction yet\" instead of a noisy estimate.  Architecture:   computeForecast(input) — pure function, takes scorecards already   fetched; ideal for tests + reuse across batched paths.   forecastForTake(engine, input) — convenience wrapper, 1-2 engine   round-trips (no domain → 1; with domain → 2).   batchForecast(engine, inputs[]) — memoizes per (holder, domainPrefix);   N inputs collapse to ≤2*unique_holders unique engine calls. Used by   the propose-queue review flow (50 candidates → 1-2 scorecard fetches).  Tests: 14 cases.   computeForecast (4): insufficient_data branch, stable forecast,     overall fallback, MIN_BUCKET_N export.   resolveDomainPrefix (5): undefined/empty/whitespace → undefined;     slug-prefix → kept; free-form → undefined.   forecastForTake (3): 1-call overall, 2-call domain, free-form fallback.   batchForecast (2): cache collapse for repeat queries; different holders     do not collapse.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: gstack-learnings coupling on incorrect resolutions (T11 / E4)  When the grade_takes phase auto-resolves a take as 'incorrect' or 'partial', optionally write a learning entry to gstack's per-project learnings.jsonl so other gstack skills (plan-ceo-review, ship, investigate, ...) can pull it as context when relevant. The brain teaches every other tool about the user's track record.  Config gate (D5 / CDX-17 mitigation):   `cycle.grade_takes.write_gstack_learnings` defaults FALSE. External   users may not have gstack installed; the gstack-learnings binary API   isn't stable yet. Garry's brain flips it true to opt in.  Quality gate:   Only 'incorrect' and 'partial' verdicts trigger the write. 'correct'   resolutions are noise (we expected the take to hold up — no learning).   'unresolvable' has no canonical column. Defense-in-depth runtime guard   in writeIncorrectResolution() rejects ineligible qualities with   reason='quality_not_eligible' so a caller misuse never surfaces a   malformed learning entry.  Auto-apply only:   Coupling fires only when grade_takes both auto-applies AND the verdict   is incorrect/partial AND the config flag is enabled. Manual resolutions   via `gbrain takes resolve` intentionally DO NOT propagate to gstack —   manual writes already carry operator intent; the calibration loop is   the noise-prone path that earns coupling.  Namespace:   Every entry's key starts with 'gbrain:calibration:v0.36.0.0:'. Lane D   `gbrain calibration --undo-wave v0.36.0.0` (T17) filters on this prefix   for the optional gstack-scrub step. First active bias tag suffixes the   key (e.g. 'take-42:over-confident-geography') so future analysis can   group learnings by bias pattern.  Architecture:   buildLearningEntry — pure. Truncates claim at 200 chars + ellipsis;   emits Pattern: line when activeBiasTags present; defaults confidence   to 0.8 when caller omits it.    writeIncorrectResolution — async wrapper. Honors config gate; honors   quality gate; calls the injected writer (or defaultGstackWriter in   production). Failures are non-fatal: returns   { written: false, reason: 'write_failed' | 'binary_missing', error }.   The grade_takes phase logs to result.warnings and continues — gstack   coupling failure NEVER aborts a cycle.    defaultGstackWriter — shells out to gstack-learnings-log binary via   execFileSync. Throws GBrainError('GSTACK_BINARY_NOT_FOUND') when the   binary isn't on PATH; writeIncorrectResolution classifies that error   to reason='binary_missing' so the operator sees the install hint   instead of a generic write_failed.    Wired into grade-takes.ts after engine.resolveTake() inside the   auto-apply block. Only fires when shouldApply=true.  Tests: 14 cases.   buildLearningEntry (7): canonical shape, partial vs incorrect wording,   bias-tag suffix, no-tag fallback, claim truncation, default confidence,   no-reasoning omission.   writeIncorrectResolution (7): config gate, quality gate, happy path,   writer-throw graceful degrade, binary-missing classification, async   writer awaited, partial quality writes.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * doctor: 4 calibration checks — abandoned/freshness/drift/voice (T12)  Adds the four calibration doctor checks per the eng-review spec.  abandoned_threads:   Counts active high-conviction takes (weight >= 0.7) older than 12 months   that have never been superseded. Signal, not error — always status='ok'   with a count. The hint sends users to `gbrain calibration` for details.  calibration_freshness:   Warns when the active profile is older than 7 days (configurable via   the same env-var pattern other freshness checks use). Cold-brain branch   (no profile yet) returns ok without scolding. Hint points at   `gbrain calibration --regenerate`.  grade_confidence_drift (CDX-11 mitigation):   Surfaces the count of auto-applied grade verdicts. Below 30: returns   \"need 30+ for drift detection\". At/above 30: returns \"drift math   arrives in v0.37+\". The surface is wired; the actual   confidence-vs-accuracy correlation math is a v0.37+ follow-up once we   have 30+ auto-applied verdicts to measure against. Closes the CDX-11   hole structurally — the operator sees the surface even before the math   is meaningful.  voice_gate_health:   Tracks voice gate failure rate over the last 7 days. <30% fail rate →   ok (template fallback is fine in isolation). >=30% → warn with hint   to review src/core/calibration/voice-gate.ts rubric. Anchors the   cross-cutting voice rule observability story.  All four checks return status='warn' with a diagnostic message on engine errors — non-blocking, never throws. Matches the existing doctor check pattern (see checkSyncFreshness for prior art).  Wired into runDoctor after checkRerankerHealth (the v0.35 cluster), in the canonical block 10 slot.  Tests: 15 cases. 4 per check (happy path, alt-status, engine-throw diagnostic, plus boundary tests for the freshness staleness gate at exactly 7 days and the grade drift gate at 30 applied verdicts).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: E7 nudge + 14-day cooldown (T13 / D16 F3)  Real-time pattern surfacing when a newly-committed high-conviction take matches an active bias pattern. Conversational nudge text via the templates module; 14-day cooldown per (take_id, nudge_pattern) via take_nudge_log to prevent the feedback loop where each cycle re-fires the same nudge on the same take.  Threshold gates (D16 F3):   - holder match (profile.holder === take.holder)   - conviction-weight > 0.7 (strict greater than)   - take's slug-derived domain hint matches an active bias tag     (takeDomainHint — same heuristic as eval-contradictions/calibration-join.ts     for cross-surface consistency)  Cooldown gate:   Before firing, probe take_nudge_log for (take_id, nudge_pattern) rows   with fired_at >= now() - 14 days. Any hit → silently skip. After firing,   insert a new row with channel='stderr' so the next 14 days are gated.  Feedback-loop prevention:   User hedges a take in response to a nudge (e.g. weight 0.85 → 0.65).   Even though the take's `weight` field changed, the cooldown row for   the over-confident-geography pattern is still there from the original   fire — so the next cycle's evaluateAndFireNudge() silently skips. The   user reset path (gbrain takes nudge --reset N) clears the cooldown to   re-arm.  Output channel (v0.36.0.0 ship state):   STDERR only. Schema's `channel` column already supports multi-channel   (webhook, admin SPA toast); routing those is a v0.37+ follow-up.  Architecture:   evaluateNudgeRule(take, profile) — pure rule check. Returns   { matched, reason, matchedTag }. No engine call.   checkCooldown(engine, takeId, pattern) — engine probe, returns boolean.   recordNudgeFire(engine, opts) — INSERT into take_nudge_log.   evaluateAndFireNudge(opts) — full pipeline. Returns NudgeDecision.   resetNudgeCooldown(engine, takeId) — DELETE...RETURNING for the CLI.    buildNudgeText delegates to templates.ts nudgeTemplate (D24 mode='nudge'   voice). v0.36.0.0 ship state uses the template directly; LLM-generated   nudge text via the voice gate lands in v0.37+ when we have production   examples to tune from.  Tests: 22 cases.   takeDomainHint (5): companies/people/macro/geography/unrecognized.   evaluateNudgeRule (6): no_profile, wrong_holder, conviction-at-threshold-   is-NOT-eligible (strict >), no matching tag, happy match,   first-match-wins for multiple candidate tags.   checkCooldown (3): true on row hit, false on no row, cutoff date param   verifies the 14-day boundary.   evaluateAndFireNudge (4): happy fire (text contains hush command +   matched tag), cooldown silent skip (no INSERT, no stderr), no_profile   short-circuit, below-conviction short-circuit (no cooldown query fired).   buildNudgeText (2): hush command shape, conviction value embedded.   resetNudgeCooldown (2): returns count, idempotent on zero rows.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: E8 team-brain sharing + D18 cross-brain query semantics (T14)  Cross-brain calibration profile resolution per the D18 4-rule contract. Pins all four cross-brain leak surfaces in dedicated unit tests so future mount features can't silently regress this security model.  D18 semantics (committed):    Rule 1 — LOCAL-FIRST ORDERING.     Query the local brain first. If a profile exists, return it. Do NOT     also query mounts (avoids stale-mount-overrides-fresh-local).     Verified: mountResolver is NOT called when local has a hit.    Rule 2 — MOUNT FALLBACK.     Only when local has no profile AND canReadMounts=true, walk the     mounts in priority order. First match wins. Each mount-side row     must have published=true to be visible (D15 asymmetric opt-in).    Rule 3 — CROSS-BRAIN ATTRIBUTION.     Every returned profile carries source_brain_id + from_mount flag.     Consumers (E1 think rewrite, E3 contradictions, E7 nudge, E6     dashboard) MUST surface this via attributionSuffix() so the user     sees which brain answered.    Rule 4 — SUBAGENT PROHIBITION.     canReadMountsForCtx() classifier returns FALSE for subagent loops     without trusted-workspace allowedSlugPrefixes. Closes the     OAuth-token-to-cross-brain-leak surface — subagents see ONLY their     local-brain results regardless of which holder they query.      Exception: trusted cycle phases (synthesize/patterns) pass     allowedSlugPrefixes set and ARE allowed to read mounts. Pinned in     the classifier test.  Architecture:   queryAcrossBrains(localEngine, opts) — pure orchestrator. Composes   getLatestProfile() from src/commands/calibration.ts. Mount engine   access is via opts.mountResolver — production wires this to the   v0.19+ gbrain mounts subsystem; tests inject a stub returning an   ordered list of mocked engines. Decouples cross-brain LOGIC from   multi-engine PLUMBING.    canReadMountsForCtx(ctx) — pure classifier table. Drives the rule-4   gate. Production callers compose it from OperationContext.    attributionSuffix(result) — pure formatter. Emits the \"(from mounted   brain: <id>)\" suffix when from_mount=true; empty string when local.   Mandatory for user-visible cross-brain consumers.  Tests: 15 cases pinned to the 4 D18 rules + 4 supplementary structural checks.   D18-1: published=false profile on mount stays hidden.   D18-2/3: subagent context cannot fall back to mounts (2 cases — null     on local-empty + canReadMounts=false, local hit still returned).   D18-4: attribution surfaces source_brain_id (3 cases — mount answer     flag, local answer flag, attributionSuffix formatter).   Rule 1 local-first ordering (2 cases — mountResolver NOT called on     local hit, IS called on local empty).   Mount priority order (3 cases — first published=true wins, all     published=false returns null, no mounts configured returns null     without throwing).   canReadMountsForCtx classifier (4 cases — local CLI true, MCP     non-subagent true, subagent without trusted-workspace false,     subagent WITH trusted-workspace true).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * admin: E6 Calibration tab + D23 server-rendered SVG + TD2 contrast bump (T15)  Adds the v0.36.0.0 admin SPA Calibration tab. Per the design review, the approved variant-B (Linear calm clarity) layout: single-column flow, generous whitespace, ONE big sparkline as hero, then patterns, then domain bars, then abandoned threads.  D23 server-rendered SVG architecture:    src/core/calibration/svg-renderer.ts — pure functions. data → SVG   string. No DOM, no React, no chart library dep. Inlines the admin   design tokens (#0a0a0f bg, #3b82f6 accent, etc.) so the SVG is   visually consistent with the rest of the admin SPA.    Four chart renderers:     - renderBrierTrend({ series }) — sparkline w/ baseline reference       at 0.25 (always-50% baseline)     - renderDomainBars({ bars }) — horizontal accuracy bars per domain     - renderAbandonedThreadsCard(threads) — D30/TD4 'revisit now' link       per row, points at /admin/calibration/revisit/<takeId>     - renderPatternStatementsCard(statements) — D29/TD3 clickable       drill-down links per row, point at /admin/calibration/pattern/<i>    XSS posture: all caller-controlled strings pass through escapeXml().   Numeric inputs are .toFixed()-coerced. Admin SPA renders via   dangerouslySetInnerHTML inside a TrustedSVG wrapper component;   endpoint is gated by requireAdmin middleware.    /admin/api/calibration/profile — returns the active profile row as JSON.   /admin/api/calibration/charts/:type — returns image/svg+xml markup     for type ∈ {brier-trend, domain-bars, pattern-statements,                 abandoned-threads}. Cache-Control: private, max-age=60.    brier-trend currently renders a single-point series from the active   profile (the time-series view across calibration_profiles.generated_at   history is a v0.37 follow-up once we have multiple snapshots).   abandoned-threads pulls the top 5 abandoned rows via the same SQL the   doctor check uses.  CalibrationPage React component (admin/src/pages/Calibration.tsx):   Fetches profile + 4 charts. Loading / error / cold-brain states all   handled. Layout includes the audit annotations (partial-grade badge,   voice-gate-fell-back-to-template badge) per the approved mockup.   TrustedSVG wrapper isolates the dangerouslySetInnerHTML to the SVG   surface only.  App.tsx nav: added 'calibration' page route + sidebar nav item, hash routing extended to support #calibration.  TD2 contrast bump:   admin/src/index.css --text-muted: #555 → #777. Old value was contrast   4.0 on the #0a0a0f bg — below WCAG AA 4.5 for body text. New value is   ~5.5, passes AA. Improvement is global across Dashboard, Agents,   RequestLog, and the new Calibration tab — single-line CSS change with   ~10x the impact.  admin/dist/ rebuilt via `bun run build` (vite). 36 modules transformed.  Tests: 19 cases in test/svg-renderer.test.ts.   escapeXml (1): canonical entities.   renderBrierTrend (6): empty state, polyline for 2+ points, clamp   beyond yMax, design tokens inlined, XSS safety on date strings,   text-anchor end on right label.   renderDomainBars (4): empty state, label/accuracy/n rendering,   out-of-range accuracy clamp, XSS safety on labels.   renderAbandonedThreadsCard (4): empty state, row rendering with   revisit link, claim truncation at 70 chars, custom revisitHref override.   renderPatternStatementsCard (4): empty state, anchor count matches   statement count, XSS safety, custom drillHref override.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * recall: calibration footer formatter for morning pulse (T16)  Pure formatter that turns a CalibrationProfileRow + optional abandoned- threads list into the conversational block the morning pulse will surface:    Calibration this quarter:     Brier 0.18 (solid).     Right on early-stage tactics, late on macro by 18 months.     Over-confident on team execution; under-calibrated on regulatory risk.    Threads you opened and never came back to:     · AI search platform differentiation         (17 months silent)     · International expansion playbook           (12 months silent)  Cold-brain branch: returns empty string when no profile or < 5 resolved takes. Caller decides whether to render the block; cold-brain absence is the cleanest non-event.  Brier trend note maps the absolute value to conversational copy:   <= 0.10 → \"(strong calibration)\"   <= 0.20 → \"(solid)\"   <= 0.25 → \"(near baseline)\"   > 0.25  → \"(worse than always-50% baseline — review your high-conviction calls)\"    v0.36.0.0 ship state has only the current profile snapshot. The   \"was 0.22 90d ago — improving\" comparison shape arrives when we   accumulate generated_at history across multiple cycles.  R3 regression posture:   This module is the FORMATTER only. Wiring into `gbrain recall`'s text   output is intentionally NOT in this commit — runRecall's surface   stays unchanged. v0.37 wires it under --show-calibration (opt-in   initially, default-on later). For now the formatter is callable from   the admin tab + custom CLI scripts that want it.  Architecture:   buildRecallCalibrationFooter(opts) — pure. opts.profile required,   opts.abandonedThreads optional, opts.threadColumnWidth defaults to 50.    Caps at 4 patterns + 5 abandoned threads to keep the footer scannable.   Truncates long abandoned-thread claim text to fit the column width with   a trailing ellipsis.  Tests: 14 cases.   Cold-brain branch (3): null profile, < 5 resolved, zero resolved.   Happy path (7): header + Brier + patterns, trend note ranges (4   brackets), null brier omits the Brier line but keeps header, caps at   4 patterns.   Abandoned threads (4): omit section when none, emit when present,   cap at 5, truncate long claim with column-width override.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: --undo-wave reversal command (T17 / D18 CDX-3)  Implements the undo-wave reversal flow. Every new row written by the v0.36.0.0 calibration wave carries wave_version='v0.36.0.0' so a precise revert is possible without touching pre-wave data.  CLI surface (replaces the v0.36.0.0 ship-state placeholder):   gbrain calibration --undo-wave v0.36.0.0 [--dry-run] [--scrub-gstack] [--json]  Reversal scope (4 steps):    Step 1 — UNSET takes.resolved_* columns for takes auto-applied by this   wave. Identifies wave-applied takes via take_grade_cache.applied=true   + wave_version match. Cross-checks resolved_by='gbrain:grade_takes' to   ensure we're not un-resolving a take a manual `gbrain takes resolve`   override has since claimed. Manual resolutions persist; only auto-grade   resolutions revert.    Step 1b — Mark take_grade_cache rows applied=false post-undo so the   audit trail shows they WERE applied but this wave was reverted. The   CDX-11 confidence-drift check filters on applied=true and gets a   cleaner sample post-undo.    Step 2 — DELETE FROM calibration_profiles WHERE wave_version = ?.    Step 3 — DELETE FROM take_nudge_log WHERE wave_version = ?.    Step 4 — Optional gstack-learnings-prune via the binary, scoped to the   GSTACK_LEARNING_NAMESPACE prefix. Opt-in via --scrub-gstack. Best-effort:   binary-missing or failure logs a warning + suggests the manual command;   the rest of the undo still succeeded.  Dry-run posture:   --dry-run computes the counts via SELECT COUNT(*) shapes without   emitting any UPDATE or DELETE. Same UndoWaveResult shape returned so   operator sees exactly what would be reverted before committing.    --dry-run intentionally skips the gstack scrub (filesystem write) too;   ship-state safety call.  Idempotency:   Re-running --undo-wave on a brain that's already reverted is a no-op.   Each query filters on wave_version; no matching rows → zero counts.  Architecture:   undoWave(engine, opts) — async, returns UndoWaveResult. Pure data   layer; no stderr writes, no process exits. CLI dispatch in   src/commands/calibration.ts handles printing.    v0.36.0.0 ship state runs steps 1-3 sequentially (no transaction).   Partial reversal is recoverable via re-run since each step is   idempotent on wave_version match. A future enhancement (v0.37+) can   wrap in engine.transaction once that surface lands in BrainEngine.  Tests: 8 cases in test/undo-wave.test.ts.   Dry-run posture (1): counts emitted, NO UPDATE/DELETE SQL fired.   Happy path (3): all 4 steps execute, resolved_by filter scopes UPDATE   to wave-applied resolutions, custom resolvedByLabel honored.   Empty wave (2): zero counts when no matching rows, idempotent re-run.   Wave-version parameter threading (2): supplied version threads   through all queries, different wave versions don't collide.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: A/B harness for think + ab-report (T18 / D19 CDX-18)  Structural answer to CDX-18 (anti-bias rewrite may make advice worse). We don't have to guess whether calibration helps — we measure.  Architecture:   runAbTrial(input) — calls thinkRunner TWICE on the same question   (baseline + --with-calibration), surfaces both answers to a   preferenceResolver, persists the trial to think_ab_results.    buildAbReport(engine, { days }) — aggregates the table over the last   N days (default 30). Computes win counts, ties, neither, and a   with_calibration_win_rate over DECISIVE trials only (excludes   neither/tie). Flags calibration_net_negative when n >= 20 AND win   rate < 45%.    formatAbReport(report, days) — pretty-prints for stdout; emits the   calibration_net_negative warning block when triggered.  CLI:   gbrain calibration ab-report [--days N] [--json]     Reads the table, prints the breakdown. Replaces the v0.36.0.0     ship-state placeholder in src/commands/calibration.ts.    gbrain think --ab \"<question>\"     Wires into runAbTrial via the dispatch in src/commands/think.ts —     follow-up commit. This commit lands the harness layer + schema +     report surface; the --ab flag itself flips on in a one-line wiring     commit when the runRecall path is ready.  Schema (migration v72 / think_ab_results):   source_id, wave_version, ran_at, question, baseline_answer,   with_calibration_answer, preferred (CHECK in {baseline,   with_calibration, neither, tie}), model_id, notes.    CHECK constraint enforces preferred enum. Default wave_version   'v0.36.0.0' stamped so --undo-wave can scrub these too.    Index on (source_id, ran_at DESC) supports the report's   \"last N days\" query.    schema.sql + pglite-schema.ts both updated for fresh-install parity.   schema-embedded.ts regenerated via build:schema.  calibration_net_negative threshold (D19):   Triggers when:     - decisive_trials (baseline + with_calibration) >= 20     - with_calibration_win_rate < 0.45 (NOT <= — exact 45% is OK)    Small-sample guard (n < 20) prevents the warning from firing on   early data with sampling noise. Confidence-flat threshold (no Wilson   CI yet) keeps the math simple; v0.37+ adds CI bounds.  Tests: 12 cases in test/think-ab.test.ts.   runAbTrial (4): both runner calls fire, preferenceResolver receives     both answers, INSERT row params shape, throws when thinkRunner     missing.   buildAbReport (5): zero trials, aggregation, net_negative trigger at     n>=20 + win<45%, no trigger at n<20 (small-sample guard), no     trigger at exact 45% boundary.   formatAbReport (3): zero-state message, decisive-trials breakdown,     net_negative warning block.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: pattern drill-down route + revisit-now CLI (TD3 / D29 + TD4 / D30)  TD3 (D29) — clickable pattern drill-down endpoint:   GET /admin/api/calibration/pattern/:id (requireAdmin)   Returns the pattern statement at index `id` plus the top 25 resolved   takes for the holder, sorted by weight desc. v0.36.0.0 ship-state   approximation: surfaces broad provenance evidence (top resolved   takes). v0.37+ stores per-pattern source_take_ids[] on a   calibration_profile_patterns join table so the drill-down shows the   EXACT takes that drove the pattern.    Surfaces a `provenance_note` field in the response so the operator   sees the v0.36.0.0-vs-v0.37 fidelity boundary inline.    The admin SPA's renderPatternStatementsCard SVG already emits anchor   tags pointing at /admin/calibration/pattern/<i> (T15 ship state).   This route makes those anchors clickable — closes the trust loop that   was the rationale for D29 (\"pattern statements without their evidence   are dressed-up LLM hallucinations\").  TD4 (D30) — `gbrain takes revisit <slug>` editor-open action:   Adds the `revisit` subcommand to gbrain takes. Opens $EDITOR (falling   back to vi) on the source markdown file for the slug. Appends a   `<!-- gbrain:revisit -->` cursor marker at the bottom of the page on   first invocation so the editor opens with intent visible.    Reads sync.repo_path from config to locate the brain repo. Refuses to   proceed with a clear error when the repo isn't configured or the page   doesn't exist.    spawnSync with stdio:'inherit' so the editor takes the terminal. Exit   status surfaced on failure.    The SVG renderer's revisit-now anchor for each abandoned thread row   emits /admin/calibration/revisit/<takeId>. A small route handler that   resolves take_id → page_slug then dispatches `gbrain takes revisit`   via spawn is a v0.37 follow-up — the CLI command exists now so   developers can wire it directly.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * docs: DESIGN.md — formalize de facto design tokens (TD1)  Promotes the admin SPA's de facto design tokens (landed v0.26.0) to a canonical DESIGN.md at the repo root. This is the calibration target for /plan-design-review and /design-review going forward — when a question is \"does this UI fit the system?\", the answer is here.  Captures the system as it stands today:    Voice (5 surfaces, all routed through gateVoice() with mode-specific   rubrics): pattern_statement, nudge, forecast_blurb, dashboard_caption,   morning_pulse. Friend-not-doctor; concrete data over abstract metrics;   no preachy / clinical / corporate language.    Color tokens: 10 CSS variables from admin/src/index.css inlined into   the SVG renderer (src/core/calibration/svg-renderer.ts). Dark theme   is the only theme — admin is an operator tool. WCAG contrast   documented per token; TD2's #555 → #777 bump on --text-muted noted.    Typography: Inter for UI, JetBrains Mono for numbers/slugs/data.   Type scale (18 / 14 / 13 / 12 / 11) documented as de facto, not yet   formalized.    Spacing scale: 4 / 8 / 16 / 24 / 32px. Linear-app density.    Layout: sidebar 200px, max content 720px (text) / 960px (tables).   No 3-column feature grids, no icons in colored circles, no   decorative blobs.    Charts: server-rendered SVG via pure functions in   src/core/calibration/svg-renderer.ts. XSS posture documented:   server-side escapeXml on caller-controlled strings, numeric inputs   .toFixed()-coerced, admin SPA renders via <TrustedSVG> wrapper.    Interaction patterns: keyboard nav required (J/K/space/u/q on the   propose-queue), loading/empty/error states ARE features.    v0.37+ roadmap: type scale formalization, animation tokens, component   library extraction. Light mode explicitly NOT planned.  The doc is a living target, not a frozen spec. Major changes route through /plan-design-review per the existing review chain.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: synthetic corpus scaffold + privacy CI guard (T19 + T20)  T19 — synthetic corpus scaffold for extract-takes prompt tuning.   test/fixtures/calibration/extract-takes-corpus/ — 5 representative   pages across 4 genres (essay, people, companies, meetings, decisions).   v0.36.0.0 ships a SMALL representative corpus as proof of structure;   the full 50-page training set + 10-page holdout gets generated by the   operator via `gbrain calibration build-corpus` (v0.37 follow-up   subcommand) or by hand with the privacy guard catching violations   either way.    Privacy contract per D13': every page is SYNTHETIC. None of the   names/companies/funds/deals/events refer to anything real. Placeholder   names per CLAUDE.md: alice-example, charlie-example, acme-example,   widget-co, fund-a/b/c, acme-seed, widget-series-a, meetings/2026-04-03.    test/fixtures/calibration/README.md spells out the privacy contract,   generation flow, and what the corpus is (stable regression set for   the extract-takes prompt) vs is not (real anything).  T20 — privacy CI guard (CDX-14 mitigation).   scripts/check-synthetic-corpus-privacy.sh greps the corpus for:     1. Explicit dollar amounts ($50M, $1.2B etc) — would suggest the        page memorized a real round size.     2. Out-of-range year references (informational only for v0.36.0.0;        deferred to a manual review checklist).     3. Pages that reference ZERO placeholder names — suggests the page        might be referring to real entities. Essay-genre fixtures        exempt (they're anonymized PG-style writing by design).    Wired into `bun run verify` (CI gate) so contributors can't accidentally   land a synthetic fixture that leaks real-world specificity. The intent   is fail-fast on accidental leakage; the operator can update the   allowlist if a generic dollar amount is intentional.    Closes CDX-14: 'CC reads real brain pages locally, writes nothing   still risks privacy if any generated synthetic fixture memorizes   structure-specific facts. Placeholder names are not enough.'  The corpus shipped here is intentionally small but covers the four core gbrain page genres (essay, people, companies, meetings/decisions). The v0.37 corpus-build subcommand will fan out to 50 with the operator spot-checking + the CI guard enforcing the privacy contract.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test: R1-R5 IRON RULE regression inventory (T21)  Per /plan-eng-review D26 IRON RULE: regressions get added to the test suite as critical requirements, no AskUserQuestion needed. Pins five regressions identified during the v0.36.0.0 wave's coverage diagram:    R1: think baseline UNCHANGED when --with-calibration absent.       Covered structurally by test/think-with-calibration.test.ts plus       assertion-pinned in this file (default user message: question       first, then retrieval; system prompt: no anti-bias section).    R2: contradictions probe output UNCHANGED when no calibration profile.       Covered structurally by test/eval-contradictions-calibration-join.test.ts       plus pinned here (null profile → null tag, byte-identical to v0.32.6).    R3: takes resolution flow works when grade_takes phase disabled.       Pinned import-surface coupling: takes-resolution.ts has zero       dependency on grade_takes module. If a future refactor accidentally       couples them, this test fails to compile.    R4: search/list_pages/get_page work identically through new source_id paths.       Marker test referencing existing v0.34.1 source-isolation suite at       test/source-isolation-pglite.test.ts. v0.36.0.0 does NOT modify       those code paths; the existing tests catch any accidental coupling.    R5: existing search modes (conservative/balanced/tokenmax) unaffected.       Marker test referencing existing test/search-mode.test.ts. The       calibration code DOES NOT IMPORT from src/core/search/mode.ts.  Plus an inventory test that confirms all 5 regressions have an 'addressed' status — fail-loud if a future contributor removes a guard without updating the inventory.  7 tests total. Pure functions, no engine, hermetic.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * docs: v0.36.0.0 CHANGELOG + CLAUDE.md anchors + calibration convention skill  CHANGELOG entry: the user-facing release notes. Leads with the headline (\"the brain learns how you tend to be wrong, then argues against your blind spots on every advice call\"), 5 'what you can now do' bullets in GStack voice, itemized changes by lane, and the 'To take advantage of v0.36.0.0' upgrade checklist per the CLAUDE.md required-block contract.  CLAUDE.md anchors: new 'v0.36.0.0 Hindsight calibration wave (key files cluster)' block inserted before the v0.31.1 thin-client section. 23 new files / extensions annotated with one-paragraph descriptions each, linking back to the convention skill at skills/conventions/calibration.md for the agent-facing rules.  skills/conventions/calibration.md: the agent-facing convention skill. Tells future contributors which calibration touchpoint applies to their task — voice gate? BaseCyclePhase? source-scope thread? doctor warning? cross-brain query rules? auto-resolve threshold posture? Test seam patterns. Bug class to avoid (the v0.34.1 source-isolation leak shape).  Version trio (per CLAUDE.md mandatory audit):   VERSION:     0.36.0.0   package.json: 0.36.0.0   CHANGELOG:   ## [0.36.0.0] - 2026-05-17  llms.txt + llms-full.txt regenerated via `bun run build:llms` after the CLAUDE.md edit (per the explicit CLAUDE.md mandate \"Any CLAUDE.md edit MUST be followed by `bun run build:llms`\"). The `test/build-llms.test.ts` guard runs in CI shard 1; the committed bundles are checked against fresh generator output.  bun run verify is clean. typecheck clean. Privacy CI guard passes (0 violations across 6 corpus pages). All ready for /ship.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * cycle: wire propose_takes / grade_takes / calibration_profile into runCycle (T-fix)  The three new v0.36.0.0 phases were declared in CyclePhase / ALL_PHASES / NEEDS_LOCK_PHASES but the runCycle orchestrator never dispatched them. ALL_PHASES advertised them, gbrain dream --phase propose_takes accepted them, but `gbrain dream` (default) silently skipped all three.  Adds a single dispatch block between consolidate and embed that:   - builds an OperationContext on the fly (trusted-workspace caller,     remote: false, sourceId resolved via the same helper sync uses)   - dispatches the three phases in the order ALL_PHASES declares   - records the same skipped-phase shape (no_database) when engine is null  Pinned by test/core/cycle.serial.test.ts \"default: all 6 phases run in order\" which was already failing against ALL_PHASES (the test name lags the actual phase count; left as-is since renaming churns history).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: expand synthetic corpus + add hand-labeled ground-truth (T19)  Adds 8 new synthetic pages modeled on the genre mix observed in the real brain (concepts-with-timeline, meeting-notes, daily-journal, people-pages, essays). Companion .gradeable-claims.json files carry hand-labeled answer keys — what a tuned propose_takes prompt SHOULD extract per page. Closes the F1 gate gap from the plan's T19/D19:    Training corpus (test/fixtures/calibration/extract-takes-corpus/):     + concept-startup-market-dynamics.md     (10 claims)     + meeting-2026-04-10-fundraise-fund-a.md (6 claims)     + daily-2026-04-15.md                    (5 claims)    Blind holdout (test/fixtures/calibration/holdout/):     + concept-founder-execution.md           (6 claims, F1 >= 0.80)     + daily-2026-04-18.md                    (4 claims, F1 >= 0.80)     + meeting-2026-04-17-hiring-charlie.md   (5 claims, F1 >= 0.80)     + essay-on-conviction.md                 (7 claims, F1 >= 0.80)     + people-bob-example.md                  (5 claims, F1 >= 0.80)  Privacy:   - No real-brain content read into any committed artifact. Pages     written from scratch using the canonical placeholder set     (alice-example, charlie-example, bob-example, acme-example,     widget-co, fund-a/b/c). Real-name grep confirms zero leakage:     wintermute, garrytan, paul-graham, sam-altman, etc. → 0 hits.   - scripts/check-synthetic-corpus-privacy.sh passes: 0 violations     across 14 pages (was 6).  Genre fidelity:   - concept-with-timeline pages mirror the dated-assertion structure     real brain uses (verb framing varies: \"argues / predicts / I     think / I bet / strong conviction / moderate conviction\").   - meeting-notes pages carry both prose claims (extracted via     hedging language) and explicit ## Takes sections.   - daily-journal pages test probabilistic framing (\"75/25 in favor\",     \"call it ~0.5\") and self-tagged conviction values.   - essay-on-conviction is the meta-page that names the author's     own bias patterns — primary signal for calibration_profile.   - people pages test claim-about-third-party extraction.  Each JSON ground-truth lists per-claim:   - claim_text + kind (prediction|judgment|bet) + domain   - conviction (0..1)   - since_date   - rationale (why this claim is gradeable + how a tuned prompt     should infer conviction from the prose)  This is the corpus that gates the T19 prompt-tune iteration:   - F1 >= 0.85 on training (10+6+5 = 21 claims across 3 pages     plus the existing 5 fixtures already shipped)   - F1 >= 0.80 on holdout (27 claims across 5 pages)  Plan reference: ~/.claude/plans/system-instruction-you-are-working-rippling-knuth.md Privacy gate: scripts/check-synthetic-corpus-privacy.sh (wired into bun run verify).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * calibration: tune propose…") | 3 months agoMay 18, 2026 |\
| [INSTALL\_FOR\_AGENTS.md](https://github.com/garrytan/gbrain/blob/master/INSTALL_FOR_AGENTS.md "INSTALL_FOR_AGENTS.md") | [INSTALL\_FOR\_AGENTS.md](https://github.com/garrytan/gbrain/blob/master/INSTALL_FOR_AGENTS.md "INSTALL_FOR_AGENTS.md") | [fix(doctor,docs): warn that the npm name 'gbrain' is unrelated + dete…](https://github.com/garrytan/gbrain/commit/5773736c6368fc19cf887eeef8d371307c050cb5 "fix(doctor,docs): warn that the npm name 'gbrain' is unrelated + detect a shadowing npm install (#505) (#3454)  Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.  The npm package named `gbrain` is an unrelated squatted package, so `npm install gbrain` gives users something that is not this project. Adds doctor detection that classifies real checkouts correctly, fails open, and is try/catch'd throughout. Classification rests on the bin-shape marker since this repo has no `repository` field — verified e2e.  Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN with 22/22 checks on the current base after batches 1-3 landed.  Known gap, recorded rather than hidden: the remediation commands were not executed against a real global install, and the Windows `which -a` path is unexercised.") | 5 days agoJul 31, 2026 |\
| [LICENSE](https://github.com/garrytan/gbrain/blob/master/LICENSE "LICENSE") | [LICENSE](https://github.com/garrytan/gbrain/blob/master/LICENSE "LICENSE") | [feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (](https://github.com/garrytan/gbrain/commit/b22cbd349ac2787ca47da98a7026a3a923f82006 "feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (#1)  * chore: add CLAUDE.md with project context and gstack skill routing rules  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: initialize project with Bun + TypeScript  package.json with dependencies (postgres, pgvector, openai, anthropic, MCP SDK, gray-matter). TypeScript config targeting ESNext with bundler module resolution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add foundation layer — engine interface, Postgres engine, schema  BrainEngine pluggable interface with full PostgresEngine: CRUD, search (keyword + vector), links, tags, timeline, versions, stats, health, ingest log, config. Trigger-based tsvector spanning pages + timeline_entries. Markdown parser with frontmatter, compiled_truth / timeline splitting, and round-trip serialization. 19 tests passing.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 3-tier chunking and embedding service  Recursive delimiter-aware chunker (5-level hierarchy, 300-word chunks, 50-word overlap). Semantic chunker with Savitzky-Golay boundary detection and recursive fallback. LLM-guided chunker via Claude Haiku with sliding window topic detection. OpenAI embedding service with batch support, exponential backoff, and rate limit handling.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add hybrid search with RRF fusion, expansion, and 4-layer dedup  Hybrid search merges vector (pgvector HNSW) + keyword (tsvector) via Reciprocal Rank Fusion. Multi-query expansion via Claude Haiku generates 2 alternative phrasings. 4-layer dedup pipeline: by source, cosine similarity, type diversity (60% cap), per-page cap.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add GBRAIN_V0 spec, pluggable engine architecture, SQLite engine plan  GBRAIN_V0.md: full product spec with architecture decisions, CLI commands, schema, search architecture, chunking strategies, first-time experience, and future plans. ENGINES.md: pluggable engine interface, capability matrix, how to add new backends. SQLITE_ENGINE.md: complete SQLite implementation plan with schema, FTS5 setup, vector search options, and contributor guide.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add CLI with all commands  Full CLI dispatcher with 25+ commands: init (Supabase wizard), get, put, delete, list, search, query (hybrid RRF), import (bulk with progress bar), export (round-trip), embed, stats, health, tag/untag/tags, link/unlink/ backlinks/graph, timeline/timeline-add, history/revert, config, upgrade, serve, call. Smart slug resolution on reads. Version snapshots on updates.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add MCP stdio server with all brain tools  20 MCP tools mirroring CLI operations: get/put/delete/list pages, search (keyword), query (hybrid RRF + expansion), tags, links with graph traversal, timeline, stats, health, version history, and revert. Auto-chunks and embeds on put_page. CLI and MCP share the same engine.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 6 skill files and ClawHub manifest  Fat markdown skills for AI agents: ingest (meetings/docs/articles with timeline merge), query (3-layer search + synthesis + citations), maintain (health checks, stale detection, orphan audit), enrich (external API enrichment), briefing (daily briefing compilation), migrate (universal migration from Obsidian/Notion/Logseq/markdown/CSV/JSON/Roam). ClawHub manifest for skill distribution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add README, CONTRIBUTING, update CLAUDE.md test references  README with quickstart, commands, architecture, library usage, MCP setup, and links to design docs. CONTRIBUTING with setup, project structure, and guides for adding commands and engines. CLAUDE.md updated to reference actual test files instead of planned-but-unwritten import test.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: address adversarial review findings — 5 critical/high fixes  - revertToVersion: add page_id check to prevent cross-page data corruption - traverseGraph: use UNION instead of UNION ALL for cycle safety - embedAll: preserve all chunks when embedding stale subset only - embedding: throw on retry exhaustion instead of returning zero vectors - putPage: validate slugs to prevent path traversal on export  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.1.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: expand README with schema, install, search architecture, and motivation  Why it exists, how search works (with ASCII diagram), full database schema with all 9 tables and index details, chunking strategies explained, storage estimates, setup wizard walkthrough, knowledge model with example page, library usage with more examples, expanded skills table.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: add MIT license (Copyright 2026 Garry Tan)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add OpenClaw install flow as primary option in README  OpenClaw users just say \"install gbrain\" and the orchestrator handles everything: package install, Supabase setup wizard, skill registration. Shows the conversational interface for querying, ingesting, and briefings. ClawHub and standalone CLI paths follow as alternatives.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add prerequisites and explicit OpenClaw install instructions  Prerequisites table listing Supabase, OpenAI, and Anthropic dependencies with links. Environment variable setup. Explicit step-by-step prompt for OpenClaw users showing exactly what to tell the orchestrator. Note that search degrades gracefully without API keys (keyword-only without OpenAI, no expansion without Anthropic).  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: scrub named references, add PG essay demo section to README  Replace all Pedro/Brex/Jensen Huang/River AI examples with Paul Graham essay examples using the kindling corpus. Add \"Try it\" section to README showing the power of hybrid search on PG essays in 90 seconds. Update test fixtures to use concept pages instead of person pages.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") [#1](https://github.com/garrytan/gbrain/pull/1) [)](https://github.com/garrytan/gbrain/commit/b22cbd349ac2787ca47da98a7026a3a923f82006 "feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (#1)  * chore: add CLAUDE.md with project context and gstack skill routing rules  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: initialize project with Bun + TypeScript  package.json with dependencies (postgres, pgvector, openai, anthropic, MCP SDK, gray-matter). TypeScript config targeting ESNext with bundler module resolution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add foundation layer — engine interface, Postgres engine, schema  BrainEngine pluggable interface with full PostgresEngine: CRUD, search (keyword + vector), links, tags, timeline, versions, stats, health, ingest log, config. Trigger-based tsvector spanning pages + timeline_entries. Markdown parser with frontmatter, compiled_truth / timeline splitting, and round-trip serialization. 19 tests passing.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 3-tier chunking and embedding service  Recursive delimiter-aware chunker (5-level hierarchy, 300-word chunks, 50-word overlap). Semantic chunker with Savitzky-Golay boundary detection and recursive fallback. LLM-guided chunker via Claude Haiku with sliding window topic detection. OpenAI embedding service with batch support, exponential backoff, and rate limit handling.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add hybrid search with RRF fusion, expansion, and 4-layer dedup  Hybrid search merges vector (pgvector HNSW) + keyword (tsvector) via Reciprocal Rank Fusion. Multi-query expansion via Claude Haiku generates 2 alternative phrasings. 4-layer dedup pipeline: by source, cosine similarity, type diversity (60% cap), per-page cap.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add GBRAIN_V0 spec, pluggable engine architecture, SQLite engine plan  GBRAIN_V0.md: full product spec with architecture decisions, CLI commands, schema, search architecture, chunking strategies, first-time experience, and future plans. ENGINES.md: pluggable engine interface, capability matrix, how to add new backends. SQLITE_ENGINE.md: complete SQLite implementation plan with schema, FTS5 setup, vector search options, and contributor guide.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add CLI with all commands  Full CLI dispatcher with 25+ commands: init (Supabase wizard), get, put, delete, list, search, query (hybrid RRF), import (bulk with progress bar), export (round-trip), embed, stats, health, tag/untag/tags, link/unlink/ backlinks/graph, timeline/timeline-add, history/revert, config, upgrade, serve, call. Smart slug resolution on reads. Version snapshots on updates.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add MCP stdio server with all brain tools  20 MCP tools mirroring CLI operations: get/put/delete/list pages, search (keyword), query (hybrid RRF + expansion), tags, links with graph traversal, timeline, stats, health, version history, and revert. Auto-chunks and embeds on put_page. CLI and MCP share the same engine.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 6 skill files and ClawHub manifest  Fat markdown skills for AI agents: ingest (meetings/docs/articles with timeline merge), query (3-layer search + synthesis + citations), maintain (health checks, stale detection, orphan audit), enrich (external API enrichment), briefing (daily briefing compilation), migrate (universal migration from Obsidian/Notion/Logseq/markdown/CSV/JSON/Roam). ClawHub manifest for skill distribution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add README, CONTRIBUTING, update CLAUDE.md test references  README with quickstart, commands, architecture, library usage, MCP setup, and links to design docs. CONTRIBUTING with setup, project structure, and guides for adding commands and engines. CLAUDE.md updated to reference actual test files instead of planned-but-unwritten import test.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: address adversarial review findings — 5 critical/high fixes  - revertToVersion: add page_id check to prevent cross-page data corruption - traverseGraph: use UNION instead of UNION ALL for cycle safety - embedAll: preserve all chunks when embedding stale subset only - embedding: throw on retry exhaustion instead of returning zero vectors - putPage: validate slugs to prevent path traversal on export  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.1.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: expand README with schema, install, search architecture, and motivation  Why it exists, how search works (with ASCII diagram), full database schema with all 9 tables and index details, chunking strategies explained, storage estimates, setup wizard walkthrough, knowledge model with example page, library usage with more examples, expanded skills table.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: add MIT license (Copyright 2026 Garry Tan)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add OpenClaw install flow as primary option in README  OpenClaw users just say \"install gbrain\" and the orchestrator handles everything: package install, Supabase setup wizard, skill registration. Shows the conversational interface for querying, ingesting, and briefings. ClawHub and standalone CLI paths follow as alternatives.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add prerequisites and explicit OpenClaw install instructions  Prerequisites table listing Supabase, OpenAI, and Anthropic dependencies with links. Environment variable setup. Explicit step-by-step prompt for OpenClaw users showing exactly what to tell the orchestrator. Note that search degrades gracefully without API keys (keyword-only without OpenAI, no expansion without Anthropic).  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: scrub named references, add PG essay demo section to README  Replace all Pedro/Brex/Jensen Huang/River AI examples with Paul Graham essay examples using the kindling corpus. Add \"Try it\" section to README showing the power of hybrid search on PG essays in 90 seconds. Update test fixtures to use concept pages instead of person pages.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") | 4 months agoApr 5, 2026 |\
| [README.md](https://github.com/garrytan/gbrain/blob/master/README.md "README.md") | [README.md](https://github.com/garrytan/gbrain/blob/master/README.md "README.md") | [fix(skills,docs): unify-types playbooks must pass apply:true after the](https://github.com/garrytan/gbrain/commit/25e4c0c3b1fcdda583881903f0116a82de8ad796 "fix(skills,docs): unify-types playbooks must pass apply:true after the #3574 default flip  #3574 flipped the unify-types worker default to dry-run (jobs.ts:2221, apply: data.apply ?? false) and updated the architecture docs, but three agent-facing surfaces still presented the bare submit as the Apply step: skills/schema-unify/SKILL.md 'Phase 3: Apply', skills/conventions/ schema-evolution.md, and README.md. Because #3545 also edited SKILL.md in this wave, each PR looked self-consistent in isolation — only the composed branch shipped a playbook whose apply step silently retypes nothing and never flips the active pack. Skills distribute downstream via the skillpack, so this would have propagated. Found by an independent cross-PR review pass.") […](https://github.com/garrytan/gbrain/pull/3574) | 5 days agoJul 31, 2026 |\
| [SECURITY.md](https://github.com/garrytan/gbrain/blob/master/SECURITY.md "SECURITY.md") | [SECURITY.md](https://github.com/garrytan/gbrain/blob/master/SECURITY.md "SECURITY.md") | [docs(security): tighten responsible disclosure language (](https://github.com/garrytan/gbrain/commit/a228776b8f350603dd1908f334e7c55d20d797fb "docs(security): tighten responsible disclosure language (#3619)  Co-Authored-By: Diego <diegodearagao@gmail.com>") [#3619](https://github.com/garrytan/gbrain/pull/3619) [)](https://github.com/garrytan/gbrain/commit/a228776b8f350603dd1908f334e7c55d20d797fb "docs(security): tighten responsible disclosure language (#3619)  Co-Authored-By: Diego <diegodearagao@gmail.com>") | 5 days agoJul 31, 2026 |\
| [TODOS.md](https://github.com/garrytan/gbrain/blob/master/TODOS.md "TODOS.md") | [TODOS.md](https://github.com/garrytan/gbrain/blob/master/TODOS.md "TODOS.md") | [test(cli): cover import side-effect guard (](https://github.com/garrytan/gbrain/commit/f39b059ad8c580b63c82d3050570d130c5384231 "test(cli): cover import side-effect guard (#3581)  Co-Authored-By: 老赵 <273731059@qq.com>") [#3581](https://github.com/garrytan/gbrain/pull/3581) [)](https://github.com/garrytan/gbrain/commit/f39b059ad8c580b63c82d3050570d130c5384231 "test(cli): cover import side-effect guard (#3581)  Co-Authored-By: 老赵 <273731059@qq.com>") | 5 days agoJul 31, 2026 |\
| [VERSION](https://github.com/garrytan/gbrain/blob/master/VERSION "VERSION") | [VERSION](https://github.com/garrytan/gbrain/blob/master/VERSION "VERSION") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |\
| [bun.lock](https://github.com/garrytan/gbrain/blob/master/bun.lock "bun.lock") | [bun.lock](https://github.com/garrytan/gbrain/blob/master/bun.lock "bun.lock") | [fix(deps): bump four override pins past newly-published CVEs](https://github.com/garrytan/gbrain/commit/e5dee4fb78481f0fb7c78016fc7e450bef252caa "fix(deps): bump four override pins past newly-published CVEs  osv-scan passed on the last two release PRs and fails on this one, so these are newly published, not tolerated debt: 2 High + 4 Medium across fast-uri (3.1.4 → 3.1.5), hono (4.12.25 → 4.12.34), ip-address (10.1.1 → 10.3.1), and admin's postcss (8.5.10 → 8.5.23). All four are transitive and pinned through the overrides block, so the fix is the pin, not a dependency addition.  Resolved: fast-uri@3.1.5, hono@4.13.0, ip-address@10.4.0, postcss@8.5.25.") | 2 days agoAug 4, 2026 |\
| [bunfig.toml](https://github.com/garrytan/gbrain/blob/master/bunfig.toml "bunfig.toml") | [bunfig.toml](https://github.com/garrytan/gbrain/blob/master/bunfig.toml "bunfig.toml") | [fix(test): isolate audit writes to a per-run scratch dir in the share…](https://github.com/garrytan/gbrain/commit/42c4ea929fe201109b67d9d0c639d22554e89039 "fix(test): isolate audit writes to a per-run scratch dir in the shared bootstrap (#2823) (#2966)  The content-sanity gate's audit logger (logContentSanityAssessment) defaults, via audit-writer.ts::resolveAuditDir(), to writing ~/.gbrain/audit/content-sanity-YYYY-Www.jsonl on disk. A GBRAIN_AUDIT_DIR env override exists, but nothing in the shared test bootstrap ever set it, so any test that exercised an audit-emitting code path without wrapping the call in its own withEnv() fell through to the operator's real audit trail. test/import-file.test.ts's oversize-boundary fixture ('borderline-slug', content just under MAX_FILE_SIZE but over DEFAULT_BYTES_BLOCK) fired a real soft_block event into the developer's live ~/.gbrain/audit on every run — which doctor's content_sanity_audit_recent check then reported as production signal.  Fix: add a bootstrap preload (test/helpers/audit-dir-preload.ts, wired via bunfig.toml) that points GBRAIN_AUDIT_DIR at a fresh per-process mkdtemp dir before any test file loads. Each run-unit-shard.sh shard is its own bun process, so each shard gets its own scratch dir with no cross-shard collision. This closes the leak for every audit-emitting test, not just this fixture. It respects a developer-exported override (only sets the var when unset), and files that manage their own per-test GBRAIN_AUDIT_DIR via withEnv() are unaffected.  Also fix a latent isolation bug this surfaced: gbrain-home-isolation.test.ts unconditionally deleted GBRAIN_AUDIT_DIR in a finally block instead of restoring the prior value, which clobbered the bootstrap's scratch dir for every test file that ran after it in the same shard process.  Adds test/audit/audit-dir-preload.test.ts to pin the behavior: it reproduces the exact soft_block event shape and asserts it lands in the scratch dir, never in ~/.gbrain/audit.  Reported by @paul-0320.  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | 2 weeks agoJul 21, 2026 |\
| [docker-compose.ci.yml](https://github.com/garrytan/gbrain/blob/master/docker-compose.ci.yml "docker-compose.ci.yml") | [docker-compose.ci.yml](https://github.com/garrytan/gbrain/blob/master/docker-compose.ci.yml "docker-compose.ci.yml") | [v0.42.43.0 feat(context): push-based context (](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") [#2095](https://github.com/garrytan/gbrain/issues/2095) [) \+ teardown-exit …](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | 2 months agoJun 14, 2026 |\
| [docker-compose.test.yml](https://github.com/garrytan/gbrain/blob/master/docker-compose.test.yml "docker-compose.test.yml") | [docker-compose.test.yml](https://github.com/garrytan/gbrain/blob/master/docker-compose.test.yml "docker-compose.test.yml") | [feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (](https://github.com/garrytan/gbrain/commit/a86f995883605922c0ae9e437ff32e8a75727ff7 "feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (#7)  * feat: contract-first operations.ts with OperationError, dry_run, importFromContent  30 shared operations as single source of truth for CLI and MCP. - OperationError with typed error codes (page_not_found, invalid_params, etc.) - dry_run support on all mutating operations - importFromContent split from importFile with transaction wrapping - Idempotency hash now includes ALL fields (title, type, frontmatter, tags) - Config env var fallback: GBRAIN_DATABASE_URL > DATABASE_URL > config file  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: rewrite MCP server + CLI + tools-json from operations  server.ts: 233 -> ~80 lines. Tool definitions and dispatch generated from operations[]. cli.ts: shared operations auto-registered, CLI-only commands kept as manual dispatch. tools-json: generated FROM operations[], eliminating the third contract surface. Parity test verifies structural contract between operations, CLI, and MCP.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: delete 12 command files migrated to operations.ts  Handler logic for get, put, delete, list, search, query, health, stats, tags, link, timeline, and version now lives in operations.ts. Kept: init, upgrade, import, export, files, embed, sync, serve, call, config.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: init --non-interactive, upgrade verification, schema migration  - gbrain init --non-interactive --url <url> for plugin mode (no TTY required) - Post-upgrade version verification in gbrain upgrade - Drop storage_url from files table (storage_path is the only identifier)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: tool-agnostic skills + new setup skill  All 7 skills rewritten with intent-based language instead of CLI commands. Works with both CLI and MCP plugin contexts. New setup skill replaces install: auto-provision Supabase via CLI, AGENTS.md injection, target TTHW < 2 min.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: ClawHub bundle plugin, CI workflows, v0.3.0  - openclaw.plugin.json with configSchema, MCP server config, skill listing - GitHub Actions: test on push/PR, multi-platform release (macOS arm64 + Linux x64) - Version bump 0.3.0, CHANGELOG, README ClawHub section, CLAUDE.md updated  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: idempotency hash mismatch + MCP dry_run passthrough  importFromContent now passes its all-fields hash through putPage via content_hash on PageInput, so the stored hash matches the computed hash. Previously the skip-if-unchanged check never fired because the hash formulas differed.  MCP server now passes dry_run from tool params to OperationContext.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.3.0.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: schema loader handles PL/pgSQL $$ blocks  Delete the semicolon-based SQL splitter in db.ts which broke on PL/pgSQL trigger functions containing semicolons inside $$ delimiter blocks. Use single conn.unsafe(schemaSql) call instead — the postgres driver handles multi-statement SQL natively. schema.sql already uses IF NOT EXISTS / CREATE OR REPLACE for idempotency.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test infrastructure + realistic brain fixtures  Add test infrastructure for running E2E tests against real Postgres+pgvector. Includes: - test/e2e/helpers.ts: DB lifecycle, fixture import, timing, diagnostics - 13 fixture files as a miniature realistic brain (people, companies,   deals, meetings, concepts, projects, sources) following the   compiled truth + timeline format from GBRAIN_RECOMMENDED_SCHEMA.md - docker-compose.test.yml: local pgvector convenience (port 5433) - .env.testing.example: template for test credentials - package.json: add test:e2e script  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test suites + CI workflow  Tier 1 (mechanical.test.ts): 14 test suites covering all operations against real Postgres — page CRUD, search with quality scoring, links, tags, timeline, versions, admin, chunks, resolution, ingest log, raw data, files, idempotency stress, setup journey (full CLI flow), init edge cases, schema idempotency, schema diff guard, performance baselines.  Tier 1 (mcp.test.ts): MCP protocol test — spawns server, sends JSON-RPC, verifies tools/list matches operations count.  Tier 2 (skills.test.ts): OpenClaw skill tests — ingest, query, health. Skips gracefully when dependencies missing.  CI (.github/workflows/e2e.yml): Tier 1 on every PR (pgvector service), Tier 2 nightly/manual with API key secrets.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * fix: E2E test fixes + traverseGraph jsonb cast  - Fix traverseGraph query: cast json_agg to jsonb_agg so SELECT DISTINCT works - Fix put_page tests to use importFromContent with noEmbed (no OpenAI key in Tier 1) - Fix get_health assertion (page_count not total_pages) - Fix raw_data test to handle JSONB string/object return - Simplify MCP test to verify tool generation directly - Add timeouts to CLI subprocess tests - Use port 5434 for docker-compose (5433 often in use)  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * docs: update all project docs for E2E test suite  - CLAUDE.md: updated test count (9 unit + 3 E2E), added E2E test   instructions, fixed skill count to 8 - CONTRIBUTING.md: updated project structure with test/e2e/, added E2E   test instructions, rewrote \"Adding a new command\" to reflect   contract-first architecture (add to operations.ts, done) - README.md: fixed table count (10 not 9), added recommended schema doc   to Docs section, added E2E instructions to Contributing section - CHANGELOG.md: added E2E test suite, docker-compose, schema loader fix,   and traverseGraph jsonb fix to v0.3.0 entry  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") [#7](https://github.com/garrytan/gbrain/pull/7) [)](https://github.com/garrytan/gbrain/commit/a86f995883605922c0ae9e437ff32e8a75727ff7 "feat: GBrain v0.3.0 — contract-first architecture + ClawHub plugin (#7)  * feat: contract-first operations.ts with OperationError, dry_run, importFromContent  30 shared operations as single source of truth for CLI and MCP. - OperationError with typed error codes (page_not_found, invalid_params, etc.) - dry_run support on all mutating operations - importFromContent split from importFile with transaction wrapping - Idempotency hash now includes ALL fields (title, type, frontmatter, tags) - Config env var fallback: GBRAIN_DATABASE_URL > DATABASE_URL > config file  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: rewrite MCP server + CLI + tools-json from operations  server.ts: 233 -> ~80 lines. Tool definitions and dispatch generated from operations[]. cli.ts: shared operations auto-registered, CLI-only commands kept as manual dispatch. tools-json: generated FROM operations[], eliminating the third contract surface. Parity test verifies structural contract between operations, CLI, and MCP.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * refactor: delete 12 command files migrated to operations.ts  Handler logic for get, put, delete, list, search, query, health, stats, tags, link, timeline, and version now lives in operations.ts. Kept: init, upgrade, import, export, files, embed, sync, serve, call, config.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: init --non-interactive, upgrade verification, schema migration  - gbrain init --non-interactive --url <url> for plugin mode (no TTY required) - Post-upgrade version verification in gbrain upgrade - Drop storage_url from files table (storage_path is the only identifier)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: tool-agnostic skills + new setup skill  All 7 skills rewritten with intent-based language instead of CLI commands. Works with both CLI and MCP plugin contexts. New setup skill replaces install: auto-provision Supabase via CLI, AGENTS.md injection, target TTHW < 2 min.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: ClawHub bundle plugin, CI workflows, v0.3.0  - openclaw.plugin.json with configSchema, MCP server config, skill listing - GitHub Actions: test on push/PR, multi-platform release (macOS arm64 + Linux x64) - Version bump 0.3.0, CHANGELOG, README ClawHub section, CLAUDE.md updated  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: idempotency hash mismatch + MCP dry_run passthrough  importFromContent now passes its all-fields hash through putPage via content_hash on PageInput, so the stored hash matches the computed hash. Previously the skip-if-unchanged check never fired because the hash formulas differed.  MCP server now passes dry_run from tool params to OperationContext.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.3.0.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: schema loader handles PL/pgSQL $$ blocks  Delete the semicolon-based SQL splitter in db.ts which broke on PL/pgSQL trigger functions containing semicolons inside $$ delimiter blocks. Use single conn.unsafe(schemaSql) call instead — the postgres driver handles multi-statement SQL natively. schema.sql already uses IF NOT EXISTS / CREATE OR REPLACE for idempotency.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test infrastructure + realistic brain fixtures  Add test infrastructure for running E2E tests against real Postgres+pgvector. Includes: - test/e2e/helpers.ts: DB lifecycle, fixture import, timing, diagnostics - 13 fixture files as a miniature realistic brain (people, companies,   deals, meetings, concepts, projects, sources) following the   compiled truth + timeline format from GBRAIN_RECOMMENDED_SCHEMA.md - docker-compose.test.yml: local pgvector convenience (port 5433) - .env.testing.example: template for test credentials - package.json: add test:e2e script  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * feat: E2E test suites + CI workflow  Tier 1 (mechanical.test.ts): 14 test suites covering all operations against real Postgres — page CRUD, search with quality scoring, links, tags, timeline, versions, admin, chunks, resolution, ingest log, raw data, files, idempotency stress, setup journey (full CLI flow), init edge cases, schema idempotency, schema diff guard, performance baselines.  Tier 1 (mcp.test.ts): MCP protocol test — spawns server, sends JSON-RPC, verifies tools/list matches operations count.  Tier 2 (skills.test.ts): OpenClaw skill tests — ingest, query, health. Skips gracefully when dependencies missing.  CI (.github/workflows/e2e.yml): Tier 1 on every PR (pgvector service), Tier 2 nightly/manual with API key secrets.  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * fix: E2E test fixes + traverseGraph jsonb cast  - Fix traverseGraph query: cast json_agg to jsonb_agg so SELECT DISTINCT works - Fix put_page tests to use importFromContent with noEmbed (no OpenAI key in Tier 1) - Fix get_health assertion (page_count not total_pages) - Fix raw_data test to handle JSONB string/object return - Simplify MCP test to verify tool generation directly - Add timeouts to CLI subprocess tests - Use port 5434 for docker-compose (5433 often in use)  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  * docs: update all project docs for E2E test suite  - CLAUDE.md: updated test count (9 unit + 3 E2E), added E2E test   instructions, fixed skill count to 8 - CONTRIBUTING.md: updated project structure with test/e2e/, added E2E   test instructions, rewrote \"Adding a new command\" to reflect   contract-first architecture (add to operations.ts, done) - README.md: fixed table count (10 not 9), added recommended schema doc   to Docs section, added E2E instructions to Contributing section - CHANGELOG.md: added E2E test suite, docker-compose, schema loader fix,   and traverseGraph jsonb fix to v0.3.0 entry  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") | 4 months agoApr 9, 2026 |\
| [gbrain.yml](https://github.com/garrytan/gbrain/blob/master/gbrain.yml "gbrain.yml") | [gbrain.yml](https://github.com/garrytan/gbrain/blob/master/gbrain.yml "gbrain.yml") | [v0.22.11 feat: storage tiering — db\_tracked vs db\_only directories (](https://github.com/garrytan/gbrain/commit/52f9581966d295115864ebc05bd3dfea656c75e7 "v0.22.11 feat: storage tiering — db_tracked vs db_only directories (#494)  * feat: storage tiering — git-tracked vs supabase-only directories  Brain repos scaling to 200K+ files. Bulk data (tweets, articles, transcripts) bloats git repos and slows operations. New storage config in gbrain.yml lets users declare git-tracked and supabase-only directories.  Changes: - New config: storage.git_tracked and storage.supabase_only in gbrain.yml - gbrain sync auto-manages .gitignore for supabase-only paths - gbrain export --restore-only restores missing supabase-only files from DB - New gbrain storage status command shows tier breakdown - Config validation warns on conflicts - 8 tests passing, full docs at docs/storage-tiering.md  Backward compatible — systems without gbrain.yml work unchanged.  * feat: add getDefaultSourcePath() typed accessor (step 1/15)  Single source of truth for \"what brain repo are we operating against?\" Replaces ad-hoc raw SQL in storage.ts:38 (Issue #3 of eng review). Used by both gbrain storage status and gbrain export --restore-only.  Returns null on miss, throws on DB error. Composes with the existing resolveSourceId chain so it honors --source flag / GBRAIN_SOURCE env / .gbrain-source dotfile / longest-prefix CWD match / brain-level default.  4 new test cases covering happy path, missing local_path, DB error propagation, and CWD-prefix resolution priority.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix: replace gray-matter with dedicated YAML parser (step 2/15)  The original storage-config.ts called gray-matter on a delimiter-less YAML file. Gray-matter only parses YAML inside `---` frontmatter blocks; without delimiters, it returns `{data: {}}`. Result: loadStorageConfig() always returned null, the entire feature was a silent no-op for every user.  Original eng review's P0 confidence-9 finding (Issue #1).  Replaces gray-matter with a small dedicated parser for the gbrain.yml shape (top-level `storage:` section, two array-valued nested keys). Yaml-lite was considered first, but its flat key:value design doesn't handle nested arrays. The dedicated parser is ~50 lines and trades expressiveness for zero-dep, predictable parsing of a file format we control.  Adds the Issue #1B sanity warning (locked B): when gbrain.yml exists but has no storage section (or empty arrays), warn once-per-process so the user sees their config didn't take. The single test that would have caught the original P0 — write a real gbrain.yml, call loadStorageConfig, assert non-null — now exists.  Also tightens loadStorageConfig per D36: distinguishes \"absent\" (silent null) from \"unreadable\" (throws). The previous code silently swallowed read errors, hiding broken installs.  8 new test cases: real-disk happy path, comments + blank lines, quoted values, missing storage section warning, empty section warning, once-per-process warning suppression, unreadable file behavior, and the existing helper tests (validation, tier matching, edge cases) all still pass.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * refactor: rename storage keys to db_tracked/db_only (step 3/15)  The vendor-specific names \"supabase_only\" and \"git_tracked\" hardcoded a backend (Supabase) into the config schema. gbrain ships two engines — PGLite and Postgres-via-Supabase. The canonical distinction is \"lives in the brain DB only\" vs \"lives in the brain DB and on disk under git.\" Both work on either engine.  Renamed throughout (Issue #4 of eng review):   git_tracked    → db_tracked   supabase_only  → db_only   isGitTracked() → isDbTracked()   isSupabaseOnly() → isDbOnly()   StorageTier 'git_tracked'/'supabase_only' → 'db_tracked'/'db_only'  Backward compatibility (D3 lock):   loadStorageConfig accepts both shapes. Loader resolution order per the   eng-review pass-2 finding: parse YAML → if canonical keys present use   them, else if deprecated keys present map to canonical AND emit   once-per-process deprecation warning → THEN run validation.   Validation always sees the canonical shape so error messages reference   db_tracked/db_only regardless of which keys the user wrote.    The deprecation warning suggests `gbrain doctor --fix` for an automated   rename (D72 — fix path lands in step 7).    When both shapes coexist in one file, canonical wins and a stronger   warning fires (\"deprecated keys ignored — remove them\").  Aliases isGitTracked/isSupabaseOnly kept for now to avoid churning the sync.ts / export.ts / storage.ts call sites in this commit; they'll be removed in a follow-up step. Storage.ts's tier-bucket initializers and output strings updated. ASCII output replaces unicode box-drawing per D10.  gbrain.yml example file updated to canonical keys with explanatory comments.  2 new test cases: deprecated-key fallback (asserts both shapes load correctly with warning), canonical-wins-over-deprecated (asserts the \"both shapes coexist\" path).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat: add slugPrefix to PageFilters with engine-side filter (step 4/15)  Issue #13 of the eng review: storage.ts and export.ts loaded every page in the brain (limit: 1_000_000) to check tier membership. On the 200K-page brains this feature targets, that's the wall-clock and memory landmine the feature exists to fix.  Adds an optional `slugPrefix` field to PageFilters. Both engines implement it as `WHERE slug LIKE prefix || '%' ESCAPE '\'`, with literal escaping of LIKE metacharacters (%, _, \) so user-supplied prefixes like `media/x/` are treated as exact string prefixes.  Performance: the (source_id, slug) UNIQUE constraint on the pages table gives both engines a btree index that supports LIKE-prefix range scans. An EXPLAIN on Postgres confirms the index range scan rather than a seq scan. PGLite has the same index shape via pglite-schema.ts.  Consumers updated:   - export.ts: --slug-prefix flag now goes engine-side (no in-memory     .filter(...)). The --restore-only path queries each db_only directory     with slugPrefix in a loop instead of one full-table scan, with seen-set     deduplication and disk-existence check inline.   - storage.ts: keeps the full-scan path because storage-status needs the     \"unspecified\" bucket count, which can't be computed without enumerating     every page. Comment notes that step 5 (single-walk filesystem scan)     will reduce per-page disk syscall cost.  2 new test cases on PGLiteEngine: slugPrefix happy path (3 tier dirs, asserts only matching slugs return) and metacharacter escape regression (asserts safe/ doesn't match unrelated slugs).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * perf: single-walk filesystem scan via walkBrainRepo() (step 5/15)  Issue #14 of the eng review: storage.ts called existsSync + statSync per-page in a synchronous loop. On a 200K-page brain that's 400K syscalls serialized. Wall-clock landmine.  Adds src/core/disk-walk.ts with walkBrainRepo(repoPath) — one recursive readdirSync walk, builds a Map<slug, {size, mtimeMs}>. Storage.ts looks up each DB page in the map (O(1)) instead of stat-checking on demand. Slug derivation matches the pages-table convention: people/alice.md on disk becomes people/alice as the map key.  Skipped during walk:   - dot-directories (.git, .gbrain, .vscode, etc) — not part of the brain     namespace   - node_modules — guards against accidentally walking into imported repos   - non-.md files (sidecar JSON, binaries) — tracked by the brain through     the files table, not by slug  Reusable: future commands (gbrain doctor's storage_tiering check, the optional autopilot tier-fix path) get the same walk for free.  9 new test cases: empty dir, nonexistent dir, top-level files, nested dirs, dot-dir skipping, node_modules skipping, non-.md filtering, size capture, mtimeMs capture.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix: path-segment matching for tier directories (step 6/15)  Issue #5 + D6 of the eng review: tier matching used slug.startsWith(dir), which falsely matches 'media/xerox/foo' against 'media/x' if a user wrote the directory without a trailing slash.  The new matcher requires the configured directory to end with `/` and treats it as a canonical path-segment ancestor:    media/x/   matches  media/x/tweet-1       ✓   media/x/   doesn't  media/xerox/foo       ✗   media/x    refused  media/x/tweet-1       (matcher requires trailing /)  Non-canonical input (no trailing slash) is refused outright. Step 7's auto-normalizing validator converts user-written 'media/x' → 'media/x/' on load, so the matcher never sees non-canonical input from real configs. The behavior tested here is the strict matcher's contract.  Regression test pins the media/xerox collision case explicitly.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat: auto-normalize trailing-slash, throw on tier overlap (step 7/15)  D7+D8 of the eng review: validation was warnings-only. Users miss warnings. Now:    - Cosmetic: missing trailing slash auto-corrected, one-time info note     showing what changed (\"normalized 2 storage paths: 'people' →     'people/', 'media/x' → 'media/x/'\"). Once-per-process to keep noise low.    - Semantic: same directory in both tiers throws StorageConfigError.     Ambiguous routing — does media/ win as db_tracked or db_only? — is a     real bug the user must fix. Caller propagates to the CLI for a clean     exit-1 with actionable message.  loadStorageConfig now applies normalize+validate after merging deprecated keys, so the path-segment matcher (step 6) only ever sees canonical trailing-slash directories.  The pure validateStorageConfig kept for callers who want the warnings list without the auto-fix side effects (gbrain doctor's reporting path).  2 new test cases: auto-normalize round-trip with warning text assertion, overlap throws StorageConfigError.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix: wire manageGitignore into runSync, only on success (step 8/15)  Issue #2 of the eng review: manageGitignore was defined and never invoked. Docs claimed \"auto-managed by gbrain\" — false. Users hit a .gitignore that never updated and committed db_only directories anyway.  Wire-up: runSync now calls manageGitignore after each successful performSync return, in both watch and one-shot modes.  Eng review pass-2 finding #1: skip on dry_run AND blocked_by_failures status. A sync that aborted partway has stale state; mutating .gitignore based on a partially-loaded config invites drift. Failure-skip test added (uses .gitignore-as-a-directory to simulate write failure; asserts warning fired and disk wasn't corrupted).  Hardened manageGitignore itself with three additional behaviors:    - GBRAIN_NO_GITIGNORE=1 escape hatch (D23) for shared-repo setups     where a maintainer wants gbrain to leave .gitignore alone.    - Submodule detection (D49). When repoPath/.git is a regular file     (gitdir: ... pointer), the repo is a git submodule. Submodule     .gitignore changes don't survive parent submodule updates, so we     skip with an actionable warning (\"add db_only directories to your     parent repo's .gitignore manually\").    - Graceful failure (D9). Read errors, write errors, and     StorageConfigError (overlap from step 7) all log a warning and     return — sync's primary job (moving data) shouldn't die because of     a side-effect on .gitignore.  manageGitignore is now exported (previously private) so the storage-sync test file can hit it directly without spinning up sync.  9 new test cases: no-op without gbrain.yml, no-op with empty db_only, happy-path append, idempotency (run twice, single entry), preservation of user-written rules, GBRAIN_NO_GITIGNORE skip, submodule skip, .git-directory normal path, write-failure graceful warning.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * fix: D5 resolution chain for --restore-only and storage status (step 9/15)  D5 of the eng review: gbrain export --restore-only without --repo silently fell through to the regular export path, dumping every page in the database to the wrong directory. Hard regression risk.  Now exits 1 with an actionable message when --restore-only has no --repo AND no configured default source. Resolution order:   1. Explicit --repo flag   2. Typed sources.getDefault() (reuses step 1's accessor)   3. Hard error — never fall through to cwd  storage.ts:38 also bypassed BrainEngine with raw SQL and a bare try/catch (Issue #3 + Issue #9). Replaced with the same typed getDefaultSourcePath() — single source of truth, errors propagate cleanly to the user, no silent cwd fallback.  Regular export (no --restore-only) keeps its current behavior per D26: exports include everything, --repo is optional.  4 new test cases on PGLite in-memory:   - hard-errors with no --repo + no default   - explicit --repo wins   - falls back to sources default local_path   - non-restore export does not require --repo  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * refactor: split storage.ts into pure data + JSON + human formatters (step 10/15)  Issue #10 of the eng review: getStorageStatus and runStorageStatus mixed data gathering, JSON serialization, and human-readable output in one function. Hard to test, hard to reuse, mismatched the orphans.ts pattern that CLAUDE.md cites as the precedent.  Now three pure functions + a thin dispatcher:    getStorageStatus(engine, repoPath) — async, returns StorageStatusResult.     Side effects: engine.listPages + one walkBrainRepo (Issue #14).     Exported so MCP exposure (D14) and gbrain doctor (D13) can consume the     same data without re-running the loop.    formatStorageStatusJson(result) — pure, returns indented JSON. Stable     contract on the StorageStatusResult shape, suitable for orchestrators.    formatStorageStatusHuman(result) — pure, returns ASCII text (D10 — no     unicode box-drawing). Composable into other commands later.    runStorageStatus(engine, args) — thin dispatcher: parses --repo /     --json, calls getStorageStatus, picks a formatter, prints.  8 new test cases on the formatters: JSON parse round-trip, null-config fallback, missing-files capped at 10 with rollup, ASCII-only assertion (D10 regression guard), warnings inline, configuration listing, disk- usage block omitted when zero bytes.  The StorageStatusResult interface is now exported as a public type, so gbrain doctor's storage_tiering check can build its own findings from the same shape.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * types: distinct PageCountsByTier and DiskUsageByTier (step 11/15)  Issue #11 of the eng review: pagesByTier (page counts) and diskUsageByTier (byte totals) shared the same structural type (Record<StorageTier, number>). Both are tier-keyed numeric maps but carry semantically different units. A future bug that swaps them at a call site (e.g., displaying disk bytes where the count belongs) wouldn't trip the compiler.  Replaced with distinct nominal types via a brand field. Structurally identical at runtime (no overhead) but compile-time disjoint — TypeScript catches accidental cross-assignment.    PageCountsByTier   { db_tracked, db_only, unspecified } : numbers (count)   DiskUsageByTier    { db_tracked, db_only, unspecified } : numbers (bytes)  Both initialized in getStorageStatus, both threaded into StorageStatusResult, both consumed by formatStorageStatusHuman / formatStorageStatusJson without further changes.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * feat: PGLite soft-warn + full lifecycle test (step 12/15)  D4: storage tiering on PGLite is a partial feature. The \"DB\" the pages live in IS the local file gbrain uses for everything else, so \"db_only\" has no real offload effect. The .gitignore management still helps (keeps bulk content out of git history), so we warn and proceed — not refuse.  Two warning sites (once-per-process each via module-local flags):   - storage status: warns at runStorageStatus entry   - sync: warns inside manageGitignore when engineKind='pglite' and     config has db_only entries  Both phrased actionably (\"To get full tiering, migrate to Postgres with `gbrain migrate --to supabase`\").  manageGitignore signature now takes an optional `engineKind` param. runSync passes engine.kind. Stand-alone callers (tests, future gbrain doctor --fix path) can omit it.  New test: test/storage-pglite.test.ts — D8 + D4 lifecycle. 6 cases: engine.kind assertion, getStorageStatus loading gbrain.yml + reporting tier counts, manageGitignore PGLite-warn (once per process), Postgres no-warn, slugPrefix on PGLite, end-to-end (config + putPage + status + gitignore).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore: add trailing-newline CI guard (step 14/15)  Issue #7 of the eng review: all four new files in the original storage-tiering branch lacked POSIX trailing newlines. Linters complain, git diffs phantom-flag every future edit. We've been adding newlines as each file landed; this commit catches the regression class.  scripts/check-trailing-newline.sh:   - sibling to check-jsonb-pattern.sh / check-progress-to-stdout.sh per     CLAUDE.md's CI guard pattern   - portable to bash 3.2 (macOS default; no mapfile, no associative arrays)   - covers src/**, test/**, gbrain.yml, top-level *.md   - reports each missing file by path and exits 1  Wired into `bun run test` between progress-to-stdout and typecheck.  Also fixed docs/storage-tiering.md (pre-existing missing newline from the original branch — caught by the new guard on first run).  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * docs: v0.23.0 — VERSION, CHANGELOG, README, CLAUDE.md, storage-tiering.md (step 15/15)  VERSION → 0.23.0 (minor bump for new feature surface).  CHANGELOG entry in Garry voice with the canonical format:   - Two-line bold headline (\"Storage tiering, finally working...\")   - Lead paragraph naming what was broken before and what users get now   - \"Numbers that matter\" before/after table for the 6 things that     actually changed   - \"What this means for your brain\" closer   - \"To take advantage of v0.23.0\" self-repair block (per CLAUDE.md     convention) — 6 numbered steps users can follow   - Itemized changes split into critical fixes / new+renamed surface /     architecture cleanup / tests + CI guards  CLAUDE.md \"Key files\" gains four new entries: storage-config.ts, disk-walk.ts, the v0.23.0 storage.ts shape, and gbrain.yml itself.  README.md gains a new \"Storage tiering\" section between Skillify and Getting Data In with the canonical example + commands + link to the full guide.  docs/storage-tiering.md rewritten end-to-end with canonical key names (db_tracked / db_only), v0.23.0 hardening details (idempotency, submodule detection, GBRAIN_NO_GITIGNORE, dry-run gating), the resolution chain for --restore-only, the auto-normalize + throw-on-overlap validator, and the PGLite engine note.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * test: e2e Postgres lifecycle for storage tiering (step 16/16)  Per the v0.23.0 plan: full lifecycle E2E against real Postgres.    - engine.kind === 'postgres' assertion   - Full lifecycle: write 4 pages (1 db_tracked, 2 db_only, 1 unspecified)     → getStorageStatus reports correct tier counts → human formatter     renders → manageGitignore writes managed block → idempotency check     → getDefaultSourcePath() resolves the configured local_path.   - Container restart simulation: 2 db_only pages in DB, files missing     on disk → status.missingFiles.length === 2 → slugPrefix engine     filter on Postgres returns exactly the tier slugs.   - slugPrefix index-based range scan regression: 50 media/x/* + 50     people/p-* pages → slugPrefix='media/x/' returns exactly 50.   - getDefaultSourcePath returns null when default source has no     local_path (the hard-error path that replaces the original silent     cwd fallback).   - manageGitignore on Postgres engine does NOT emit the PGLite     soft-warn (cross-engine assertion).  Skips gracefully when DATABASE_URL is unset, per CLAUDE.md E2E pattern. Run via: DATABASE_URL=... bun test test/e2e/storage-tiering.test.ts  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore: rebump version 0.23.0 → 0.22.9  Reverts the minor bump back to a patch-style version on the v0.22 line. Storage tiering ships within the v0.22.x train alongside the recent fix waves. Updates VERSION, package.json, CHANGELOG header + body refs, CLAUDE.md Key files annotations, README.md section heading, and the docs/storage-tiering.md backward-compat note.  * chore: bump version 0.22.9 → 0.22.11  Sibling workspaces claimed v0.22.10 in the queue. This branch advances to v0.22.11 to keep the version monotonic on master.  Updates VERSION, package.json, CHANGELOG header + body refs, CLAUDE.md Key files annotations, README.md section heading, and the docs/storage-tiering.md backward-compat note.  * fix: address Codex pre-landing review findings (4 fixes)  Codex found 4 real issues during pre-landing review of v0.22.11 diff:  [P0] export --restore-only fell through to full export when storageConfig was null (no gbrain.yml present). On older or misconfigured brains, the recovery command would silently dump the entire database. src/commands/export.ts now refuses with an actionable error before any page query fires — matches the D5 lock spirit (\"never silently fall through\").  [P1] manageGitignore wire-up only fired when --repo was passed explicitly. performSync resolves the repo from sync.repo_path or sources.local_path, so the common `gbrain sync` path (after setup, no flag) never updated .gitignore. src/commands/sync.ts now uses the same source-resolver chain as the rest of /ship: opts.repoPath → getDefaultSourcePath → null. Fires in both watch and one-shot modes.  [P2] getDefaultSourcePath only consulted sources.local_path, missing the legacy global sync.repo_path config key that pre-v0.18 brains use. Added a fallback to engine.getConfig('sync.repo_path') when the sources row has NULL local_path. Pre-v0.18 brains now work without forcing a `gbrain sources add . --path .` migration.  [P2] sync --all multi-source loop never called manageGitignore even though src.local_path was already known. Each source now gets its own gitignore update on successful sync.  Tests:   - test/storage-export.test.ts: replaced the old \"falls through to     full export\" test with one that asserts the new refusal path     (storage-tiering config required for --restore-only).   - test/source-resolver.test.ts: added a fallback test exercising the     legacy sync.repo_path code path for pre-v0.18 brains.   - All 78 storage-tiering tests still pass.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>  * chore: regenerate llms.txt + llms-full.txt for v0.22.11  Per CLAUDE.md: \"Run `bun run build:llms` after adding a new doc.\" The README's new Storage tiering section + the rewritten docs/storage-tiering.md changed the inlined bundle. test/build-llms.test.ts catches the drift and was failing on master pre-regen.  * fix: typecheck error in disk-walk.ts (CI #73350475897)  tsc --noEmit failed in CI because ReturnType<typeof readdirSync> with withFileTypes:true picks an overload union that includes Dirent<Buffer<ArrayBufferLike>>. Strict tsc treats entry.name as Buffer, so .startsWith / .endsWith / string comparisons all blew up.  Annotate the variable as Dirent[] (string-based) and cast through unknown, matching the pattern sync.ts already uses for its own filesystem walk. Same runtime behavior; clean typecheck.  Tests still 9/9.  Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> EOF  ---------  Co-authored-by: root <root@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>") [#494](https://github.com/garrytan/gbrain/pull/494) | 4 months agoApr 30, 2026 |\
| [llms-full.txt](https://github.com/garrytan/gbrain/blob/master/llms-full.txt "llms-full.txt") | [llms-full.txt](https://github.com/garrytan/gbrain/blob/master/llms-full.txt "llms-full.txt") | [fix(skills,docs): unify-types playbooks must pass apply:true after the](https://github.com/garrytan/gbrain/commit/25e4c0c3b1fcdda583881903f0116a82de8ad796 "fix(skills,docs): unify-types playbooks must pass apply:true after the #3574 default flip  #3574 flipped the unify-types worker default to dry-run (jobs.ts:2221, apply: data.apply ?? false) and updated the architecture docs, but three agent-facing surfaces still presented the bare submit as the Apply step: skills/schema-unify/SKILL.md 'Phase 3: Apply', skills/conventions/ schema-evolution.md, and README.md. Because #3545 also edited SKILL.md in this wave, each PR looked self-consistent in isolation — only the composed branch shipped a playbook whose apply step silently retypes nothing and never flips the active pack. Skills distribute downstream via the skillpack, so this would have propagated. Found by an independent cross-PR review pass.") […](https://github.com/garrytan/gbrain/pull/3574) | 5 days agoJul 31, 2026 |\
| [llms.txt](https://github.com/garrytan/gbrain/blob/master/llms.txt "llms.txt") | [llms.txt](https://github.com/garrytan/gbrain/blob/master/llms.txt "llms.txt") | [v0.42.43.0 feat(context): push-based context (](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") [#2095](https://github.com/garrytan/gbrain/issues/2095) [) \+ teardown-exit …](https://github.com/garrytan/gbrain/commit/a81f7e05e86f98a3302c5932f341085e0e0c89fb "v0.42.43.0 feat(context): push-based context (#2095) + teardown-exit hardening (#2084) (#2175)  * fix(cli): exit deliberately after bounded teardown instead of riding the 10s backstop (#2084)  Root cause: bounded teardown (endPoolBounded, #2015) RESOLVES, but lingering sockets — embedding-provider fetch keep-alive, PgBouncer txn-mode sockets the bound raced past — keep Bun's event loop alive, so every `gbrain query` paid a flat 10s tax exiting via the hard-deadline force-exit banner.  Three changes, one contract:  - flushStdoutThenExit (cli-force-exit.ts): when main() resolves and the   command is not a daemon, exit deliberately — after stdout AND stderr drain   (writableLength===0, 'drain'-event + poll loop, 2s unref'd guard for a   blocked pipe). Incident #1959 (force-exit truncating piped stdout) is the   regression class; pinned by a 256KB real-pipe subprocess test.  - drainThenDisconnect (cli.ts): ONE owner-disconnect helper at all 8 sites   (op-dispatch, CLI_ONLY fall-through, search dashboard, doctor remediation   x3, ze-switch, dream, read-only timeout path). Drains the background-work   registry, then disconnect (best-effort), bounded by the 10s unref'd   hard-deadline — which is now armed around the TEARDOWN window only, not   before the op handler (the old placement would have force-killed any op   slower than 10s). Closes the filed TODOS P3 drain-hoist: six sites   previously skipped the drain entirely and had no hang timer at all.  - Inner process.exit sweep: mid-handler exits in engine-owning/output-bearing   paths (status, friction, claw-test, smoke-test, eval cross-modal /   takes-quality replay / conversation-parser / whoknows-thin, status-thin)   become process.exitCode + return so they flow through the drains and the   flush-exit. Pre-engine usage/parse/refusal exits stay as-is.  BrainRegistry.disconnectAll deliberately unchanged: zero production callers in src/, per-engine disconnects already bounded, and the kernel reclaims sockets on exit (src/core/timeout.ts doctrine).  DAEMON_COMMANDS gains 'watch' ahead of the #2095 push transport.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): PgBouncer transaction-mode pooler in CI + teardown e2e (#2084)  Three consecutive waves (#1972 → #2015 → #2084) fixed pooler-teardown bugs verified only against one production deployment — CI had no transaction-mode pooler and could never see the class. Now it can:  - docker-compose.ci.yml: `pgbouncer` service (transaction pooling) fronting   postgres-1, mirroring the production split-pool topology (direct :5432 +   pooled :6543). AUTH_TYPE=plain (pg16 SCRAM verifiers need the plaintext   password in the userlist) + IGNORE_STARTUP_PARAMETERS for the   statement_timeout/idle_in_transaction_session_timeout startup params   gbrain's client sets (the Supabase pooler whitelists the same). - test/e2e/pgbouncer-teardown.test.ts: schema + fixture via the DIRECT url   into a dedicated `gbrain_pgbouncer` database (never races shard TRUNCATEs),   then spawns the real CLI against the POOLED url and asserts: exit 0,   stdout intact (the #1959 truncation class), and NO   \"did not return within 10000ms — force-exiting\" banner (pre-#2084 it   printed on 100% of query-shaped ops on this topology). Class bound, not   exact timing. Skips gracefully without GBRAIN_PGBOUNCER_URL. - scripts/ci-local.sh: threads GBRAIN_PGBOUNCER_URL +   GBRAIN_PGBOUNCER_DIRECT_URL into all three e2e phases.  Verified live: both tests green against pgbouncer 1.25.2 in transaction mode.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(schema): context_volunteer_events table (v116) — push-context feedback log (#2095)  One row per page the brain volunteers (op / reflex / watch channels). \"Used\" is DERIVED, never written: pages.last_retrieved_at > volunteered_at (the existing bumpLastRetrievedAt write-back is the open/cite signal), so there is no second tracking path. session_id/turn are nullable caller-supplied attribution; rationale is a deterministic template string, never raw conversation text.  - Migration v116 (idempotent) + mirrors in src/schema.sql +   src/core/pglite-schema.ts + regenerated schema-embedded.ts (regen also   folds in pre-existing comment-only drift from the v114 links edits). - src/core/context/volunteer-events.ts: insertVolunteerEvents (ONE   multi-row parameterized INSERT — never per-row awaited round-trips) +   purgeStaleVolunteerEvents (90-day GC, returns 0 on pre-v116 brains). - Dream cycle purge phase prunes stale events alongside op_checkpoints /   brainstorm checkpoints / batch-retry audit files. - RLS on Postgres comes from the v35 auto_rls_on_create_table event   trigger (the same mechanism that covered v110 page_aliases and v115   op_checkpoint_paths); the volunteer Postgres e2e pins it. - No ::jsonb anywhere; no bootstrap probe needed (nothing references the   table pre-creation; writers guard with try/catch).  Tests: v116 shape + columns + indexes + live insert/purge round-trip on PGLite (test/migrate.test.ts, 161 pass); schema-bootstrap-coverage green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): multi-turn window extraction + confidence-scored volunteer core (#2095)  - entity-salience.ts: extractCandidatesFromWindow(turns) — runs the existing   per-turn extractor across the last N turns (oldest→newest), merges by the   normalizeAlias form with occurrence/newest-turn/user-mention metadata, and   orders by salience (recency > frequency > user-role) so the MAX_CANDIDATES   cap drops stale assistant chatter, not the entity the user just named.   Closes the filed assistant-introduced-entities recall TODO; true pronoun   coreference (never-named antecedents) stays out of scope.  - retrieval-reflex.ts: ReflexPointer gains source_id + arm + confidence +   matchedNorm. ARM_CONFIDENCE (alias 0.9 / title 0.8 / slug-suffix 0.6)   lives next to the arm definitions so identity and score can't drift.   Arm-2 provenance is classified in JS (codex D8 — the combined OR can't   report which predicate matched). Federated sourceIds[] scope (alias arm   loops per source; arm 2 uses source_id = ANY — no engine-interface   change). Suppression gains 'slug-only' mode (codex D7, REQUIRED for   windowing): the legacy title-whole-word rule would suppress every entity   merely MENTIONED in a prior window turn, breaking the feature by   construction — slugs only enter context when a pointer/page was actually   surfaced. Default stays 'slug-and-title' for the window=1 legacy path.  - volunteer.ts (new): parseWindow (lenient user:/assistant: prefixes, CRLF,   unprefixed → one user turn), volunteerContext (zero-LLM: extract →   resolve → +0.05 multi-turn/newest-turn boost → min_confidence 0.7 gate →   cap 3/5; deterministic rationale strings, never raw conversation text),   and volunteerUsageStats (per-arm/channel precision from the   last_retrieved_at join, labeled approximate — 5-min throttle false   negatives, unrelated-read false positives; codex D9).  Tests: 35 green across volunteer-context (window parsing, pronoun follow-up via assistant-introduced entity, confidence gating, slug-only suppression, takes-fence privacy, multi-source scope, caps, stats join math) + retrieval-reflex back-compat + resolve-ipc.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(ops): volunteer_context op — CLI (stdin) + MCP, drained event sink (#2095)  New read-scope op on the contract surface (CLI `gbrain volunteer-context` with stdin → window, MCP tool for free): takes a rolling conversation window, returns confidence-gated page pointers with rationales + synopses. `window` is optional-unless-stats (validated in the handler, codex D9); `stats: true` returns the volunteered-vs-used precision summary, labeled APPROXIMATE (the 5-min last-retrieved throttle and unrelated reads both bias the join). Source scope threads through sourceScopeOpts — federated grants narrow the volunteer to the granted sources.  Event logging is fire-and-forget through a new `volunteer-events` background-work sink (volunteer-events.ts, mirrors last-retrieved: tracked dangling promise set + bounded drain + snapshot-drop on timeout so a long-lived process never accumulates ghosts). ONE batched INSERT per call, drained on every exit path by the commit-1 drain hoist; failure never fails the op (pinned by an injected failing-engine test).  cli formatResult renders both shapes (pointer lines with confidence/arm/ rationale; the stats summary with per-arm precision).  Tests: op contract surface, window-required validation, sink round-trip with session_id/turn attribution, failing-engine fail-open, federated grant scoping, stats mode (26 green on PGLite) + a real-Postgres e2e proving the op + sink + stats join AND that context_volunteer_events has RLS enabled (keeps the auto-RLS event-trigger mechanism honest for v116).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(context): reflex consumes the rolling window + ambient-channel logging (#2095)  The default-on retrieval reflex now extracts entities from the last N turns (retrieval_reflex_window_turns, default 4; env GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS; window=1 reproduces the legacy current-turn-only behavior exactly). assemble() passes the recent user/assistant turns (hard cap 12); the reflex slices to the configured window. Assistant-introduced entities and \"what did she invest in?\" follow-ups whose antecedent was NAMED in the window now surface pointers — the issue's \"zero agent-initiated queries\" success criterion on the ambient path.  Under windowing, suppression switches to slug-only (codex D7): the legacy title-whole-word rule would suppress every entity merely MENTIONED in a prior window turn, breaking the feature by construction. Slugs only enter prior context when a pointer/page was actually surfaced, so already-surfaced pages still suppress. The suppression mode flows through all three resolver rungs (host opts, serve IPC request, direct Postgres).  Ambient-channel feedback (codex D11): the server-side resolver paths (serve IPC + direct Postgres) log volunteered pointers with channel: 'reflex' through the drained volunteer-events sink, so `gbrain volunteer-context --stats` measures the default-on path where most volunteering happens. Host-injected resolvers (no gbrain engine) can't log — documented gap. Precision gates, 1.5s ceiling, fail-open, and the pointer cap are unchanged.  Tests: prev-assistant-turn entity fires; window=1 legacy parity; slug-only vs already-surfaced suppression; throwing resolver stays fail-open (16 green).  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * feat(cli): gbrain watch — push transport over stdin (#2095)  The issue's headline: the brain volunteers pages as the conversation flows, instead of waiting to be asked. `some-transcript-feed | gbrain watch` reads turns line-by-line ('user:'/'assistant:' prefixes set the role; unprefixed lines are user turns), keeps a rolling window (--window-turns, default 4), and streams confidence-gated pointers with rationales to stdout (--json for JSONL). Session dedupe rides the core's slug-only suppression — a slug is volunteered at most once per session. Events log on channel 'watch' with session_id + turn through the drained sink.  Lifecycle: watch BLOCKS in the stdin iteration (like `jobs work`) — an interactive TTY stays alive until Ctrl-C/Ctrl-D, piped input ends at EOF — so it is deliberately NOT in DAEMON_COMMANDS (reverts the commit-1 placeholder): when main() resolves the work is over, the CLI_ONLY finally drains volunteer events via drainThenDisconnect, and the entrypoint flush-exit ends the process. Keeping it in the daemon set would have made the piped EOF path hang on lingering sockets — the exact #2084 class. SIGINT closes the stream and flows through the same drain path instead of killing mid-write. Per-turn resolution failures are fail-open (the stream never dies on a transient DB error).  Full wiring (eng-review D12): CLI_ONLY + CLI_ONLY_SELF_HELP (WATCH_HELP) + THIN_CLIENT_REFUSED_COMMANDS (thin clients use the volunteer_context MCP op) + main --help entry.  Tests: 18 green — help, per-turn volunteering + clean EOF return, rolling window via assistant-introduced entity, session dedupe, --json shape with turn attribution, channel-watch event rows, --min-confidence gate, CRLF/ blank tolerance, daemon-gate semantics. Live smoke: piped `gbrain watch` on a fresh PGLite brain exits 0 at EOF with no force-exit banner.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: KEY_FILES + push-context guide + TODOS for the #2084/#2095 wave  - docs/architecture/KEY_FILES.md (current-state): context entries gain the   window extractor, arm provenance/confidence, suppression modes, volunteer   + volunteer-events modules; background-work entry now lists FIVE sinks and   the drainThenDisconnect owner-disconnect contract; new entries for   src/core/cli-force-exit.ts (the exit contract) and src/commands/watch.ts. - docs/guides/push-context.md (new): the three channels (reflex/op/watch),   the confidence model, CLI usage, config keys, and the approximate-stats   caveat. Linked from CLAUDE.md's reference map. - CLAUDE.md: ops line mentions volunteer_context + the guide link;   bun run build:llms regenerated in the same commit (freshness test green). - TODOS.md: #2095 deferrals filed (SSE push channel, policy skill + doctor   check, structured messages[] param); the #1981 entity-detection TODO   narrowed (window extraction covered assistant-introduced entities +   named-antecedent follow-ups); the drain-hoist P3 marked DONE by the   #2084 wave.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(e2e): truncate context_volunteer_events in setupDB (#2095)  The new feedback-log table wasn't in ALL_TABLES, so volunteered-event rows persisted across e2e runs on a reused database and poisoned count/stats assertions in volunteer-context-postgres on the second run. No FK to pages (slug join), so position before pages is for hygiene only.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(cli): own the exit verdict — never trust ambient process.exitCode (#2084)  Caught by the full unit suite: `gbrain apply-migrations` on PGLite started exiting 99. Root cause: PGLite's Emscripten runtime writes the WASM backend's proc_exit status into process.exitCode (initdb at create-time, the postmaster at close-time — `exitCode=status` in pglite's dist), and the writes land ASYNCHRONOUSLY, outside any snapshot/restore window around create/close (a guarded attempt verified this). The pre-#2084 success path never read process.exitCode, so the pollution was invisible; the new deliberate flush-exit propagated it faithfully.  Fix: gbrain records its own verdict. setCliExitCode(n)/getCliExitCode() in cli-force-exit.ts — every gbrain-owned exit-code assignment routes through the setter (still mirrored to process.exitCode for outside readers), and both exit paths (entrypoint flushStdoutThenExit + the drainThenDisconnect hard-deadline backstop) read the getter. Swept all assignment sites: cli.ts (op error, friction, claw-test, smoke-test, eval runners, status, import errors) + reindex/transcripts/brainstorm/frontmatter/autopilot.  Also updates the v0.42.20 structural pins to the drainThenDisconnect shape (ordering invariant asserted INSIDE the helper + >=8 helper call sites, superseding the two-inline-pairs assertion).  Verified: apply-migrations spawn test green; `init --migrate-only` exits 0; an errored op still exits 1.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: re-pin the teardown-arming invariant at its post-#2084 home  Master's v0.42.41.0 triage wave and the #2084 wave fixed the same pre-armed-timer bug independently; the merge keeps #2084's shape (arming inside the shared drainThenDisconnect helper, covering all 8 exit paths). The structural pin now asserts the same invariant — no pre-try arming; gated, unref'd, before-drain, cleared — at the helper.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: coverage for ambient reflex-channel logging + watch window/cap flags  Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: close the remaining plan-audit gaps — formatResult rendering + watch SIGINT  formatResult exported for tests (same import-safety contract as cliAliases); test/cli-format-volunteer.test.ts pins the pointer lines, empty-gate message, and approximate stats summary. test/watch-command.test.ts gains a real subprocess SIGINT test: piped stdin that never reaches EOF, SIGINT mid-stream, assert exit 0 with no force-exit banner — the drain-then-exit lifecycle under the actual signal, not just the shared exit path.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix: doctor's FAIL verdict was zeroed by the owned exit — sweep stragglers + class pin  The merged-state suite caught it: doctor --fast --json reported FAIL but exited 0. Master's v0.42.41.0 brought raw `process.exitCode =` writes (doctor.ts hasFail ternary, extract.ts) that the #2084 verdict-owning exit silently zeroes — getCliExitCode() deliberately never reads ambient process.exitCode (the PGLite-Emscripten pollution defense), so any setter that bypasses setCliExitCode reports success on failure.  Swept both sites and added the structural class pin: a test greps src/ for raw `process.exitCode =` outside cli-force-exit.ts, so the next merge that introduces one fails loudly instead of lying about exit codes.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * chore: bump version and changelog (v0.42.43.0)  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test: quarantine the watch SIGINT subprocess test to the serial lane  The parallel unit shards flake on concurrent CLI subprocess spawns (failed at 7ms in-suite, green solo) — same isolation rationale as apply-migrations-pglite-spawn.serial.test.ts and #2141's R3 quarantine.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs: update project documentation for v0.42.43.0  Post-ship doc verification against the release diff (#2095 push-based context + #2084 superset hardening), with a cross-model doc review:  - push-context.md: version tag corrected to v0.42.43.0; per-call knobs   now cover prior_context/days and watch's flag surface accurately;   feedback-log writes described as best-effort; synopsis fence-strip   described as unconditional. - CLAUDE.md: stale operation count (~47 -> ~90); volunteer_context   release reference corrected to v0.42.43.0. - KEY_FILES.md: ci-local entry rewritten to current topology (4-shard   parallel default, four Postgres services, transaction-mode PgBouncer   + GBRAIN_PGBOUNCER_URL/_DIRECT_URL exports); stale E2E file counts   dropped from the selector entry. - TESTING.md: inventory entries for the new #2084 structural pins   (cli-exit-verdict-pin, cli-pipe-truncation), the push-context test   suite (volunteer-context, watch-command, watch-sigint.serial,   cli-format-volunteer), migrate v117 coverage, and the two new E2E   files (pgbouncer-teardown env gating, volunteer-context-postgres RLS   pin); check:all row corrected (not a superset of verify). - AGENTS.md + RELEASING.md: ci:local descriptions updated to the   sharded + pooler topology. - CHANGELOG (wording only, entry preserved): \"retrieved\" instead of   \"opened\" for the used-signal, pooler scoped to the local CI gate,   feedback log labeled best-effort. - llms-config.ts: index the new push-context guide; bundles   regenerated (build:llms) and freshness test green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * docs(test): correct the v116 reference — the table shipped as migration v117  * fix: pre-landing review hardening — federated alias parallelism, trust-boundary clamps, shared protocol helpers (#2095)  Five specialist reviewers (testing/maintainability/security/performance/ data-migration) on the reconciled diff; every finding applied:  Performance: the alias arm now resolves all granted sources CONCURRENTLY (a federated caller paid M sequential RTTs per turn — ~355ms at 5 sources cross-region, inside the reflex's 1.5s budget); watch's session dedupe is O(1) Set membership instead of a monotonically growing priorContext string (O(T²) over a long-lived session); getWindowTurns iterates from the tail (per-turn cost no longer grows with session length); the resolver's provenance maps fold into the existing candidate pass.  Security: volunteer_context clamps caller-supplied attribution at the trust boundary — session_id capped at 256 chars (a read-scoped token could bank ~1MiB TEXT per request, retained 90 days), turn logged only when a safe integer (a non-integer threw inside the batched INSERT and silently dropped the whole batch). The privacy comments now state precisely what rationale may contain (the matched entity's surface form — which by construction resolved to an existing alias/title/slug — never free conversation text).  Maintainability: TURN_PREFIX_RE + formatVolunteeredPage exported from volunteer.ts and shared by watch/cli (the two surfaces can no longer drift); volunteerEventRowsFrom is the single VolunteerEventRow assembly site for all three channels; watch's window default now honors the same retrieval_reflex_window_turns config knob the reflex reads; the stale pre-v116 comments swept to pre-v117.  Testing: the two flake-class CRITICALs fixed (pipe test asserts the backstop banner instead of a cold-CI-hostile 9s wall bound; the SIGINT test waits on watch's new machine-readable ready line instead of a fixed 15s sleep — 2.5s and deterministic now); new coverage for the sink's timeout branch + ghost-reference drop, watch per-turn fail-open, untrusted knob clamps (min_confidence/max_pages/days), window-cap ordering (newest user mention survives), serve-IPC suppression passthrough + channel=reflex logging, windowTurnCount edge semantics, and structural pins for the sink registration + cycle purge wiring. The exit-verdict pin's grep is now operator/whitespace-tolerant.  Deferred with TODOs: resolver index shapes for the per-turn query; batched first-prune after a long dream-cycle gap.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): red-team hardening — pre-cap dedupe, delivery-side reflex logging, window clamp  Four red-team findings on the #2095 push-context surface:  - RT1 starvation: watch's session-dedupe Set filtered AFTER volunteerContext's   cap, so a recurring already-pushed entity burned cap slots every turn and   starved fresh pages behind it. VolunteerOpts.excludeSlugs now skips inside   the pointer loop BEFORE the confidence gate and the cap. - RT3 honest stats: reflex-channel event logging moved from inside the   resolver to the DELIVERY point — serve's resolve-IPC onDelivered hook fires   only after the response write succeeds, and buildReflexAddition logs only   after the per-turn timeout admits the block. A block the client's 250ms   budget abandoned was never injected and no longer counts as volunteered.   (logChannel resolver opt removed; logDeliveredReflexPointers is the seam.) - RT5 unbounded window: --window-turns is clamped to [1, 64] so a config typo   can't reintroduce the re-scan-everything-per-turn cost class. - RT2/RT4 documented + filed: PGLite watch connection monopoly (WATCH_HELP,   push-context guide, TODO to route watch via serve IPC); host-resolver   suppression contract at ResolveEntitiesFn (TODO for a capability gate).  Tests: starvation guard (watch + volunteerContext unit), window clamp floor + ceiling, delivery-side logging (helper writes channel=reflex through the drained sink; bare resolver writes nothing; empty list no-op), IPC wiring test rewired to onDelivered. KEY_FILES.md + push-context.md updated; build:llms run.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * fix(context): env-plane window knob works config-less; harden two gateway-state-leak victims  Three CI-only check failures, two root causes:  1. windowTurnCount ignored GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS when    loadConfig() returned null (no config file AND no DATABASE_URL — a clean    CI shard with no brain). loadConfig drops its env→config mapping in that    case, so the documented escape hatch silently died and the window fell    back to 4 → windowed extraction widened when the test set window=1 →    prior-turn entity leaked. Fixed: read the env var directly in    windowTurnCount, mirroring reflexEnabled's direct process.env read. This    is a real product bug, not just a test artifact — any config-less host    using the env hatch was affected. Regression test pins it.  2. sync-cost-preview + doctor-federation-health failed only IN-SHARD: a    sibling test configured a non-legacy (ZeroEntropy 1280-d / $0.05) gateway    and never reset it. The legacy-embedding preload only restores the    OpenAI/1536 default when the gateway slot is EMPTY, so a non-empty foreign    config survives into the next file — and a file's beforeAll runs BEFORE    the preload's restoring beforeEach, so federation-health built a    vector(1280) column and its 1536-d fixture hit CheckExpectedDim. My new    test files reshuffled the deterministic file→shard assignment, exposing    this latent ordering bug. Hardened both victims to establish the gateway    state they assert (sync-cost-preview resets to the unconfigured fallback;    federation-health pins legacy 1536 before initSchema) so they're    order-independent. Verified against a simulated leaker run before them.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  * test(context): use withEnv() in the window env-hatch test (test-isolation guard)  The regression test added in 82cc7fff mutated process.env directly, which check:test-isolation (R1) forbids — use the withEnv() helper that restores on exit, same as the rest of this file. Behavior identical; guard green.  Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>  ---------  Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | 2 months agoJun 14, 2026 |\
| [openclaw.plugin.json](https://github.com/garrytan/gbrain/blob/master/openclaw.plugin.json "openclaw.plugin.json") | [openclaw.plugin.json](https://github.com/garrytan/gbrain/blob/master/openclaw.plugin.json "openclaw.plugin.json") | [fix(openclaw): declare gbrain plugin manifest entry (takeover of](https://github.com/garrytan/gbrain/commit/d9eb027bdd75928bf39276769af5cbbc4e375e43 "fix(openclaw): declare gbrain plugin manifest entry (takeover of #2551) (#3185)  Add the OpenClaw-required top-level id to openclaw.plugin.json, export a direct register(api) entrypoint from src/openclaw-context-engine.ts, add a manifest regression test, and document that skillpack harvest must preserve OpenClaw-native manifest fields (id, configSchema, contracts).  llms bundles regenerated (bun run build:llms) — no content drift.  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Filip <FilipHarald@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") [#2551](https://github.com/garrytan/gbrain/pull/2551) [)…](https://github.com/garrytan/gbrain/commit/d9eb027bdd75928bf39276769af5cbbc4e375e43 "fix(openclaw): declare gbrain plugin manifest entry (takeover of #2551) (#3185)  Add the OpenClaw-required top-level id to openclaw.plugin.json, export a direct register(api) entrypoint from src/openclaw-context-engine.ts, add a manifest regression test, and document that skillpack harvest must preserve OpenClaw-native manifest fields (id, configSchema, contracts).  llms bundles regenerated (bun run build:llms) — no content drift.  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Filip <FilipHarald@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>") | 2 weeks agoJul 22, 2026 |\
| [package.json](https://github.com/garrytan/gbrain/blob/master/package.json "package.json") | [package.json](https://github.com/garrytan/gbrain/blob/master/package.json "package.json") | [v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's…](https://github.com/garrytan/gbrain/commit/15b9863d13635d173562a54f55a1d388bfcf546b "v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope (#3809)  * fix(security): fence the dedup-resolved slug under the caller's own confinement  put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only. The delegated submit_agent -> subagent context carries `viaSubagent` + `allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent` scope could delegate a write and have importFromContent's dedup pre-check redirect it onto a page outside its grant — where the disk write-through then re-rendered the victim's file with the caller's provenance.  The re-check now applies whichever confinement the caller is actually under (OAuth binding and/or subagent allow-list / legacy namespace) via `slugOutsideCallerFence`, which composes the existing match rules rather than re-deriving them. Dedup returns status 'skipped' before any DB write, so the throw still rolls nothing back. The denial does not name the resolved slug (slug-enumeration oracle) and reads \"your write scope\", since either confinement can trigger it.  Reported privately by Aleksei Razsadin.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * test: coverage for the OAuth in-fence redirect and the missing-subagentId guard  * v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope  VERSION + package.json + CHANGELOG for 0.42.73.2.  Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>  * docs: state that the write fence follows a delegated write  ---------  Co-authored-by: Garry Tan <garrytan@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>") | yesterdayAug 4, 2026 |\
| [tsconfig.json](https://github.com/garrytan/gbrain/blob/master/tsconfig.json "tsconfig.json") | [tsconfig.json](https://github.com/garrytan/gbrain/blob/master/tsconfig.json "tsconfig.json") | [feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (](https://github.com/garrytan/gbrain/commit/b22cbd349ac2787ca47da98a7026a3a923f82006 "feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (#1)  * chore: add CLAUDE.md with project context and gstack skill routing rules  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: initialize project with Bun + TypeScript  package.json with dependencies (postgres, pgvector, openai, anthropic, MCP SDK, gray-matter). TypeScript config targeting ESNext with bundler module resolution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add foundation layer — engine interface, Postgres engine, schema  BrainEngine pluggable interface with full PostgresEngine: CRUD, search (keyword + vector), links, tags, timeline, versions, stats, health, ingest log, config. Trigger-based tsvector spanning pages + timeline_entries. Markdown parser with frontmatter, compiled_truth / timeline splitting, and round-trip serialization. 19 tests passing.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 3-tier chunking and embedding service  Recursive delimiter-aware chunker (5-level hierarchy, 300-word chunks, 50-word overlap). Semantic chunker with Savitzky-Golay boundary detection and recursive fallback. LLM-guided chunker via Claude Haiku with sliding window topic detection. OpenAI embedding service with batch support, exponential backoff, and rate limit handling.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add hybrid search with RRF fusion, expansion, and 4-layer dedup  Hybrid search merges vector (pgvector HNSW) + keyword (tsvector) via Reciprocal Rank Fusion. Multi-query expansion via Claude Haiku generates 2 alternative phrasings. 4-layer dedup pipeline: by source, cosine similarity, type diversity (60% cap), per-page cap.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add GBRAIN_V0 spec, pluggable engine architecture, SQLite engine plan  GBRAIN_V0.md: full product spec with architecture decisions, CLI commands, schema, search architecture, chunking strategies, first-time experience, and future plans. ENGINES.md: pluggable engine interface, capability matrix, how to add new backends. SQLITE_ENGINE.md: complete SQLite implementation plan with schema, FTS5 setup, vector search options, and contributor guide.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add CLI with all commands  Full CLI dispatcher with 25+ commands: init (Supabase wizard), get, put, delete, list, search, query (hybrid RRF), import (bulk with progress bar), export (round-trip), embed, stats, health, tag/untag/tags, link/unlink/ backlinks/graph, timeline/timeline-add, history/revert, config, upgrade, serve, call. Smart slug resolution on reads. Version snapshots on updates.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add MCP stdio server with all brain tools  20 MCP tools mirroring CLI operations: get/put/delete/list pages, search (keyword), query (hybrid RRF + expansion), tags, links with graph traversal, timeline, stats, health, version history, and revert. Auto-chunks and embeds on put_page. CLI and MCP share the same engine.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 6 skill files and ClawHub manifest  Fat markdown skills for AI agents: ingest (meetings/docs/articles with timeline merge), query (3-layer search + synthesis + citations), maintain (health checks, stale detection, orphan audit), enrich (external API enrichment), briefing (daily briefing compilation), migrate (universal migration from Obsidian/Notion/Logseq/markdown/CSV/JSON/Roam). ClawHub manifest for skill distribution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add README, CONTRIBUTING, update CLAUDE.md test references  README with quickstart, commands, architecture, library usage, MCP setup, and links to design docs. CONTRIBUTING with setup, project structure, and guides for adding commands and engines. CLAUDE.md updated to reference actual test files instead of planned-but-unwritten import test.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: address adversarial review findings — 5 critical/high fixes  - revertToVersion: add page_id check to prevent cross-page data corruption - traverseGraph: use UNION instead of UNION ALL for cycle safety - embedAll: preserve all chunks when embedding stale subset only - embedding: throw on retry exhaustion instead of returning zero vectors - putPage: validate slugs to prevent path traversal on export  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.1.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: expand README with schema, install, search architecture, and motivation  Why it exists, how search works (with ASCII diagram), full database schema with all 9 tables and index details, chunking strategies explained, storage estimates, setup wizard walkthrough, knowledge model with example page, library usage with more examples, expanded skills table.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: add MIT license (Copyright 2026 Garry Tan)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add OpenClaw install flow as primary option in README  OpenClaw users just say \"install gbrain\" and the orchestrator handles everything: package install, Supabase setup wizard, skill registration. Shows the conversational interface for querying, ingesting, and briefings. ClawHub and standalone CLI paths follow as alternatives.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add prerequisites and explicit OpenClaw install instructions  Prerequisites table listing Supabase, OpenAI, and Anthropic dependencies with links. Environment variable setup. Explicit step-by-step prompt for OpenClaw users showing exactly what to tell the orchestrator. Note that search degrades gracefully without API keys (keyword-only without OpenAI, no expansion without Anthropic).  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: scrub named references, add PG essay demo section to README  Replace all Pedro/Brex/Jensen Huang/River AI examples with Paul Graham essay examples using the kindling corpus. Add \"Try it\" section to README showing the power of hybrid search on PG essays in 90 seconds. Update test fixtures to use concept pages instead of person pages.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") [#1](https://github.com/garrytan/gbrain/pull/1) [)](https://github.com/garrytan/gbrain/commit/b22cbd349ac2787ca47da98a7026a3a923f82006 "feat: GBrain v0.1.0 — Postgres-native personal knowledge brain (#1)  * chore: add CLAUDE.md with project context and gstack skill routing rules  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: initialize project with Bun + TypeScript  package.json with dependencies (postgres, pgvector, openai, anthropic, MCP SDK, gray-matter). TypeScript config targeting ESNext with bundler module resolution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add foundation layer — engine interface, Postgres engine, schema  BrainEngine pluggable interface with full PostgresEngine: CRUD, search (keyword + vector), links, tags, timeline, versions, stats, health, ingest log, config. Trigger-based tsvector spanning pages + timeline_entries. Markdown parser with frontmatter, compiled_truth / timeline splitting, and round-trip serialization. 19 tests passing.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 3-tier chunking and embedding service  Recursive delimiter-aware chunker (5-level hierarchy, 300-word chunks, 50-word overlap). Semantic chunker with Savitzky-Golay boundary detection and recursive fallback. LLM-guided chunker via Claude Haiku with sliding window topic detection. OpenAI embedding service with batch support, exponential backoff, and rate limit handling.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add hybrid search with RRF fusion, expansion, and 4-layer dedup  Hybrid search merges vector (pgvector HNSW) + keyword (tsvector) via Reciprocal Rank Fusion. Multi-query expansion via Claude Haiku generates 2 alternative phrasings. 4-layer dedup pipeline: by source, cosine similarity, type diversity (60% cap), per-page cap.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add GBRAIN_V0 spec, pluggable engine architecture, SQLite engine plan  GBRAIN_V0.md: full product spec with architecture decisions, CLI commands, schema, search architecture, chunking strategies, first-time experience, and future plans. ENGINES.md: pluggable engine interface, capability matrix, how to add new backends. SQLITE_ENGINE.md: complete SQLite implementation plan with schema, FTS5 setup, vector search options, and contributor guide.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add CLI with all commands  Full CLI dispatcher with 25+ commands: init (Supabase wizard), get, put, delete, list, search, query (hybrid RRF), import (bulk with progress bar), export (round-trip), embed, stats, health, tag/untag/tags, link/unlink/ backlinks/graph, timeline/timeline-add, history/revert, config, upgrade, serve, call. Smart slug resolution on reads. Version snapshots on updates.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add MCP stdio server with all brain tools  20 MCP tools mirroring CLI operations: get/put/delete/list pages, search (keyword), query (hybrid RRF + expansion), tags, links with graph traversal, timeline, stats, health, version history, and revert. Auto-chunks and embeds on put_page. CLI and MCP share the same engine.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * feat: add 6 skill files and ClawHub manifest  Fat markdown skills for AI agents: ingest (meetings/docs/articles with timeline merge), query (3-layer search + synthesis + citations), maintain (health checks, stale detection, orphan audit), enrich (external API enrichment), briefing (daily briefing compilation), migrate (universal migration from Obsidian/Notion/Logseq/markdown/CSV/JSON/Roam). ClawHub manifest for skill distribution.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add README, CONTRIBUTING, update CLAUDE.md test references  README with quickstart, commands, architecture, library usage, MCP setup, and links to design docs. CONTRIBUTING with setup, project structure, and guides for adding commands and engines. CLAUDE.md updated to reference actual test files instead of planned-but-unwritten import test.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * fix: address adversarial review findings — 5 critical/high fixes  - revertToVersion: add page_id check to prevent cross-page data corruption - traverseGraph: use UNION instead of UNION ALL for cycle safety - embedAll: preserve all chunks when embedding stale subset only - embedding: throw on retry exhaustion instead of returning zero vectors - putPage: validate slugs to prevent path traversal on export  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: bump version and changelog (v0.1.0)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: expand README with schema, install, search architecture, and motivation  Why it exists, how search works (with ASCII diagram), full database schema with all 9 tables and index details, chunking strategies explained, storage estimates, setup wizard walkthrough, knowledge model with example page, library usage with more examples, expanded skills table.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * chore: add MIT license (Copyright 2026 Garry Tan)  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add OpenClaw install flow as primary option in README  OpenClaw users just say \"install gbrain\" and the orchestrator handles everything: package install, Supabase setup wizard, skill registration. Shows the conversational interface for querying, ingesting, and briefings. ClawHub and standalone CLI paths follow as alternatives.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: add prerequisites and explicit OpenClaw install instructions  Prerequisites table listing Supabase, OpenAI, and Anthropic dependencies with links. Environment variable setup. Explicit step-by-step prompt for OpenClaw users showing exactly what to tell the orchestrator. Note that search degrades gracefully without API keys (keyword-only without OpenAI, no expansion without Anthropic).  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  * docs: scrub named references, add PG essay demo section to README  Replace all Pedro/Brex/Jensen Huang/River AI examples with Paul Graham essay examples using the kindling corpus. Add \"Try it\" section to README showing the power of hybrid search on PG essays in 90 seconds. Update test fixtures to use concept pages instead of person pages.  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>  ---------  Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>") | 4 months agoApr 5, 2026 |\
| View all files |\
\
## Repository files navigation\
\
# GBrain\
\
[Permalink: GBrain](https://github.com/garrytan/gbrain#gbrain)\
\
**Search gives you raw pages. GBrain gives you the answer.** It's the brain layer your AI agent has been missing — the only one that does synthesis, graph traversal, and gap analysis in one box. Run a full autonomous agent on top of it, or just wire it into Claude Code or Codex as a supercharged retrieval layer in one command; either way your coding agent stops being amnesiac about everything that isn't code.\
\
I'm Garry Tan, President and CEO of Y Combinator. I built GBrain to run my own AI agents. It's the production brain behind my OpenClaw and Hermes deployments: **146,646 pages, 24,585 people, 5,339 companies**, 66 cron jobs running autonomously. My agent ingests meetings, emails, tweets, voice calls, and original ideas while I sleep. It enriches every person and company it encounters. It fixes its own citations and consolidates memory overnight. I wake up smarter than when I went to bed — and so will you.\
\
**And now it works as a company brain too.** Each person on the team gets their own slice of the brain, scoped by login. When you query, you only see what you're allowed to see — never another person's notes, never another team's data. We fuzz-tested this across every way you can read the brain (search, list, lookup, multi-source reads) and got zero leaks. Drop GBrain in as your team's shared institutional memory — the [company-brain](https://www.ycombinator.com/rfs#company-brain) shape YC just put on its Request for Startups. If you're building in that space, you might as well build on this. **[Tutorial: set up GBrain as your company brain →](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/company-brain.md)**\
\
Lots of personal-knowledge systems give you keyword matching and grep in a box. GBrain does that, and adds two things nobody else ships together:\
\
- **A synthesis layer that gives you the actual answer.** Synthesized, well-cited prose across people, companies, deals, and ideas. Not "here are 10 chunks that mention your query"; an actual answer with citations and an explicit note on what the brain doesn't know yet. The gap analysis is the part that changes how you use the brain.\
- **A self-wiring knowledge graph.** Every page write extracts entity refs and creates typed edges (`attended`, `works_at`, `invested_in`, `founded`, `advises`) with zero LLM calls. Ask "who works at Acme AI?" or "what did Bob invest in this quarter?" and get answers vector search alone can't reach. Benchmarked: **P@5 49.1%, R@5 97.9%** on a 240-page Opus-generated rich-prose corpus, **+31.4 points P@5** over its graph-disabled variant and over ripgrep-BM25 + vector-only RAG by a similar margin. Full BrainBench scorecards live in the sibling [gbrain-evals](https://github.com/garrytan/gbrain-evals) repo.\
\
The point of building a 100K-page brain is to use it as a strategic moat. To never lose context. To query what's in your own head without re-reading it. The brain layer is what makes the moat usable. The 24/7 dream cycle is what keeps it sharp. Both run on your hardware, your DB, your keys.\
\
It's easier to ship a daemon that runs 24/7 to ingest, enrich, and consolidate than it is to keep an agent in chat working hard. GBrain is that daemon, generalized. Install in 30 minutes. Your agent does the work. As my personal agent gets smarter, so does yours.\
\
> **~30 minutes to a fully working brain.** Database ready in 2 seconds (PGLite, no server). You just answer questions about API keys.\
\
> **LLMs:** fetch [`llms.txt`](https://github.com/garrytan/gbrain/blob/master/llms.txt) for the documentation map, or [`llms-full.txt`](https://github.com/garrytan/gbrain/blob/master/llms-full.txt) for the same map with core docs inlined in one fetch. **Agents:** start with [`AGENTS.md`](https://github.com/garrytan/gbrain/blob/master/AGENTS.md) (or [`CLAUDE.md`](https://github.com/garrytan/gbrain/blob/master/CLAUDE.md) if you're Claude Code).\
\
## What this looks like\
\
[Permalink: What this looks like](https://github.com/garrytan/gbrain#what-this-looks-like)\
\
Say you have a meeting with Alice tomorrow. You want to walk in remembering what she works on, when you last talked, and what's still open between you. Here's what you'd type, and what you'd get back.\
\
**You ask:**\
\
> "What do I need to know before my meeting with Alice tomorrow?"\
\
**Most personal-knowledge tools give you back a list of pages.** Something like:\
\
```\
1. people/alice — Alice runs engineering at Acme...\
2. meetings/2026-03-15-alice-q1 — Q1 product review with Alice...\
3. meetings/2026-01-08-acme-kickoff — Kickoff meeting with Acme team...\
4. customers/acme — Acme is a series-B fintech we work with...\
5. notes/2026-04-22 — Quick chat with Alice about pricing...\
```\
\
Five pages you now have to open and read yourself to actually prepare. The tool found the right material, but it didn't do the work.\
\
**GBrain gives you back the answer, with sources:**\
\
```\
Alice runs engineering at Acme (a series-B fintech). You last spoke\
on April 22 in a quick pricing chat. Three things are still open\
from that conversation:\
\
1. She owes you the security review for the new tier\
   (deadline was May 1; no update since).\
2. You committed to pricing for a 500-seat tier\
   (you sent it April 25; no response yet).\
3. She mentioned they're hiring a CISO; you said you'd intro\
   someone from your network.\
\
Heads up: nothing's been added to the brain about Alice or Acme\
since April 22, six weeks ago. She may have replied through email\
or Slack DM, channels the brain doesn't see. Worth asking her to\
catch up before assuming any of this is still current.\
```\
\
Every claim has a source page behind it. The "heads up" at the end tells you what the brain doesn't know yet, so you can ask Alice about it directly instead of being surprised. The brain just did your meeting prep.\
\
This is the difference between a search engine and a brain. Search finds the pages. The brain reads them for you and writes the answer.\
\
## Install\
\
[Permalink: Install](https://github.com/garrytan/gbrain#install)\
\
Warning\
\
**GBrain is NOT distributed on npm.** The npm package named `gbrain` is an unrelated\
package with no connection to this project. Do not run `npm install -g gbrain` or\
`bun add -g gbrain` — you'll get something else, and it can shadow the real binary on\
your PATH. Install and upgrade ONLY via the documented paths below\
(`bun install -g github:garrytan/gbrain`, or `git clone` \+ `bun install && bun link`).\
If you already ran the npm install by mistake: `npm uninstall -g gbrain` /\
`bun remove -g gbrain`, then reinstall from GitHub. `gbrain doctor` detects a\
shadowing npm install and prints the fix.\
\
GBrain is designed to be installed and operated by an AI agent. The fastest path is to have your agent do it for you. The CLI and MCP paths below are for people who want to wire it up themselves.\
\
### Have your agent install it (recommended)\
\
[Permalink: Have your agent install it (recommended)](https://github.com/garrytan/gbrain#have-your-agent-install-it-recommended)\
\
If you don't already have an AI agent platform running, start with one of these. Both are designed to read GBrain's install protocol and execute it:\
\
- **[OpenClaw](https://github.com/openclaw/openclaw)** — deploy [AlphaClaw on Render](https://render.com/deploy?repo=https://github.com/chrysb/alphaclaw) (one click, 8GB+ RAM)\
- **[Hermes](https://github.com/NousResearch/hermes-agent)** — deploy on [Railway](https://github.com/praveen-ks-2001/hermes-agent-template) (one click)\
\
Then paste this into your agent:\
\
```\
Retrieve and follow the instructions at:\
https://raw.githubusercontent.com/garrytan/gbrain/master/INSTALL_FOR_AGENTS.md\
```\
\
The agent installs GBrain, creates the brain, asks for your API keys, loads 43 skills, configures the dream cycle, and verifies the install end-to-end. ~30 minutes. You answer questions, it does the work.\
\
> **Never set up an AI agent platform before?** The [personal-brain tutorial](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/personal-brain.md) walks the whole path end-to-end — picking OpenClaw vs Hermes, deploying it, pointing it at INSTALL\_FOR\_AGENTS.md, getting the API keys, and verifying the first query. Start there if any of the above is new.\
\
### Quick start: Claude Code or Codex\
\
[Permalink: Quick start: Claude Code or Codex](https://github.com/garrytan/gbrain#quick-start-claude-code-or-codex)\
\
Already running Claude Code or Codex? There are two ways to wire GBrain in, depending on what you want.\
\
**Just want a memory for your coding agent (recommended starting point).** Spin up a local brain and connect it in two commands — zero server, zero token, zero tunnel:\
\
```\
gbrain init --pglite                     # 2-second local brain (no Docker)\
claude mcp add gbrain -- gbrain serve    # or: codex mcp add gbrain -- gbrain serve\
```\
\
**Already have a brain on a remote host** (OpenClaw, Hermes, or any `gbrain serve --http`)? Point your laptop agents at it with one command each — `--install` wires it up and smoke-tests the token before handoff:\
\
```\
gbrain connect https://your-host/mcp --token gbrain_xxx --install               # Claude Code\
gbrain connect https://your-host/mcp --token gbrain_xxx --agent codex --install # Codex\
```\
\
**[→ Full walkthrough: give your coding agent a memory](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/connect-coding-agent.md)** — both paths end to end, plus the brain-first protocol you paste into `CLAUDE.md` / `AGENTS.md` and the four habits that make it actually change how you work.\
\
### Install the full autonomous setup into your existing agent\
\
[Permalink: Install the full autonomous setup into your existing agent](https://github.com/garrytan/gbrain#install-the-full-autonomous-setup-into-your-existing-agent)\
\
Want the whole thing — local brain, 43 skills, the overnight dream cycle that enriches while you sleep? Paste this into Codex, Claude Code, Cursor, or another coding agent:\
\
```\
Retrieve and follow the instructions at:\
https://raw.githubusercontent.com/garrytan/gbrain/master/INSTALL_FOR_AGENTS.md\
```\
\
This works in any agent that can read files over HTTPS and execute shell commands. Tested with Codex, Claude Code, Claude Cowork, Cursor, and AlphaClaw.\
\
### CLI standalone (no agent)\
\
[Permalink: CLI standalone (no agent)](https://github.com/garrytan/gbrain#cli-standalone-no-agent)\
\
```\
bun install -g github:garrytan/gbrain\
gbrain init --pglite     # 2 seconds; no server, no Docker\
gbrain doctor            # verify health\
gbrain import ~/notes/   # index your markdown\
gbrain query "what themes show up across my notes?"\
```\
\
Postgres-at-scale, Supabase, and thin-client setup paths live in [`docs/INSTALL.md`](https://github.com/garrytan/gbrain/blob/master/docs/INSTALL.md).\
\
### Connect GBrain to your AI client (MCP)\
\
[Permalink: Connect GBrain to your AI client (MCP)](https://github.com/garrytan/gbrain#connect-gbrain-to-your-ai-client-mcp)\
\
GBrain exposes 30+ tools over MCP (stdio and HTTP). The specific snippet depends on which client you use:\
\
- **[Claude Code](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CLAUDE_CODE.md)** — local: one command, `claude mcp add gbrain -- gbrain serve` (zero server, zero tunnel). Remote with just a bearer token: `gbrain connect https://your-host/mcp --token gbrain_xxx` prints a paste-ready block (or `--install` wires it up and smoke-tests the token).\
- **[Codex](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CODEX.md)** — `gbrain connect https://your-host/mcp --token gbrain_xxx --agent codex` (or `--install`). Codex reads the bearer from `$GBRAIN_REMOTE_TOKEN` at runtime, so the token never lands in Codex config.\
- **[Cursor / Windsurf / any stdio MCP client](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CLAUDE_CODE.md)** — same shape, add `{"command": "gbrain", "args": ["serve"]}` to your MCP config.\
- **[Claude Desktop (Cowork)](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CLAUDE_DESKTOP.md)** — Settings → Integrations → add the URL of your HTTP server. Remote only; the local `claude_desktop_config.json` does not work for remote servers.\
- **[Claude Cowork (team plan)](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CLAUDE_COWORK.md)** — org Owner adds the connector under Organization Settings → Connectors.\
- **[Perplexity Computer](https://github.com/garrytan/gbrain/blob/master/docs/mcp/PERPLEXITY.md)** — `gbrain connect https://your-host/mcp --agent perplexity --oauth --register` mints a least-privilege OAuth client and prints the Issuer/Client ID/Secret to paste into Settings → Connectors (OAuth is the right path for a cloud connector; a bearer token also works for local use). Pro subscription required.\
- **[ChatGPT](https://github.com/garrytan/gbrain/blob/master/docs/mcp/CHATGPT.md)** — uses OAuth 2.1 with PKCE (the hard requirement). Register a `chatgpt` client from the admin dashboard with grant type `authorization_code`.\
\
For the HTTP server itself:\
\
```\
gbrain serve              # stdio MCP (local subprocess; for Claude Code, Cursor, Windsurf)\
gbrain serve --http       # HTTP MCP with OAuth 2.1 + admin dashboard at /admin\
                          # (required for Claude Desktop, Cowork, Perplexity, ChatGPT)\
```\
\
The HTTP server includes DCR-style client registration, scope-gated access (`read` / `write` / `admin`), and rate limiting. Deployment guides (ngrok, Railway, Fly.io) live under [`docs/mcp/`](https://github.com/garrytan/gbrain/blob/master/docs/mcp).\
\
## Two ways to query your brain\
\
[Permalink: Two ways to query your brain](https://github.com/garrytan/gbrain#two-ways-to-query-your-brain)\
\
Raw retrieval (what most personal-knowledge tools ship) and a synthesis layer that gives you an actual answer. They serve different jobs.\
\
```\
# raw retrieval: top pages by hybrid score, fast, no LLM cost\
gbrain search "who's working on AI agents at portfolio companies?"\
\
# brain layer: synthesized answer with citations and gap analysis\
gbrain think "who's working on AI agents at portfolio companies?"\
```\
\
**`gbrain search`** returns the top retrieved pages, ranked by hybrid scoring (vector + keyword + RRF + source-tier boost + reranker). Use it when you want raw material to skim: agent context windows, citation lookups, finding a specific quote.\
\
**`gbrain think`** runs the same retrieval, then composes a synthesized answer across the results with explicit citations to the source pages AND an honest note on what the brain doesn't know yet. The gap analysis is the differentiator: the answer tells you when a page is stale, when a claim is uncited, when two pages contradict each other, when there's a hole you should fill.\
\
**Why it compounds.** Pair the brain layer with `find_trajectory` and you get answers like _"how have the company's metrics changed AND what does the team look like right now AND what did they promise / share AND when did we last meet AND what's the value-add I can offer here"_: well-scored, well-cited, in one shot. That's the strategic moat. That's why building a 100K-page brain is worth the effort.\
\
`gbrain agent run "..."` exposes the same surface to a sub-agent through the Minions queue, with crash-safe two-phase persistence. Same answers, durable.\
\
## How to get data in\
\
[Permalink: How to get data in](https://github.com/garrytan/gbrain#how-to-get-data-in)\
\
One command, local or hosted, synchronous receipt:\
\
```\
gbrain capture "the thought I want to remember"\
gbrain capture --file ./notes/today.md\
echo "from a pipe" | gbrain capture --stdin\
SLUG=$(gbrain capture "..." --quiet)\
```\
\
The page lands in the database and on disk in one move. Default slug `inbox/YYYY-MM-DD-<hash8>` so captures cluster in a predictable triage location. On thin-client installs the verb routes through MCP to the server: same command, same UX.\
\
For webhook ingestion (Zapier / IFTTT / Apple Shortcuts):\
\
```\
curl -X POST https://your-brain/ingest \\
  -H "Authorization: Bearer $TOKEN" \\
  -H "Content-Type: text/markdown" \\
  -d "# a thought from a Shortcut"\
```\
\
For mobile capture, the inbox folder source picks up anything dropped into\
`~/.gbrain/inbox/` from iOS Shortcuts / AirDrop / Drafts / Finder.\
\
Third-party skillpacks can ship custom ingestion sources (Granola, Linear,\
voice, OCR) against the versioned `IngestionSource` contract at\
`gbrain/ingestion`. See [`docs/skillpack-anatomy.md`](https://github.com/garrytan/gbrain/blob/master/docs/skillpack-anatomy.md).\
\
## Your brain's shape (schema packs)\
\
[Permalink: Your brain's shape (schema packs)](https://github.com/garrytan/gbrain#your-brains-shape-schema-packs)\
\
Most personal-knowledge tools force one fixed layout: their idea of "notes" + "people" + "tags." Drop a Notion export or your own years-old Obsidian vault on top, and the agent doesn't know what a `Projects/` folder means or whether `Reading/` is people or sources.\
\
**gbrain doesn't have a fixed layout.** It ships with bundled schema packs and lets you author your own when none fit:\
\
- **`gbrain-base-v2`** (default as of v0.41.22) — 15-type DRY/MECE canonical taxonomy (14 canonical + `note` catch-all): `person`, `company`, `media`, `tweet`, `social-digest`, `analysis`, `atom`, `concept`, `source`, `deal`, `email`, `slack`, `writing`, `project`, `note`. Subtypes/format/origin pushed to frontmatter. The taxonomy that responds to issue #1479.\
- **`gbrain-base`** (legacy, v0.41 and earlier brains) — the original 24-type layout. Stays bundled for back-compat; brains on it can upgrade via `gbrain onboard --check --explain` → `gbrain jobs submit unify-types --allow-protected --params '{"target_pack":"gbrain-base-v2","apply":true}'` (omit `"apply":true` for a dry-run preview — that is the default).\
- **`gbrain-recommended`** — extends `gbrain-base` with the 13 additional directories from `docs/GBRAIN_RECOMMENDED_SCHEMA.md` (source, place, trip, conversation, personal, civic, project, etc.). Activate with `gbrain schema use gbrain-recommended`.\
- **Your own pack** — `gbrain schema detect` clusters your actual filesystem into proposed types, `gbrain schema suggest` runs an LLM pass over them, and `gbrain schema review-candidates --apply` promotes the ones you like. Three commands and the brain knows your shape. Authoring a successor pack (declares `migration_from:` so existing brains can opt in): see [`docs/architecture/pack-upgrade-mechanism.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/pack-upgrade-mechanism.md).\
\
```\
gbrain schema active                # which pack is running, which tier set it\
gbrain schema list                  # bundled + installed packs\
gbrain schema detect                # propose types matching your filesystem\
gbrain schema suggest               # LLM-refined proposals on top of detect\
gbrain schema review-candidates     # human gate: promote / rename / ignore\
gbrain schema use my-pack           # activate\
```\
\
The active pack threads through every read + write path: `parseMarkdown` infers page type from the pack's path prefixes; `whoknows` scopes expert routing to types declared `expert_routing: true`; `extract_facts` runs only on `extractable: true` types; the search cache folds the pack name + version into its key so cross-pack contamination is structurally impossible. Switch packs and the brain re-interprets itself; switch back and nothing's lost.\
\
Seven-tier resolution chain (per-call flag → env var → per-source DB key → brain-wide DB key → `gbrain.yml` → `~/.gbrain/config.json` → `gbrain-base` default). Full reference + authoring guide: [`docs/architecture/schema-packs.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/schema-packs.md).\
\
## Tutorials\
\
[Permalink: Tutorials](https://github.com/garrytan/gbrain#tutorials)\
\
Step-by-step walkthroughs for getting the most out of GBrain. Each one takes you from zero to a working outcome, with concrete commands and real numbers.\
\
- [**Set up your personal AI agent + brain from zero**](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/personal-brain.md) — the canonical full-stack install. Two GitHub repos, a Telegram bot, AlphaClaw on Render, OpenClaw + GBrain + Supabase. End-to-end in about 2 hours.\
- [**Set up GBrain as your company brain**](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/company-brain.md) — federated, multi-user, OAuth-scoped institutional memory for a 10-50 person team. About 90 minutes end-to-end.\
- [**Auto-improve a skill with `gbrain skillopt`**](https://github.com/garrytan/gbrain/blob/master/docs/tutorials/improving-skills-with-skillopt.md) — treat a `SKILL.md` as a trainable parameter. Generate a starter benchmark straight from the skill with `--bootstrap-from-skill` (or write your own), strengthen the judges, then watch the optimizer propose edits and keep only the ones that measurably score higher. ~20 minutes, ~$1 in API calls. Flag + cost + safety reference: [`docs/guides/skillopt.md`](https://github.com/garrytan/gbrain/blob/master/docs/guides/skillopt.md).\
\
More walkthroughs in progress: connecting an existing agent (Claude Code, Cursor, OpenClaw, Hermes) to a GBrain memory layer; setting up GBrain for VC dealflow with founder scorecards and meeting prep; migrating an existing Notion or Obsidian vault; indexing a codebase as a queryable code brain. Full tutorial index: [`docs/tutorials/`](https://github.com/garrytan/gbrain/blob/master/docs/tutorials).\
\
Want to see a tutorial that isn't here yet? [Open an issue](https://github.com/garrytan/gbrain/issues) describing the workflow you want documented.\
\
## What it does (the loop)\
\
[Permalink: What it does (the loop)](https://github.com/garrytan/gbrain#what-it-does-the-loop)\
\
```\
  signal   →   search   →   respond   →   write   →   auto-link   →   sync\
  (every    (brain-first  (informed     (page +    (typed edges     (cron\
  message)  retrieval)    by context)   timeline)  + backlinks)     keeps fresh)\
```\
\
- **Signal detector** runs on every message your agent receives. Captures ideas, entity mentions, time-sensitive todos, names, links.\
- **Brain-first lookup** before any external API call. The cheapest, fastest, most personal information source you have.\
- **Auto-link** fires on every page write. No LLM calls; pure pattern matching on `[[wiki/people/bob]]` style references. New entity → new page stub → graph grows.\
- **Cron-driven enrichment** runs while you sleep: dedup people pages, fix citations, score salience, find contradictions, prep tomorrow's tasks.\
\
The whole loop is described in [`docs/architecture/topologies.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/topologies.md) with diagrams.\
\
## Capabilities\
\
[Permalink: Capabilities](https://github.com/garrytan/gbrain#capabilities)\
\
**Hybrid search.** Vector (HNSW on pgvector) + BM25 keyword + reciprocal-rank fusion + source-tier boost + intent-aware query rewriting. Three named search modes (`conservative`, `balanced`, `tokenmax`) bundle the cost/quality knobs into a single config key. Live cost/recall comparisons in [`docs/eval/SEARCH_MODE_METHODOLOGY.md`](https://github.com/garrytan/gbrain/blob/master/docs/eval/SEARCH_MODE_METHODOLOGY.md). Default: `balanced` with ZeroEntropy reranker on. Per-query graph signals notice when a top result is a hub for THAT query (adjacency boost), is corroborated across team brains (cross-source boost), or is being crowded out by weak chunks from a chatty session (session demote). Run `gbrain search "<query>" --explain` to see per-stage attribution: base score, every boost that fired, what it multiplied. `gbrain doctor` ships a `graph_signals_coverage` check; `gbrain search stats` shows fire counts and failure breakdowns. Vector retrieval pools the best chunk per page, so a page surfaces on its strongest evidence instead of losing to a neighbor on one weak chunk. Queries that match a page's title phrase or a declared free-text alias (`gbrain reindex --aliases` backfills existing pages) get boosted to the page they name. Every result carries an `evidence` tag (why it matched) and a `create_safety` hint (`exists` / `probable` / `unknown`) so an agent decides whether a page already exists instead of guessing from a raw score. `gbrain search diagnose "<query>" --target <slug>` traces which retrieval layer surfaces (or misses) a page.\
\
**Self-wiring knowledge graph.** Every `put_page` extracts entity refs from markdown/wikilinks/typed-link syntax and writes edges with zero LLM calls. Typed edges (`attended`, `works_at`, `invested_in`, `founded`, `advises`, `mentions`, …). Multi-hop traversal via `gbrain graph-query`. The graph is what produces the +31.4 P@5 lift over vector-only RAG. **Obsidian-style vaults:** bare `[[note-name]]` wikilinks that point across folders — you wrote `[[struktura]]` but the page lives at `projects/struktura.md` — resolve by basename once you opt in with `gbrain config set link_resolution.global_basename true`. Off by default; `gbrain doctor` tells you how many edges you'd gain before you flip it. See [migrating an Obsidian vault](https://github.com/garrytan/gbrain/blob/master/INSTALL_FOR_AGENTS.md#step-45-wire-the-knowledge-graph).\
\
**Job queue (Minions).** BullMQ-shaped, Postgres-native job queue. Durable subagents (LLM tool loops that survive crashes via two-phase pending→done persistence), shell jobs with audit, child jobs with cascading timeouts, rate leases for outbound providers, attachments via S3/Supabase storage. Replaces "spawn subagent as fire-and-forget Promise" with something that recovers from anything.\
\
**Non-English brains (FTS language config).** The Postgres full-text search tokenizer is configurable via `GBRAIN_FTS_LANGUAGE`. Defaults to `english`. Set it to any text-search configuration that exists in your Postgres instance:\
\
```\
export GBRAIN_FTS_LANGUAGE=portuguese     # uses built-in portuguese stemmer\
export GBRAIN_FTS_LANGUAGE=spanish        # built-in spanish stemmer\
export GBRAIN_FTS_LANGUAGE=pt_br          # custom config (e.g. unaccent + portuguese)\
```\
\
List available configs: `psql -c "SELECT cfgname FROM pg_ts_config"`. Both the **query side** (`websearch_to_tsquery`) and the **write side** (the trigger functions that populate `pages.search_vector` and `content_chunks.search_vector`) honor `GBRAIN_FTS_LANGUAGE`. On first install (or upgrade), the `configurable_fts_language` schema migration reads the env var and creates trigger functions in the configured language; subsequent inserts/updates tokenize using that setting. To change language on a brain that has already run the migration, use the dedicated CLI command:\
\
```\
export GBRAIN_FTS_LANGUAGE=portuguese\
gbrain reindex-search-vector --dry-run    # preview row counts\
gbrain reindex-search-vector --yes        # recreate triggers + backfill\
```\
\
The command is idempotent (re-running with the same language is a no-op for vector content) and uses the same recreate-and-backfill primitives as the migration. For accent-insensitive Portuguese (`pt_br`), see [docs/guides/multi-language-fts.md](https://github.com/garrytan/gbrain/blob/master/docs/guides/multi-language-fts.md) for the `unaccent` \+ portuguese stemmer recipe.\
\
**43 curated skills.** Routing lives in [`skills/RESOLVER.md`](https://github.com/garrytan/gbrain/blob/master/skills/RESOLVER.md). Covers signal capture, ingest (idea / media / meeting), enrichment, querying, brain ops, citation fixing, daily task management, cron scheduling, reports, voice, soul audit, skill creation, eval framework, and migrations. Skills are markdown files (tool-agnostic), packaged as a single skillpack the installer drops into your agent workspace.\
\
**Eval framework.**`gbrain eval longmemeval` runs the public [LongMemEval](https://huggingface.co/datasets/xiaowu0162/longmemeval) benchmark against your hybrid retrieval. `gbrain eval export` \+ `gbrain eval replay` capture real queries and replay them against code changes (set `GBRAIN_CONTRIBUTOR_MODE=1`). `gbrain eval cross-modal` cross-checks an output against the task using three different-provider frontier models. `gbrain eval retrieval-quality` runs NamedThingBench, which hard-gates the named-thing retrieval families (title-substring, alias-synonym, generic-to-named, multi-chunk-dilution) so a regression in "find the page this query names" fails CI loudly. Full methodology in [`docs/eval/SEARCH_MODE_METHODOLOGY.md`](https://github.com/garrytan/gbrain/blob/master/docs/eval/SEARCH_MODE_METHODOLOGY.md).\
\
**Brain consistency.**`gbrain eval suspected-contradictions` samples retrieval pairs, layered date pre-filter, query-conditioned LLM judge, persistent cache. Surfaces conflicts between takes + facts the agent has written. Wired into the daily dream cycle.\
\
**Agent-authored schema (v0.40.7.0).** Your brain has a shape — what page types exist (`person`, `meeting`, `paper`, `case`, `lab-result`), what they link to (`attended`, `authored`, `prescribed-by`), what facts get extracted automatically. The default ships with 22 universal types, but your brain's actual shape is not the default shape. Agents can now evolve that shape on your behalf via 14 `gbrain schema` CLI verbs + a batched MCP op (`schema_apply_mutations`, admin scope, NOT localOnly so remote agents reach it over HTTPS). Atomic file locks, audit log with the agent's identity, chunked UPDATE backfill in 1000-row batches that never wedge concurrent writers. The brain stops being a pile of notes and becomes something with structure. **Why it matters:** [`docs/what-schemas-unlock.md`](https://github.com/garrytan/gbrain/blob/master/docs/what-schemas-unlock.md) — 7 killer use cases (4000 invisible meetings, founder ops brain, research brain, legal brain, team brain, agent-as-co-curator). **5-minute walkthrough:** [`docs/schema-author-tutorial.md`](https://github.com/garrytan/gbrain/blob/master/docs/schema-author-tutorial.md). **Agent skill:** [`skills/schema-author/SKILL.md`](https://github.com/garrytan/gbrain/blob/master/skills/schema-author/SKILL.md).\
\
## Integrations\
\
[Permalink: Integrations](https://github.com/garrytan/gbrain#integrations)\
\
Data flowing into the brain. Each integration is a recipe — markdown + setup hints — that ships in `recipes/` and is discoverable via `gbrain integrations list`.\
\
- **Voice**: Phone calls create brain pages via Twilio + OpenAI Realtime (or DIY STT+LLM+TTS). Setup recipe: [`recipes/twilio-voice-brain.md`](https://github.com/garrytan/gbrain/blob/master/recipes/twilio-voice-brain.md).\
- **Email + calendar**: webhook handlers that route to brain signals. [`docs/integrations/meeting-webhooks.md`](https://github.com/garrytan/gbrain/blob/master/docs/integrations/meeting-webhooks.md).\
- **Embedding providers**: 16 recipes covering OpenAI (default fallback), OpenRouter, Voyage, ZeroEntropy (default), Google Gemini, Azure OpenAI, MiniMax, Alibaba DashScope, Zhipu, Ollama (local), llama.cpp llama-server (local), LiteLLM proxy. Pricing matrix + decision tree in [`docs/integrations/embedding-providers.md`](https://github.com/garrytan/gbrain/blob/master/docs/integrations/embedding-providers.md).\
- **Rerankers**: ZeroEntropy `zerank-2` hosted (default in `tokenmax` mode) plus the v0.40.6.1 `llama-server-reranker` recipe for fully-local cross-encoder rerank via llama.cpp — runs Qwen3-Reranker or self-hosted ZeroEntropy weights against the same `gateway.rerank()` seam. Setup walkthrough in [`docs/ai-providers/llama-server-reranker.md`](https://github.com/garrytan/gbrain/blob/master/docs/ai-providers/llama-server-reranker.md).\
- **Credential gateway**: vault-aware secret distribution. [`docs/integrations/credential-gateway.md`](https://github.com/garrytan/gbrain/blob/master/docs/integrations/credential-gateway.md).\
- **MCP clients**: every major MCP client is supported. [`docs/mcp/`](https://github.com/garrytan/gbrain/blob/master/docs/mcp) per-client setup.\
\
## Architecture\
\
[Permalink: Architecture](https://github.com/garrytan/gbrain#architecture)\
\
**Two engines, one contract.** PGLite (Postgres 17 via WASM, zero-config, default) for personal brains up to ~50K pages. Postgres + pgvector (Supabase or self-hosted) for shared / large / multi-machine deployments. The contract-first `BrainEngine` interface in [`src/core/engine.ts`](https://github.com/garrytan/gbrain/blob/master/src/core/engine.ts) defines ~47 operations both engines implement; CLI and MCP server are generated from one source.\
\
**Brain repo is the system of record.** Your knowledge lives in a regular git repo (your "brain repo") as markdown files. GBrain syncs the repo into Postgres for retrieval; deletes in git become soft-deletes in DB. You can publish public subsets, share team mounts, run thin-client setups pointing at a colleague's brain server. Topologies in [`docs/architecture/topologies.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/topologies.md).\
\
**Two organizational axes (brain ⊥ source).** A _brain_ is a database (your personal brain, a team mount you joined). A _source_ is a repo inside that brain (wiki, gstack, an essay, a knowledge base). Routing lives in `.gbrain-source` dotfiles and resolves via a documented 6-tier precedence chain. Full diagrams in [`docs/architecture/brains-and-sources.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/brains-and-sources.md).\
\
**Why the graph matters.** Vector search returns chunks that are semantically close. The graph returns chunks that are factually connected. Hybrid search pulls from both; auto-linking on every write keeps the graph fresh. Deep dive: [`docs/architecture/RETRIEVAL.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/RETRIEVAL.md).\
\
## Troubleshooting\
\
[Permalink: Troubleshooting](https://github.com/garrytan/gbrain#troubleshooting)\
\
**`gbrain init --pglite` crashes on macOS 26.x (Tahoe)?** PGLite's embedded WASM engine is incompatible with macOS 26.x on Apple Silicon. The fix is to use native Homebrew PostgreSQL + pgvector instead. Full step-by-step setup in [`docs/INSTALL.md` — Troubleshooting: PGLite crashes on macOS 26.x](https://github.com/garrytan/gbrain/blob/master/docs/INSTALL.md#pglite-crashes-on-macos-26x-tahoe).\
\
**`gbrain import` fails with `expected N dimensions, not M`?** Run `gbrain doctor`. It will print the exact `gbrain config set ...` or `gbrain retrieval-upgrade` command to repair the mismatch. You should not need to delete `~/.gbrain`. Fresh `gbrain init --pglite` auto-detects your embedding provider from API keys in your environment: set `OPENAI_API_KEY` (or `ZEROENTROPY_API_KEY` / `VOYAGE_API_KEY`) before running init, or pass `--embedding-model <provider>:<model>` explicitly. With multiple keys set, init fires an interactive picker. In non-TTY contexts (CI, Docker) with no keys, init exits 1 with a paste-ready setup hint; pass `--no-embedding` to defer setup until runtime. See [`docs/integrations/embedding-providers.md`](https://github.com/garrytan/gbrain/blob/master/docs/integrations/embedding-providers.md) for the full provider matrix and [`docs/operations/headless-install.md`](https://github.com/garrytan/gbrain/blob/master/docs/operations/headless-install.md) for Docker/CI sequencing.\
\
**Hourly cron sync keeps timing out on a federated brain?** v0.41.13.0 ships\
two flags + a recommended pattern. Switch your cron to a per-source loop\
with shell `timeout(1)` doing the OS-level kill and gbrain self-terminating\
gracefully half-a-minute earlier:\
\
```\
gbrain sync --break-lock --all --max-age 1800\
for src in $(gbrain sources list --json | jq -r '.[].id'); do\
  timeout 600 gbrain sync --source "$src" --timeout 540 || true\
done\
```\
\
When `--timeout` fires mid-import, `gbrain sync` exits 0 with status\
`partial` and `last_commit` UNCHANGED — the next run re-walks the same\
diff and `content_hash` short-circuits already-imported files. The\
`--max-age 1800` first command self-heals any wedged-but-alive locks\
left by a hung previous run, using the v98 `last_refreshed_at` semantic\
(NOT `acquired_at`) so healthy long-running holders are safe by\
construction. See the v0.41.13.0 entry in [`CHANGELOG.md`](https://github.com/garrytan/gbrain/blob/master/CHANGELOG.md)\
for the honest scope notes (extract + embed phases run to completion;\
30-min rollout window for `--max-age` post-migration v98; full-sync\
triggers deferred to v0.42+).\
\
**Dream cycle silently losing wiki links on Supabase?** v0.41.19.0 fixes\
the bug class structurally. The engine now self-retries every bulk batch\
write (`addLinksBatch` / `addTimelineEntriesBatch` / `upsertChunks`) on\
Supavisor pooler blips, with a 12s worst-case wait that covers the full\
5-10s circuit-breaker recovery window. `gbrain doctor` surfaces incidents\
via the new `batch_retry_health` check (reads the last 24h of\
`~/.gbrain/audit/batch-retry-YYYY-Www.jsonl`). To tune for an unusually\
slow pooler:\
\
```\
# Defaults: 3 retries, base 1s, max 10s, decorrelated jitter.\
# Override per operator without a release:\
export GBRAIN_BULK_MAX_RETRIES=5       # int >= 0; 0 disables retries\
export GBRAIN_BULK_RETRY_BASE_MS=2000  # int > 0\
export GBRAIN_BULK_RETRY_MAX_MS=15000  # int >= base\
```\
\
Bad values surface at `gbrain doctor` startup with a paste-ready fix\
(not at first-retry mid-cycle). PGLite-only installs pay zero cost — the\
retry wrap is engine-level, but PGLite has no pooler so retries never\
fire in practice.\
\
**Dream cycle losing ~150 link rows per run with `'No database connection: connect() has not been called'` errors in the log?** v0.41.27.0\
makes the retry layer self-heal on a nulled-out database singleton. A\
new `reconnect` callback on `withRetry` rebuilds the connection between\
attempts; `PostgresEngine.batchRetry` injects `() => this.reconnect()`\
so engine-level batch writes survive a mid-cycle disconnect by something\
else in the same process. Same release: `gbrain capture` no longer trails\
a `'No database connection'` stderr line from a background facts:absorb\
worker firing after CLI exit — the op-dispatch finally block awaits\
`getFactsQueue().drainPending({timeout: 1000})` before\
`engine.disconnect()`. To find which code path is still calling\
disconnect mid-process, run `gbrain doctor --json | jq '.checks[] | select(.id=="batch_retry_health")'`; the extended check now surfaces\
24h disconnect-call count and the most-recent caller frame from a new\
`~/.gbrain/audit/db-disconnect-YYYY-Www.jsonl` audit. (Closes #1570.)\
\
**`gbrain brainstorm` returning `judge_failed: true` with 0 scored**\
**ideas?** v0.41.21.0 closes the two bugs that caused it. The judge\
hard-coded a 4K-token output cap; for any run past ~40 ideas the call\
truncated mid-JSON and the parser threw. Same release closes a slash-\
form pricing miss: `gbrain brainstorm --judge-model anthropic/claude-sonnet-4-6 --max-cost 5` failed with\
`BudgetExhausted reason=no_pricing` because every pricing site only\
matched the colon form. Both shapes work now. No config change, no\
schema migration — `gbrain upgrade` is the whole fix.\
\
**`gbrain reindex --markdown` wiped your auto/dream/signal-detector**\
**tags?** v0.41.37.0 makes tag reconciliation add-only. Re-import and\
`reindex --markdown` now ADD current frontmatter tags and never delete,\
so enrichment tags written to the DB (auto-tag, dream synthesize,\
signal-detector) survive a re-chunk. The reindex DB-only fallback also\
reconstructs the full markdown (frontmatter + body + timeline) before\
re-chunking, so a page with no on-disk source keeps its frontmatter,\
title, and timeline instead of getting overwritten with empty\
frontmatter. Trade-off: removing a tag from a page's frontmatter no\
longer removes it from the DB on the next sync (frontmatter-tag removal\
needs a provenance column, deferred). (Closes #1621.)\
\
**`gbrain sync` wedges on a large brain (no progress, high CPU)?**\
v0.41.37.0 ships three things. First, name the stalling file:\
\
```\
GBRAIN_SYNC_TRACE=1 gbrain sync --no-pull --no-embed --yes\
```\
\
The last `[sync] begin import: <path>` line with no following completion\
is the file being processed when the hang hit. Second, if you suspect a\
schema-pack `inference.regex` with catastrophic backtracking, complete\
the sync with the pack disabled and re-run extraction later:\
\
```\
gbrain sync --no-schema-pack --no-pull --no-embed --yes\
```\
\
`gbrain schema lint` now warns on the classic nested-quantifier ReDoS\
shapes (`(a+)+`, `(a*)*`, …) in pack regexes, and the runtime caps\
inference-regex input length (override via `GBRAIN_MAX_REGEX_INPUT_CHARS`).\
Third, on a PGLite brain, stop `gbrain serve` before a large sync —\
PGLite is single-writer and a live MCP server contends for the write\
lock. See [`docs/architecture/serve-sync-concurrency.md`](https://github.com/garrytan/gbrain/blob/master/docs/architecture/serve-sync-concurrency.md)\
for the full triage. (Closes #1569.)\
\
**`gbrain init --migrate-only` / a schema migration fails on Windows**\
**with `getaddrinfo ENOTFOUND`?** v0.41.37.0 runs the 9 schema-bring-up\
phases in-process instead of spawning a child `gbrain init --migrate-only` per phase. The spawned child died on\
Windows + bun + Supabase pooler with a DNS-resolution failure even\
though the parent connected fine; running in-process removes the spawn\
entirely. The v0.13.1 grandfather migration that hung 70+ minutes on an\
82K-page PGLite brain is also fixed — it now runs as a chunked bulk SQL\
pass (keyed on the page PK, soft-delete-filtered, source-safe) that\
completes in ~1-2 seconds. (Closes #1605, #1581.)\
\
## Docs\
\
[Permalink: Docs](https://github.com/garrytan/gbrain#docs)\
\
- [`docs/INSTALL.md`](https://github.com/garrytan/gbrain/blob/master/docs/INSTALL.md) — every install path, end to end\
- [`docs/what-schemas-unlock.md`](https://github.com/garrytan/gbrain/blob/master/docs/what-schemas-unlock.md) — why schemas matter: 7 killer use cases, the structural argument for typed page kinds, the agent-co-curates pattern (v0.40.7.0)\
- [`docs/schema-author-tutorial.md`](https://github.com/garrytan/gbrain/blob/master/docs/schema-author-tutorial.md) — 5-minute walkthrough: fork the bundled pack, add a custom type, backfill existing pages, prove the wiring via `gbrain whoknows`\
- [`docs/architecture/`](https://github.com/garrytan/gbrain/blob/master/docs/architecture) — system design, topologies, retrieval theory\
- [`docs/guides/`](https://github.com/garrytan/gbrain/blob/master/docs/guides) — how-to runbooks (sub-agent routing, minion deployment, skill development, brain-first lookup, idea capture, diligence ingestion)\
- [`docs/integrations/`](https://github.com/garrytan/gbrain/blob/master/docs/integrations) — connecting external data sources (voice, email, calendar, embedding providers)\
- [`docs/mcp/`](https://github.com/garrytan/gbrain/blob/master/docs/mcp) — per-client MCP setup (Claude Desktop, Code, Cursor, ChatGPT, Perplexity, Cowork)\
- [`docs/eval/`](https://github.com/garrytan/gbrain/blob/master/docs/eval) — eval framework, metric glossary, methodology\
- [`docs/ethos/`](https://github.com/garrytan/gbrain/blob/master/docs/ethos) — philosophy (thin harness, fat skills, markdown as recipes, origin story)\
- [`AGENTS.md`](https://github.com/garrytan/gbrain/blob/master/AGENTS.md) — entry point for non-Claude agents\
- [`CLAUDE.md`](https://github.com/garrytan/gbrain/blob/master/CLAUDE.md) — entry point for Claude Code (deep operating context)\
- [`CONTRIBUTING.md`](https://github.com/garrytan/gbrain/blob/master/CONTRIBUTING.md) — contributor guide, test discipline, eval-capture mode\
- [`SECURITY.md`](https://github.com/garrytan/gbrain/blob/master/SECURITY.md) — OAuth threat model, hardening defaults\
\
## Contributing\
\
[Permalink: Contributing](https://github.com/garrytan/gbrain#contributing)\
\
Run `bun run test` for the fast loop, `bun run verify` for the pre-push gate, `bun run ci:local` to run the full Docker-backed CI stack locally. Detailed test discipline in [`CONTRIBUTING.md`](https://github.com/garrytan/gbrain/blob/master/CONTRIBUTING.md).\
\
Community PRs are batched into release waves rather than merged one-by-one — see the "PR wave workflow" section in [`CLAUDE.md`](https://github.com/garrytan/gbrain/blob/master/CLAUDE.md). Contributor attribution stays attached via `Co-Authored-By:` trailers. We credit every accepted contribution in [`CHANGELOG.md`](https://github.com/garrytan/gbrain/blob/master/CHANGELOG.md).\
\
If you find a bug or want a feature: open an issue first. Quick fixes (typo, doc bug, obvious regression) can go straight to a PR. Anything touching schema, retrieval ranking, MCP protocol, or the security boundary needs a design discussion in the issue first.\
\
## License + credit\
\
[Permalink: License + credit](https://github.com/garrytan/gbrain#license--credit)\
\
MIT. I built GBrain to run my OpenClaw and Hermes deployments — the production brain behind my AI agents.\
\
Origin story: [`docs/ethos/ORIGIN.md`](https://github.com/garrytan/gbrain/blob/master/docs/ethos/ORIGIN.md).\
\
Community PR contributors are credited in `CHANGELOG.md` per release. ZeroEntropy ( [@zeroentropy](https://zeroentropy.dev/)) for the embedding + reranker stack that ships as the default. Voyage AI for the asymmetric-encoding recipe template. Ramp Labs for the search quality improvements lineage.\
\
## About\
\
Garry's Opinionated OpenClaw/Hermes Agent Brain\
\
### Resources\
\
[Readme](https://github.com/garrytan/gbrain#readme-ov-file)\
\
[MIT license](https://github.com/garrytan/gbrain#MIT-1-ov-file)\
\
### Contributing\
\
[Contributing](https://github.com/garrytan/gbrain#contributing-ov-file)\
\
### Security policy\
\
[Security policy](https://github.com/garrytan/gbrain#security-ov-file)\
\
[Activity](https://github.com/garrytan/gbrain/activity)\
\
### Stars\
\
**27.8k** stars\
\
### Watchers\
\
**129** watching\
\
### Forks\
\
[**4.1k** forks](https://github.com/garrytan/gbrain/forks)\
\
[Report repository](https://github.com/contact/report-content?content_url=https%3A%2F%2Fgithub.com%2Fgarrytan%2Fgbrain&report=garrytan+%28user%29)\
\
## Releases\
\
## Packages\
\
## Contributors\
\
## Languages\
\
You can’t perform that action at this time.