On-demand reference. CLAUDE.md (the always-loaded orientation file) routes here via its Reference map. Read a file's entry before editing that file.
Entries describe CURRENT behavior + load-bearing invariants only. Release history
lives in CHANGELOG.md + git log / git blame, NOT here. Do not append
per-release **vX.Y.Z:** narration — CI enforces this
(scripts/check-key-files-current-state.sh).
src/core/operations.ts— Contract-first operation definitions (the foundation). Exports upload validatorsvalidateUploadPath,validatePageSlug,validateFilename, plusmatchesSlugAllowList(slug, prefixes)(glob matcher:<prefix>/*matches recursive children; bare<prefix>matches exact only).OperationContext.remoteis a REQUIRED field flagging untrusted callers;OperationContext.allowedSlugPrefixesis the trusted-workspace allow-list set by the dream cycle;OperationContext.auth?: AuthInfois threaded through HTTP dispatch for scope enforcement inserve-http.tsbefore the op runs.put_pageenforces: whenviaSubagentandallowedSlugPrefixesis set, slug must match the allow-list; else the legacywiki/agents/<id>/...namespace check applies. Auto-link skipped only whenremote=true && !trustedWorkspace. EveryOperationcarriesscope?: 'read' | 'write' | 'admin'+localOnly?: boolean;sync_brain,file_upload,file_list,file_urlareadmin + localOnly(rejected over HTTP). Four trust-boundary call sites (put_pageallowlist, file_upload trust-narrowing, submit_job protected-name guard, auto-link skip) use FAIL-CLOSED semantics:ctx.remote === falsefor trusted-only sites,ctx.remote !== falsefor "untrust unless explicit-false" — anything not strictlyfalseis treated as remote (closes the HTTP MCP shell-job RCE where a read+write OAuth token could submitshelljobs).sourceScopeOpts(ctx)encodes the source-scoped read precedence ladder — federated array (ctx.auth.allowedSources) wins over scalar (ctx.sourceId/ctx.auth.sourceId) over nothing; every read-side op handler routes through it so a source-bound OAuth client can't see neighboring sources viasearch/query/list_pages/get_page/find_experts/query's image path.put_page's inline disk write-through is the sharedwritePageThroughhelper (src/core/write-through.ts), ATOMIC via temp-sibling + rename so a crash or concurrentgbrain synccan't read a half-written.md; same helper backsgbrain brainstorm/lsd --save. Link provenance surface (#1941):add_link(gbrain link/link-add) +remove_link(gbrain unlink/link-rm) exposelink_source/link_type;add_linkrejects the reconciliation-managed built-ins viaMANAGED_LINK_SOURCES(markdown/frontmatter/mentions/wikilink-resolved) and defaults omitted provenance to'manual'(the engine's own default stays'markdown'for internal callers);list_link_sources(gbrain link-sources, read) lists provenances viasourceScopeOpts. CLI aliases register throughcliHints.aliases(collision-guarded insrc/cli.ts).src/core/engine.ts— Pluggable engine interface (BrainEngine).clampSearchLimit(limit, default, cap)takes an explicit cap so per-operation caps can be tighter thanMAX_SEARCH_LIMIT. ExportsLinkBatchInput/TimelineBatchInputfor the bulk-insert API (addLinksBatch/addTimelineEntriesBatch).readonly kind: 'postgres' | 'pglite'discriminator letssrc/core/migrate.tsand others branch withoutinstanceof+ dynamic imports. Methods:batchLoadEmotionalInputs(slugs?)(CTE-shaped read with per-table aggregates so page × N tags × M takes never produces N×M rows),setEmotionalWeightBatch(rows)(UPDATE FROM unnest($1::text[],$2::text[],$3::real[])composite-keyed on(slug, source_id)),getRecentSalience(opts),findAnomalies(opts).PageFiltershassort?: 'updated_desc'|'updated_asc'|'created_desc'|'slug'+PAGE_SORT_SQLwhitelist consumed by both engines.listAllPageRefs(): Promise<Array<{slug, source_id}>>ordered by(source_id, slug)— cheap cross-source enumeration replacing thegetAllSlugs()→getPage(slug)N+1 (which silently defaulted tosource_id='default'); parity across postgres-engine.ts + pglite-engine.ts; Pinned bytest/e2e/multi-source-bug-class.test.ts.SearchOpts+PageFiltersaddsourceIds?: string[](federated read axis; both engines applyWHERE source_id = ANY($N::text[])when set, preserve scalarsourceIdfast path when unset);traverseGraph(slug, depth, opts?)andtraversePaths(slug, opts?)acceptopts.sourceId/opts.sourceIds.traverseGraphopts hasfrontierCap?: number(per-iteration recursive-CTE cap, approx per-BFS-layer); return typePromise<GraphNode[]>for MCP wire stability; exportTraverseGraphOpts; Postgres uses parenthesizedLIMIT N ORDER BY (slug, id)inside the recursive term, PGLite mirrors with positional params; Pinned bytest/regressions/v0_36_frontier_cap.test.ts. Phantom-redirect methods:refreshPageBody(slug, sourceId, compiled_truth, timeline, content_hash)narrow-UPDATEs three columns + updated_at, skipping soft-deleted rows (content_hash refresh required sogbrain syncsees the canonical as unchanged after fence merge);migrateFactsToCanonical(phantomSlug, canonicalSlug, sourceId)UPDATEsentity_slug+source_markdown_slugon every active fact row keyed on the phantom, preserving embedding/validUntil/kind/status/source_session/confidence; parity attest/phantom-redirect-engine-parity.test.ts.getAdjacencyBoosts(pageIds): Promise<Map<number, AdjacencyRow>>powers the per-query graph-signals stage — one SQL query returning inbound-link counts among top-K plus a cross-source count (links from differingsource_id);COALESCE(p.source_id,'default')null safety,HAVING >= 1, cross-source CASE-WHEN excludes the target's own source; parity SQL across both engines;SearchResultgains optionalbase_score,backlink_boost,salience_boost,recency_boost,exact_match_boost,graph_adjacency_boost,graph_cross_source_boost,session_demote_factor,reranker_delta+ internal staging fields; Pinned bytest/e2e/graph-signals-engine.test.ts. Two REQUIRED methods:deletePages(slugs, {sourceId}): Promise<string[]>(single-batch primitive returning slugs actually deleted) andresolveSlugsByPaths(paths, {sourceId}): Promise<Map<path,slug>>(batch path→slug lookup);sourceIdREQUIRED on both at the type level (asymmetric with single-rowdeletePagewhich keeps optional/'default'); both short-circuit on empty input and throw when> DELETE_BATCH_SIZE. Embedding-signature stale-detection quartet:countStaleChunks(opts?)gains optionalsignature?: stringwidening the stale predicate fromembedding IS NULLto ALSO include chunks whose JOINed pageembedding_signature IS NOT NULL AND <> $signature(NULL signature is GRANDFATHERED, never counted; omitsignaturefor the legacy NULL-only count);sumStaleChunkChars(opts?: {sourceId?, signature?}): Promise<number>=SUM(LENGTH(chunk_text))over stale chunks (same predicate + embed_skip filter + optional sourceId scope), used bygbrain sync --allcost preview viaestimateCostFromChars;setPageEmbeddingSignature(slug, {sourceId?, signature})stampspages.embedding_signatureafter a page's chunks (re)embed, idempotent no-op when page absent;invalidateStaleSignatureEmbeddings({signature, sourceId?}): Promise<number>NULLsembedding+embedded_aton every chunk whose page signature is set AND differs, returning the count, called BEFORElistStaleChunksso signature-drift pages flow through the NULL-embedding keyset cursor unchanged (NULL never invalidated). WidensfindOrphanPages(opts?: {sourceId?, sourceIds?})(candidate-side scoping only; inbound links counted from any source). Pinned bytest/sum-stale-chunk-chars.test.ts,test/embedding-signature-stale.test.ts,test/e2e/engine-parity.test.ts. Free-text alias layer:resolveAliases(aliasNorms, opts?): Promise<Map<string, Array<{slug, source_id}>>>(READ; maps each normalized alias to declaring(slug, source_id)pairs, source-scoped) andsetPageAliases(slug, sourceId, aliasNorms)(WRITE; replaces the full alias set, delete-then-insert, empty clears, idempotent on the unique triple), called by theimportFromContentingest projection and thereindex --aliasesbackfill; parity across both engines, Pinned bytest/search/page-aliases-engine.test.ts.searchVectorin both engines injects the sharedbuildBestPerPagePoolCteper-page max-pool so a page surfaces on its strongest chunk.executeRawDirect(sql, params?, opts?)is the lock-hot-path sibling ofexecuteRaw: same single-statement contract, but routes to the direct session-mode pool when dual-pool is active (Postgres/Supabase port 5432) so a long-held lock heartbeat survives the transaction pooler's per-transaction connection recycling; PGLite delegates straight toexecuteRaw(no pooler). Both engines implement it; the Minion lock path (claim/renewLock) is the consumer.src/core/engine-constants.ts— single source of truth for engine batch-sizing constants. ExportsDELETE_BATCH_SIZE = 500consumed by both engines'deletePages+resolveSlugsByPathsand by the sync delete + rename loops. Lives outsideengine.ts(the interface module) to avoid circular-import worry — bounded per-statement work for predictable lock hold time + write amplification.src/core/background-work.ts(#1762/#1745/#1775) — process background-work registry: the single owner of "drain every fire-and-forget DB-write sink before the CLI disconnects."registerBackgroundWorkDrainer({name, order, drain(timeoutMs), abort?})+drainAllBackgroundWorkForCliExit({timeoutMs})over aMap<name, BackgroundWorkDrainer>(idempotent registration by name;__registerDrainerForTestreturns an unregister handle). Drains in explicit(order, name)order — facts FIRST (order 0) so its abort-path DBlogIngestruns against the freshest live engine — and AWAITSabort()only whendrain()reportsunfinished>0. Best-effort per drainer: one sink's failure never blocks the others or the disconnect. FOUR sinks register at module import (rule-of-four):facts/queue.ts(order 0;abort=shutdown()cancels a hung facts:absorb Haiku via internalAbort),last-retrieved.ts(order 1),search/hybrid.ts(order 2;awaitPendingSearchCacheWritesbounded viaPromise.race),eval-capture.ts(order 3;captureEvalCandidateself-tracks its promise viaawaitPendingEvalCaptures). Both CLI-exit paths (src/cli.tsop-dispatch finally +handleCliOnlyfinally) call it beforeengine.disconnect()— closing the PGLite busy-loop wheredb.close()raced an in-flight job and pinned the single-writer lock (#1762). CLI-EXIT-ONLY: the factsshutdown()abort is permanent process state, never call in a long-livedgbrain serve. Companion changes:src/core/ai/gateway.tswithDefaultTimeout(caller, ms)bounds every outbound AI call (chat 300s, embed+multimodal 60s; envGBRAIN_AI_{CHAT,EMBED,MULTIMODAL}_TIMEOUT_MS; composed with caller signals viaAbortSignal.any) and the op-dispatch + handleCliOnly force-exit timersprocess.exit(process.exitCode ?? 0)so a hung disconnect can't mask an errored op as success;src/core/postgres-engine.tsreconnect()module-mode branch re-establishes via idempotentdb.connect()+connectionManager.setReadPoolrefresh instead ofdb.disconnect()(no null window for concurrent ops; fail-loud on real connect failure — #1745);src/core/search/hybrid.tsembedQueryBounded+ a sharedQueryEmbedDeadline(6s, floored 2s per embed viaMIN_QUERY_EMBED_BUDGET_MS; envGBRAIN_QUERY_EMBED_TIMEOUT_MS) bounds the cache-lookup AND inner query embeds so a stalled provider falls back to keyword instead of hanging past the 10s force-exit (#1775). Incorporates + hardens PR #1763 (@ElliotDrel). Pinned bytest/core/background-work.test.ts,test/search/query-embed-deadline.test.ts,test/eval-capture-drain.test.ts,test/e2e/postgres-reconnect-singleton.test.ts,test/e2e/pglite-cli-exit.serial.test.ts,test/fix-wave-structural.test.ts.src/core/search/graph-signals.ts— per-query graph-signals helper.applyGraphSignals(results, engine, opts)runs as the 4th post-fusion stage (after backlink/salience/recency). Three boosts:ADJACENCY_BOOST=1.05(page linked from 2+ OTHER top-K results — local hub for THIS query),CROSS_SOURCE_BOOST=1.10(page linked from 2+ DIFFERENT sources — corroborated across team brains, dormant in single-source brains),SESSION_DEMOTE=0.95(3+ results from same chat session — keep the highest-scoring at full score, demote the rest). All three inherit the floor-ratio gate preventing weak pages from being boosted past strong ones via popularity.computeScoreDistribution(results)emits min/p25/p50/p75/p95/max +reorder_band_width.sessionPrefix(slug)extracts the chat-session anchor (chat/2026-05-15-...). PurepairedBootstrapPValue(deltas, resamples, rng)exported for eval gates. Test seam viaadjacencyFnDI. Fail-open: any error logs vialogGraphSignalsFailure(JSONL audit viaaudit-writer) and returns the input array unchanged. Pinned bytest/search/graph-signals.test.ts(incl. the IRON-RULE floor-gate regression).src/core/search/hybrid.tsextension —runPostFusionStageshas a 4th stage (graphSignalsEnabled,onGraphMeta,onScoreDistribution).base_scorestamped at function entry idempotently (captured ONCE before any boost stage mutatesscore). Each post-fusion stage stamps its multiplier:applyBacklinkBoost→backlink_boost,applySalienceBoost→salience_boost,applyRecencyBoost→recency_boost.applyReranker(earlier in the pipeline) stampsreranker_deltaas a rank delta (positive = improved).applyExactMatchBoostinsrc/core/search/intent-weights.tsstampsexact_match_boostwhen fired. Per-stage attribution powersgbrain search --explain— every boost surface carries its own field soformatResultsExplainreads them all without coupling to internal stage ordering.src/core/search/explain-formatter.ts— rendersSearchResult[]as a multi-line per-result breakdown forgbrain search --explain. Reads every boost-stamping field. Handles the "no boosts applied" empty path. 4-decimal precision with trailing-zero strip. Pinned bytest/search/explain-formatter.test.ts.src/core/search/mode.tsextension —graph_signals: booleanknob inModeBundle(defaults:conservative=false,balanced=true,tokenmax=true).KNOBS_HASH_VERSIONappends ags=parts entry per the cache-key contamination convention so a graph-on cache write can't be served to a graph-off lookup.SearchKeyOverrides+SearchPerCallOpts+loadOverridesFromConfig+SEARCH_MODE_CONFIG_KEYS+resolveSearchMode+attributeKnoball carry the field. Opt-out:gbrain config set search.graph_signals false. Mid-deployquery_cacherows from before the upgrade hash differently — natural row segregation, clears withincache.ttl_seconds(3600s default).src/core/audit/audit-writer.ts— shared JSONL audit primitive consolidating the hand-rolled audit modules. ExportscreateAuditWriter({kind, recordSchema})returning{log, readRecent}plus shared helperscomputeIsoWeekFilename(kind, now?)andresolveAuditDir()(honorsGBRAIN_AUDIT_DIR). ISO-week file rotation; best-effort writes (stderr warn on failure, never throws); read-path scans current-week + previous-week files for boundary spans. Refactored onto it for parity:src/core/rerank-audit.ts,src/core/audit-slug-fallback.ts,src/core/minions/handlers/shell-audit.ts,src/core/minions/handlers/supervisor-audit.ts,src/core/facts/phantom-audit.ts(each module's public API preserved bit-for-bit). Thegraph-signals-failuresaudit (logGraphSignalsFailure) uses the same primitive. One hand-rolled audit remains atsrc/core/skillpack/audit.ts. Pinned bytest/audit/audit-writer.test.ts.src/core/cli-options.tsextension —CliOptionsgainsexplain: boolean.parseGlobalFlagsrecognizes--explainanywhere in argv (stripped before command dispatch).src/cli.tsformatResultforsearch+querycases routes toformatResultsExplainfromsrc/core/search/explain-formatter.tswhenCliOptions.explainis set; falls through to the existing JSON / human formatters otherwise.src/commands/search.ts:gbrain search statsextension —graph_signalssection (enabled/source/failures_count/failures_by_reason). JSON envelope adds agraph_signalssibling property;_meta.metric_glossaryaddsgraph_signals.enabled+graph_signals.failures_by_reason. Human output prints the section after the existing block. Readssearch.graph_signalsconfig first, falls back to the mode default. Pinned bytest/search/search-stats-graph-signals.test.ts.src/commands/doctor.tsextension —graph_signals_coveragecheck wired into bothrunDoctor(local) anddoctorReportRemote(HTTP/JSON thin-client path). Readssearch.graph_signalsconfig first, falls back to mode default; silentokwhen disabled. Computes inbound link coverage on the page set; warns at <10% withgbrain extract allfix hint;okat ≥30% ("fire on most queries") and 10-29% ("fire occasionally"), each with the percentage embedded. Pinned by cases intest/doctor.test.ts.src/core/engine-factory.ts— Engine factory with dynamic imports ('pglite'|'postgres').src/core/pglite-engine.ts— PGLite (embedded Postgres 17.5 via WASM) implementation, all BrainEngine methods.listLinkSources({sourceId?, sourceIds?})returns distinctlink_sourceprovenances + counts (ORDER BY count DESC, link_source ASC NULLS LAST; scalar + federated scoped; parity with postgres-engine.ts) poweringgbrain link-sources.addLinksBatch/addTimelineEntriesBatch/addTakesBatchpass the whole batch as one JSONB document viajsonb_to_recordset(($1::jsonb)->'rows')(bound throughexecuteRawJsonbwith a{ rows }wrapper; rows built by the sharedsrc/core/batch-rows.tshelpers, NUL-stripped), and arebatchRetry-wrapped.connect()wrapsPGlite.create()in a try/catch that emits an actionable error (macOS 26.3 WASM bug #223, points atgbrain doctor); the lock is released on failure so the next process can retry cleanly.searchKeyword/searchKeywordChunksmultiplyts_rankby the source-factor CASE at chunk grain;searchVectoris a two-stage CTE — inner CTE keepsORDER BY cc.embedding <=> vecso HNSW stays usable, outer SELECT re-ranks byraw_score * source_factor, inner LIMIT scales with offset to preserve pagination.initSchema()callsapplyForwardReferenceBootstrap()BEFORE replaying SCHEMA_SQL — probes for forward-referenced state the embedded blob needs (pages.source_id,links.link_source,links.origin_page_id,content_chunks.symbol_name,content_chunks.language,sourcesFK target, plusfiles.source_id,files.page_id,oauth_clients.source_id,oauth_clients.federated_read,sources.archived,sources.archived_at,sources.archive_expires_at) and adds only what's missing; threads the DDL connection frominitSchemaso probes run inside the advisory-lock scope; no-op on fresh installs and modern brains (closes the upgrade-wedge bug class #239/#243/#266/#357/#366/#374/#375/#378/#395/#396/#1018/#974/#820).getBrainScorereturns 100/100 with full breakdown (35/25/15/15/10) whenpageCount === 0(vacuous truth — empty brain has no coverage problem); Pinned bytest/brain-score-breakdown.test.tsempty-brain assertion +test/doctor-report-remote.serial.test.ts.disconnect()uses snapshot+early-null (snapshot_db/_lock, null instance fields BEFORE anyawaitso a concurrentconnect()can't see a partial mid-close state) wrapped in try/finally guaranteeing lock-release even ifdb.close()throws; KEEPS close-then-release order (release-then-close was rejected: it would widen the window where a sibling process connects to a still-closing brain); Pinned bytest/pglite-engine-disconnect.serial.test.ts. ExportsclassifyPgliteInitError(message): 'bunfs' | 'macos-26-3' | 'unknown'+buildPgliteInitErrorMessage(verdict, original)routing the catch-block hint by failure shape (bunfsmatches literal$$bunfsORENOENT[\s\S]*pglite\.dataco-occurrence, surfaces a paste-readybun upgrade+ Node fallback;macos-26-3keeps the #223 link;unknownfalls through); Pinned bytest/pglite-init-classifier.test.ts. ImplementsdeletePages(slugs, {sourceId})+resolveSlugsByPaths(paths, {sourceId})viaslug = ANY($1::text[])array-param binding, caller-chunking primitive throwing when input exceedsDELETE_BATCH_SIZE,deletePagesreturnsRETURNING slugrows so callers filterpagesAffectedto confirmed deletes. Implements the embedding-signature stale-detection quartet —sumStaleChunkChars({sourceId?, signature?}),setPageEmbeddingSignature(slug, {sourceId?, signature}),invalidateStaleSignatureEmbeddings({signature, sourceId?}), widenedcountStaleChunks({sourceId?, signature?})(thesignatureopt widens viaJOIN pages p ... WHERE cc.embedding IS NULL OR (p.embedding_signature IS NOT NULL AND p.embedding_signature <> $signature), NULL grandfathered); parity SQL with postgres-engine.ts. PGLite-specific DDL (pgvector, pg_trgm, triggers).src/core/postgres-engine.ts— Postgres + pgvector implementation (Supabase / self-hosted).addLinksBatch/addTimelineEntriesBatch/addTakesBatchpass the batch as one JSONB document —INSERT ... SELECT FROM jsonb_to_recordset(($1::jsonb)->'rows') AS v(...) JOIN pages ...bound throughexecuteRawJsonb({ rows })— which encodes arbitrary free text safely (the oldunnest(${arr}::text[])array-literal path crashed Postgres with "malformed array literal" on calendar/Zoom context, gbrain#1861) and sidesteps the 65535-parameter cap; takes declares native recordset column types (page_id int, weight real, active boolean, …) so no per-element casts; all three arebatchRetry-wrapped.searchKeyword/searchVectorscopestatement_timeoutviasql.begin+SET LOCALso the GUC dies with the transaction instead of leaking across the pooled postgres.js connection.getEmbeddingsByChunkIdsusestryParseEmbeddingso one corrupt row skips+warns instead of killing the query.searchKeyword/searchKeywordChunks/searchVectorapply source-aware ranking by inlining the source-factor CASE andNOT (col LIKE …)hard-exclude fromsrc/core/search/sql-ranking.ts;searchVectoris a two-stage CTE (HNSW-safe inner ORDER BY, source-boost re-rank in outer SELECT) carryingp.source_idinner→outer._savedConfigretains the connect config;reconnect()tears down + recreates the pool from saved config (called by supervisor watchdog after 3 consecutive health-check failures, and bybatchRetryon a retryable connection error). Concurrent callers share one in-flight_reconnectPromise(they await the single reconnect rather than racing a half-rebuilt pool); ownership re-samples through the atomicdb.connect()token on the connect leg.reconnect(ctx?)accepts the triggering error and records a pool-recovery audit event (reap_detected/reconnect_other/reconnect_succeeded/reconnect_failed) for thepool_reap_healthdoctor check.executeRawis a single-statement passthrough — no per-call retry (unsound for non-idempotent statements; recovery is supervisor-driven).connect()appliesresolveSessionTimeouts()fromdb.tsas connection-time startup parameters (statement_timeout,idle_in_transaction_session_timeout) so orphan pgbouncer backends can't hold locks for hours.countStaleChunks()+listStaleChunks()server-side-filter onembedding IS NULLforembed --stale(eliminates ~76 MB/call client-side pull);upsertChunks()resets bothembeddingANDembedded_atto NULL when chunk_text changes without a new embedding.initSchema()callsapplyForwardReferenceBootstrap()BEFORE replaying SCHEMA_SQL on the same probe set as PGLite (extended for column-only forward-reference cases:files.source_id,files.page_id,oauth_clients.source_id,oauth_clients.federated_read,sources.archived/archived_at/archive_expires_at); the entire probe path runs on the DDL connection threaded frominitSchema(closing a concurrent-bootstrap race for Supabase pooler users); closes #1018/#974/#820.disconnect()is idempotent —_connectionStyletracks whether the engine owns its pool (worker engines) or shares the module-level singleton; second call on an instance-pool engine is a no-op rather than clobbering the singleton; and a module-style engine only callsdb.disconnect()when it owns the singleton (_ownsModuleSingleton, set from thedb.connect()creation token), so a borrower probe engine's teardown leaves the cycle owner's connection intact. Pinned bytest/e2e/postgres-engine-disconnect-idempotency.test.ts+test/postgres-engine-singleton-ownership.test.ts.getBrainScoreempty-brain parity with PGLite — 100/100 with breakdown 35/25/15/15/10 whenpageCount === 0(both engines must agree to keepdoctor-report-remote.serial.test.tsdeterministic). ImplementsdeletePages(slugs, {sourceId}): Promise<string[]>viaDELETE FROM pages WHERE slug = ANY($1::text[]) AND source_id = $2 RETURNING slug(single round-trip; caller chunks);resolveSlugsByPathsdoesSELECT slug, source_path FROM pages WHERE source_path = ANY($1::text[]) AND source_id = $2; FK cascades throughcontent_chunks/links/tags/raw_data/timeline_entries/page_versions,files.page_id+links.origin_page_idgo SET NULL; throws when input exceedsDELETE_BATCH_SIZE(fromsrc/core/engine-constants.ts); both short-circuit on empty input. Implements the embedding-signature stale-detection quartet (sumStaleChunkChars,setPageEmbeddingSignature,invalidateStaleSignatureEmbeddings, widenedcountStaleChunks, all accept optionalsignatureextending "stale" to model/dims-swap drift via thepages.embedding_signatureJOIN, NULL grandfathered; theembedding IS NULLserver-side filter is preserved as the no-signature fast path); Pinned bytest/e2e/engine-parity.test.ts.src/core/cjk.ts— Single source of truth for CJK detection. ExportsCJK_RANGES_REGEX,CJK_SLUG_CHARS(character-class fragment for embedding inside other regexes),CJK_SENTENCE_DELIMITERS(。!?),CJK_CLAUSE_DELIMITERS(;:,、),CJK_DENSITY_THRESHOLD = 0.30,hasCJK(s),countCJKAwareWords(s)(30% density threshold — English docs with one Japanese term stay whitespace-tokenized; Chinese-dominant docs get char-counted), andescapeLikePattern(s)(escapes%,_,\\forILIKE ... ESCAPE '\\'). BMP-only ranges (Han / Hiragana / Katakana / Hangul Syllables). Consumers:expansion.ts,sync.ts:slugifySegment,operations.ts:validatePageSlug + validateFilename,chunkers/recursive.ts:countWords + DELIMITERS,pglite-engine.ts:searchKeyword + searchKeywordChunks.src/core/audit-slug-fallback.ts— Weekly ISO-week-rotated audit JSONL at~/.gbrain/audit/slug-fallback-YYYY-Www.jsonl.logSlugFallback(slug, sourcePath)fires whenimportFromFilefalls back to a frontmatter slug becauseslugifyPathreturned empty (emoji / Thai / Arabic / non-CJK exotic-script filenames).readRecentSlugFallbacks(days)reads the last N days forgbrain doctor'sslug_fallback_auditcheck. HonorsGBRAIN_AUDIT_DIRvia the sharedresolveAuditDir(). Separate surface fromsync-failures.jsonl— that file carries bookmark-gating semantics that info events shouldn't trigger.src/core/embedding-pricing.ts—EMBEDDING_PRICINGmap keyedprovider:modelfor the post-upgrade reindex cost estimate. Sibling toanthropic-pricing.ts. Entries: OpenAI text-embedding-3-large ($0.13/1M), 3-small ($0.02/1M), ada-002 ($0.10/1M), Voyage 3-large ($0.18/1M), 3 ($0.06/1M).lookupEmbeddingPrice(modelString)returns a tagged union (knownwith price +unknownwith provider name);estimateCostFromChars(charCount, pricePerMTok)uses 3.5 chars/token. Unknown providers degrade to "estimate unavailable" instead of fabricating numbers.src/core/post-upgrade-reembed.ts— Pure functions backing thegbrain upgradechunker-bump cost prompt.computeReembedEstimate(engine, model)queries real SQL (COUNT(*)+COALESCE(SUM(LENGTH(compiled_truth)) + SUM(LENGTH(timeline)), 0)) onpages WHERE chunker_version < MARKDOWN_CHUNKER_VERSION.formatReembedPrompt(est, graceSeconds)is the stderr-line formatter.runPostUpgradeReembedPrompt(engine, model, opts)orchestrates the 10-second Ctrl-C window; TTY-only wait (non-TTY auto-proceeds for CI / cron);GBRAIN_NO_REEMBED=1bails with a doctor-warning marker;GBRAIN_REEMBED_GRACE_SECONDS=0skips the wait.src/commands/reindex.ts—gbrain reindex --markdown [--limit N] [--dry-run] [--json] [--no-embed] [--repo PATH]. Walkspages WHERE page_kind = 'markdown' AND chunker_version < MARKDOWN_CHUNKER_VERSIONin 100-row batches ordered by id. Rows with non-nullsource_pathre-import viaimportFromFile; rows without fall back toimportFromContent. Both paths passforceRechunk: trueto bypassimportFromContent'scontent_hashshort-circuit — without it the chunker version bump never reaches pages whose source content hasn't changed, AND the stripFactsFence privacy strip never applies to pre-strip chunks. Idempotent — partial-completion re-runs pick up via id-ordered batches. Wired intosrc/commands/upgrade.ts:runPostUpgradeafterapply-migrations. The DB-only fallback (no source file on disk) does NOT pass body-onlycompiled_truthtoimportFromContent(that path re-parses with EMPTY frontmatter and OVERWRITES the page's real frontmatter/title/timeline); itgetPage+getTags, reconstructs FULL markdown viaserializeMarkdown(frontmatter, compiled_truth, timeline, {type, title, tags}), and re-imports THAT so re-chunking a DB-only page preserves everything while bumpingchunker_version. Pinned bytest/reindex-preserve-tags.test.ts.src/commands/reindex-code.ts—gbrain reindex --code [--source ID] [--dry-run] [--yes] [--json] [--force] [--no-embed]. Walkspages WHERE type = 'code'in 100-row batches, replays throughimportCodeFilefor chunk + embed + content_hash folding. Idempotent unless--forcebypasses the content_hash early-return. Cost-preview model field readsgetEmbeddingModelName()from the gateway so preview reflects what the gateway will actually embed with. An informational stderr nudge insiderunReindexCode(so dry-run + execute both surface it): when the configured embedding model isn't code-tuned (allowlist{'voyage-code-3'}, case-insensitive bare match), prints a recommendation to switch tovoyage:voyage-code-3; suppress withGBRAIN_NO_CODE_MODEL_NUDGE=1,--no-embed, or--json. PureshouldNudgeCodeModel(bareName)returns a taggedNudgeDecisionunion (takes the bare model name, emits qualifiedvoyage:voyage-code-3for the paste-readygbrain config setline). When--yesis absent and the caller is non-TTY or passed--json, the cost gate refuses (exit 2, no spend) via the pure exportedbuildCostRefusal({json, previewMsg, preview, costUsd, model}): {stdout?, stderr?}— JSON envelope only when--jsonis explicit, otherwise a human refusal on stderr (the spend guardrail is independent of the output format). Pinned bytest/ai/voyage-code-3-recipe.test.ts,test/reindex-code-nudge.serial.test.ts,test/reindex-code-model-source.serial.test.ts(IRON-RULE regression for the cost-preview fix),test/reindex-cost-refusal.test.ts.src/commands/sync.ts:resolveSlugByPathOrSourcePath— Resolves a slug bypages.source_pathfirst (returns the stored slug for frontmatter-fallback pages whose path doesn't derive a slug), then falls back toresolveSlugForPath(path). Threaded into all 4 delete/rename call sites (performSync's un-syncable cleanup at ~:531, deletes at ~:603, rename oldSlug at ~:622). Without this, emoji-only / Thai / Arabic filenames whose slug came from frontmatter would orphan on delete/rename (the delete path would compute the wrong path-derived slug). Best-effort query — pre-migration brains fall through to the legacy path.src/core/sources-ops.ts— Multi-source registration + clone-lifecycle ops (addSource,recloneIfMissing,defaultCloneDir,isOwnedClone,unownedHint). Reclone-ownership invariant (must-never-violate): gbrain may only delete/re-clone a clone it created, NEVER a user working tree.recloneIfMissingdeleteslocal_path, so it gates onisOwnedClone(src)and throws aSourceOpError('unmanaged_path', ...)BEFORE any filesystem op when ownership is unprovable — fail-closed. Ownership is proven byconfig.managed_clone === true(written byaddSource's--urlpath, covering default-location and--clone-dirclones) ORlocal_path === defaultCloneDir(id)(back-compat for pre-marker clones, via exact normalized-path equality, symlink-free). A row withremote_url+ an unownedlocal_path(a user-registered working tree, e.g.sources add --path) is refused untouched; re-add with--urlto regain auto-reclone. The reclone is EXDEV-safe: clone into a SIBLING temp oflocal_path(not the sharedclones/.tmp, which may sit on a different mount than a--clone-dirtarget), then swap (move old aside → move new in → drop old) solocal_pathis never left missing-and-unrecoverable; on swap failure the original is restored, and if restore fails the error names theasidepath so it's never reflexively deleted. A TOCTOU re-check re-confirms ownership immediately before the destructive move and rejects a symlink leaf swapped in after the entry check (symlink_escape).unownedHint(src, state)is the shared recovery message used by both the core error and thegbrain sync --sourceCLI error;gbrain sources restorespecial-casesunmanaged_pathto print "DB row restored; gbrain syncs this path read-only" instead of the misleading "try sync to recover" guidance.SourceOpErrorCodeincludesunmanaged_path. Pinned bytest/sources-ops.test.ts,test/sources-resync-recovery.test.ts.src/core/utils.ts— Shared SQL utilities extracted from postgres-engine.ts. ExportsparseEmbedding(value)(throws on unknown input, used by migration + ingest paths where data integrity matters) andtryParseEmbedding(value)(returnsnull+ warns once per process, used by search/rescore paths where availability matters more than strictness).isUndefinedColumnError(err)predicate — pattern-matches Postgres SQLSTATE 42703 / "column ... does not exist" with engine-driver shape variation tolerated; replaces barecatch {}blocks inoauth-provider.tsso genuine errors (lock timeout, network blip, permission denied) propagate while column-missing falls through to the legacy fallback.validateSourceId(id)throws on anything outside^[a-z0-9_-]+$, used by the per-source disk-layout fix in patterns.ts/synthesize.ts before anyjoin(brainDir, '.sources', source_id, slug+'.md')so source_id can't traverse out of brainDir.rowToPagepopulates the requiredPage.source_idfrom the SELECT projection (scripts/check-source-id-projection.shenforces every projection feedingrowToPageincludes the column).src/core/db.ts— Connection management, schema initialization.resolveSessionTimeouts()returnsstatement_timeout+idle_in_transaction_session_timeout(defaults 5min each, env-overridable viaGBRAIN_STATEMENT_TIMEOUT/GBRAIN_IDLE_TX_TIMEOUT/GBRAIN_CLIENT_CHECK_INTERVAL). Bothconnect()(module singleton) andPostgresEngine.connect()(worker pool) consume the result via postgres.js'sconnectionoption, sending GUCs as startup parameters that survive PgBouncer transaction mode (setSessionDefaultskept as a back-compat no-op shim).connect()returnsPromise<boolean>—trueiff THIS call created the module singleton,falseif it joined an existing one; the decision is atomic (noawaitbetween theif (sql)null-check and the synchronoussql = postgres(...)assignment), so two concurrent module connects can't both claim creation.PostgresEnginestores the return as its_ownsModuleSingletontoken and only the creating engine maydb.disconnect()the singleton — a borrower probe engine (lint/doctor config-lift) no-ops its disconnect, so its teardown can't null the connection the long-lived cycle owner is still using (the dream-cycle "connect() has not been called" failure). The modulesqlis only ever nulled bydb.disconnect()(postgres.js auto-reconnects its own internal pool and never touches our reference).disconnect()snapshots + nullssqlbefore awaitings.end()so a concurrent connect can't join a pool that's already closing.src/commands/migrate-engine.ts— Bidirectional engine migration (gbrain migrate --to supabase/pglite).src/core/import-file.ts— importFromFile + importFromContent (chunk + embed + tags).importFromContentandimportCodeFilestamppages.embedding_signatureviasetPageEmbeddingSignature(slug, {sourceId, signature: currentEmbeddingSignature()})when the import actually embedded (not--no-embed) so a model/dims swap is detectable as stale;importCodeFileonly stamps when every chunk was freshly embedded this call (needsEmbedIndexes.length === chunks.length), mixed reuse-by-hash pages stay unstamped (reindex --code --force/embed --stalehandle those).importFromContent's tag reconciliation is ADD-ONLY: it onlyaddTag(idempotent, ON CONFLICT DO NOTHING). Thetagstable has no provenance column and frontmatter tags are stripped from storedpages.frontmatter(markdown.ts:118), so a frontmatter-origin tag can't be distinguished from a DB-enrichment tag (auto-tag / dream synthesize / signal-detector) at re-import — deletion is unsafe (would wipe enrichment undergbrain reindex --markdown). Accepted trade-off: removing a tag from frontmatter no longer removes it from the DB on next sync (needs atag_sourceprovenance column). Pinned bytest/reindex-preserve-tags.test.ts+test/import-file.test.ts.src/core/sync.ts— Pure sync functions (manifest parsing, filtering, slug conversion). ExportedpruneDir(name: string): booleanis the single source of truth for descent-time directory exclusion across walkers — blocksnode_modules(no leading dot, so naive walkers slipped through and inflated MISSING_OPEN counts via vendor packages), dot-prefix dirs,ops/, and*.rawsidecars;isSyncableapplies it per path segment, andwalkMarkdownFilesinsrc/commands/extract.ts+listTextFilesinsrc/core/cycle/transcript-discovery.tsconsult it BEFORE recursing to save the IO of walking thousands of vendor files (closes #923 + #202).manageGitignoreworktree discriminator matches the gitdir path segment (/modules/<name>= submodule,/worktrees/<name>= worktree, per Git's documented layout) so Conductor worktrees (first-class repos) get.gitignoremanagement for storage-tiering (closes #889). The sync-failure ledger (failure store, error classifier, the shared bookmark gate, and the doctor severity rule) lives insrc/core/sync-failure-ledger.ts;sync.tsre-exportsclassifyErrorCode,summarizeFailuresByCode,loadSyncFailures,unacknowledgedSyncFailures,acknowledgeSyncFailures,recordSyncFailures,decideSyncFailureSeverity,applySyncFailureGate, and theSyncFailuretype for backward-compatible imports — see its entry below.src/core/sync-failure-ledger.ts— the bounded auto-skip sync failure ledger (issue #1939; formerly inline "Bug 9" insync.ts). A LEAF module (imports only fs/path/crypto/config) sosync.tscan re-export it without a circular dependency. State lives in~/.gbrain/sync-failures.jsonl, one JSON object per line, keyed by(source_id, path)with a per-keyattemptscount and a 3-state machine:open(fresh/blocking) →acknowledged(human resolved viagbrain sync --skip-failed) orauto_skipped(chronic).classifyErrorCode(errorMsg)regex classifier with 12 codes (SLUG_MISMATCH,YAML_PARSE,YAML_DUPLICATE_KEY,MISSING_OPEN,MISSING_CLOSE,NESTED_QUOTES,EMPTY_FRONTMATTER,NULL_BYTES,INVALID_UTF8,STATEMENT_TIMEOUT,FILE_TOO_LARGE,SYMLINK_NOT_ALLOWED) plusUNKNOWN(also recognizesPAGE_JUNK_PATTERNfrom the content-sanity gate);summarizeFailuresByCode(failures)returns sorted[{code, count}];MISSING_OPEN/MISSING_CLOSE/EMPTY_FRONTMATTERregexes match themarkdown.tsvalidator strings,FILE_TOO_LARGEcoversimport-file.ts:199, 352, 401,SYMLINK_NOT_ALLOWEDcovers:347. All mutations run underwithLedgerLock(cross-process file lock) with an atomic rename write. The auto-skip threshold resolves viaresolveAutoSkipThreshold()fromGBRAIN_SYNC_AUTOSKIP_AFTER(defaultDEFAULT_AUTOSKIP_AFTER = 3;0disables the valve = pure fail-closed). Two pure decision functions are the unit-test surface:decideGateAction({fileFailures, sentinels, attemptsByPath, threshold, skipFailed})returnshard_block | block | advance | advance_then_autoskip(sentinels like<head>ALWAYS hard-block, even with--skip-failed, so a history rewrite can't auto-skip; any FRESH failure withattempts < thresholdblocks fail-closed; only when ALL failures are chronic does itadvance_then_autoskip), anddecideSyncFailureSeverity({entries, nowMs, failHours})returns thesync_failuresdoctor status (okwhen zero unresolved;failwhen ≥10 OPEN-blocking or the oldest OPEN failure has blocked the bookmark pastfailHours; otherwisewarn—auto_skipped-only rows stay WARN-visible regardless of count because the bookmark already advanced).applySyncFailureGate(input)is the one orchestrator BOTH sync paths (incremental + full/runImport) call: it records/clears ledger rows, runsdecideGateAction, then executes effects in the crash-safe order (advance the bookmark FIRST via the injectedadvance()callback, THEN auto-skip the chronic set) so a crash can never mark a file skipped while leaving sync wedged.isSkippablePathrejects<…>sentinels. Pinned bytest/sync-failure-ledger.serial.test.ts+test/sync-failures.test.ts.src/core/storage.ts— Pluggable storage interface (S3, Supabase Storage, local).src/core/storage-config.ts— Storage tiering:loadStorageConfigreadsgbrain.yml, normalizes deprecated keys (git_tracked/supabase_only) to canonical (db_tracked/db_only) with once-per-process deprecation warning, and runsnormalizeAndValidateStorageConfig(auto-fixes missing trailing/, throwsStorageConfigErroron tier overlap). Path-segment matcher:media/x/does NOT matchmedia/xerox/foo. Uses a dedicated parser for thegbrain.ymlshape rather than gray-matter (broken on delimiter-less YAML).src/core/disk-walk.ts—walkBrainRepo(repoPath)returnsMap<slug, {size, mtimeMs}>from one recursivereaddirSync. Skips dot-dirs,node_modules, non-.mdfiles. Used bygbrain storage statusto replace per-pageexistsSync + statSync(~400K syscalls on 200K-page brains → tens).src/core/git-head.ts— local git HEAD freshness probe forgbrain doctor.isSourceUnchangedSinceSync(localPath, lastCommit, opts?)returns true ifflocalPathis a git repo whose current HEAD matcheslastCommit; whenopts.requireCleanWorkingTreeis true also requires a clean working tree (mirrorsgbrain sync's force-walk gate atsync.ts:1075so doctor and sync agree on "is there work to do?").requireCleanWorkingTreeisboolean | 'ignore-untracked'— in'ignore-untracked'mode the clean probe runsgit status --porcelain --untracked-files=noso a quiet repo with stray untracked dirs (?? companies/,?? media/) is still "unchanged" (sync's incremental path keys off the commit diff and never imports untracked files);GitCleanProbegains anignoreUntracked?second arg. Two probe seams (_setGitHeadProbeForTests,_setGitCleanProbeForTests) keep unit tests R2-compliant (nomock.module). UsesexecFileSyncwith array args so shell metachars inlocal_pathcannot escape to a shell (the regression test runs realexecFileSyncagainst'/nonexistent/$(touch <sentinel>)/repo'and asserts the sentinel is never created). Fail-open on every error (missing path, not a git repo, git not installed, timeout, NULL inputs, dirty-probe errored → false) preserving the caller's prior time-based behavior. The chunker-version-match check lives in the caller (doctor.ts) because it depends on engine state (sources.chunker_versionvsCHUNKER_VERSIONfromsrc/core/chunkers/code.ts). Pinned bytest/core/git-head.test.ts(incl. the shell-injection regression guard).src/core/source-health.ts— per-source health metrics forgbrain sources status+ doctor'sfederation_health. Commit-relative staleness:newestCommitMs(localPath)= HEAD committer time viagit log -1 --format=%ct(fail-open null; NO working-tree mtime parsing — committed content only, robust against the porcelain-mtime bug farm); purelagFromContentMs(contentMs|null, lastSyncMs|null, nowMs)= remote/column comparator (null lastSync → null; negative wall-clock → skew passthrough;contentMs <= lastSync→ 0; else/null-content → wall-clock).computeAllSourceMetrics(engine, sources, {probeContent?}): LOCAL (probeContent:true,gbrain sources status) →isSourceUnchangedSinceSync(..., {requireCleanWorkingTree:'ignore-untracked'}) ? 0 : wall-clock(live commit-hash catches HEAD moving to an old-dated commit a timestamp compare would miss); REMOTE (default,federation_healthon the HTTP MCP path) →lagFromContentMs(row.newest_content_at, ...), NO git subprocess (trust boundary).commitTimeMs(localPath, sha)is thenewestCommitMssibling pinned to an arbitrary commit (committer time viagit show -s --format=%ct <sha>, fail-open null, execFileSync array args) — the resumable sync stampsnewest_content_atagainst its pinned target commit, not whatever HEAD raced to. Pinned bytest/source-health.test.ts.src/core/git-remote.ts— SSRF-hardened git invocations for remote-sourcecloneRepoandpullRepo. Exports two distinct flag constants becausegit's argv grammar treats them differently:GIT_SSRF_FLAGS(3-cconfig flags —protocol.allow=user,protocol.file.allow=never,http.allowRedirects=false) is global config, spread BEFORE the subcommand verb;GIT_SSRF_SUBCOMMAND_FLAGS = ['--no-recurse-submodules']is subcommand-scoped, spread AFTER the verb (a combined array would spread--no-recurse-submodulesbefore the verb where real git rejects it exit 129).cloneRepoargv:git <GIT_SSRF_FLAGS> clone <GIT_SSRF_SUBCOMMAND_FLAGS> --depth=1 [--branch X] -- <url> <dir>.pullRepoargv:git <GIT_SSRF_FLAGS> -C <dir> pull <GIT_SSRF_SUBCOMMAND_FLAGS> --ff-only. Pinned bytest/git-remote.test.tsposition-anchored regression guard (argv.indexOf('--no-recurse-submodules') > argv.indexOf(verb)).src/commands/storage.ts—gbrain storage status [--repo P] [--json]. Split into pure data (getStorageStatus) + JSON formatter + human formatter (ASCII-only) matching theorphans.tspattern.PageCountsByTierandDiskUsageByTierare distinct nominal types so swaps fail at compile time.gbrain.yml(brain repo root) — Optional storage tiering config. Top-levelstorage:section withdb_tracked:anddb_only:array-valued keys.gbrain syncauto-manages.gitignorefordb_onlypaths on successful sync (skips on dry-run, blocked-by-failures, submodule context, orGBRAIN_NO_GITIGNORE=1).gbrain export --restore-only [--repo P] [--type T] [--slug-prefix S]repopulates missingdb_onlyfiles from the database.src/core/supabase-admin.ts— Supabase admin API (project discovery, pgvector check).src/core/file-resolver.ts— File resolution with fallback chain (local -> .redirect.yaml -> .redirect -> .supabase).src/core/chunkers/— 3-tier chunking (recursive, semantic, LLM-guided).code.tsis a tree-sitter-based semantic chunker for 30 languages (plus SQL via DerekStride/tree-sitter-sql) with embedded-asset WASMs (src/assets/wasm/),@dqbd/tiktokencl100k_base tokenizer, small-sibling merging.CHUNKER_VERSIONis folded intoimportCodeFile'scontent_hashso chunker shape changes force clean re-chunks across releases.extractSymbolNamehas an inline SQL branch (extractSqlSymbolName) diving through DerekStride'sstatementwrapper into the inner DDL child (create_table/create_function/create_view/create_index/create_procedure/create_type/create_schema/create_database/create_trigger/alter_table/alter_view) and extracting the target identifier via thenamefield with identifier-shaped fallback; DML kinds (select/insert/update/delete/merge/with) deliberately return null so chunks emit unnamed (code-def is a DDL signal).normalizeSymbolTypehas parallel SQL branches mappingcreate_table → 'table',create_view → 'view', etc.src/commands/code-def.ts:DEF_TYPESis extended with'table' | 'view' | 'index' | 'procedure' | 'schema' | 'database' | 'trigger'so the new chunks surface ingbrain code-def <name>queries.src/core/errors.ts—StructuredAgentError+buildError+serializeError. Every agent-facing surface (code-def, code-refs, usage errors) uses this envelope; matches theCycleReport.PhaseResult.errorshape.src/assets/wasm/— 37 tree-sitter grammar WASMs + tree-sitter runtime. Committed to the repo sobun --compileembeds them deterministically viaimport path from ... with { type: 'file' }. The CI guardscripts/check-wasm-embedded.shfails the build if the compiled binary ever silently falls through to recursive chunks.tree-sitter-sql.wasm(DerekStride/tree-sitter-sql @ c2e1e08db1ea20dc23bdb8d228a81a8756e9c450, built with tree-sitter-cli@v0.26.3 --abi 14) adds SQL coverage at 11 MB — larger than peers because the grammar covers PostgreSQL + MySQL + SQLite + T-SQL basics (40 MB generated parser.c); the compiled binary grows ~6%.src/commands/code-def.ts+src/commands/code-refs.ts— symbol definition + references lookup. Querycontent_chunks.symbol_nameor chunk_text ILIKE withpage_kind='code'filter. Auto-JSON when stdout is not a TTY (gh-CLI convention). Bypass the standardsearchKeywordDISTINCT ON (slug)collapse so multiple call-sites from the same file surface. The JSON envelope (CLI + thecode_def/code_refsMCP ops) carriesstatus+readyfromsrc/core/code-graph-readiness.tsso acount:0result is distinguishable asnot_built(no code indexed) vsready(genuinely no match); human output prints a one-line hint when not ready.src/core/code-graph-readiness.ts— typed readiness signal shared by the four code-* surfaces (code-def/code-refs/code-callers/code-callees).resolveCodeReadiness(engine, {kind:'symbol'|'edge', count, sourceId?, allSources?})returns{status:'not_built'|'indexing'|'ready'|'unknown', ready, has_code, pending_edges}.count>0short-circuits toreadywith no query; on empty it runsEXISTSprobes againstcontent_chunksJOINpages(page_kind='code') — nopage_kindindex needed, and the pending probe rides the partialidx_content_chunks_edges_backfill.kind:'symbol'(code-def/refs) is 2-state + brain-wide because symbol metadata is set at chunk time;kind:'edge'(code-callers/callees) is 3-state + source-scoped, with the pending predicate mirroring the resolver (edges_backfilled_at IS NULL OR < EDGE_EXTRACTOR_VERSION_TSfromsrc/core/chunkers/symbol-resolver.ts) so a resolver-version bump never falsely reportsready. Probe scope matches each command's result-querydeleted_atposture (def/refs don't filterdeleted_at, so neither do the probes). Any DB error returnsstatus:'unknown'(fail-open; never breaks the command).readinessHint(r)renders the human one-liner. Wired intocode-def.ts/code-refs.ts(brain-wide),code-callers.ts/code-callees.ts(resolvedsourceId/allSources), and all fourcode_*MCP op handlers insrc/core/operations.ts. Pinned bytest/code-graph-readiness.test.ts+ readiness-envelope cases intest/e2e/code-intel-mcp-ops-pglite.test.ts.src/core/search/— Hybrid search: vector + keyword + RRF + multi-query expansion + dedup.searchKeyword/searchKeywordChunks/searchVectorapply source-aware ranking at the SQL layer (curated content likeoriginals/,concepts/,writing/outranks bulk content like<fork>/chat/,daily/,media/x/).searchVectoruses a two-stage CTE so source-boost re-ranking doesn't kill the HNSW index. Hard-exclude prefixes (test/,archive/,attachments/,.raw/by default) filter at retrieval, not post-rank. Both gates honordetail !== 'high'so temporal queries surface chat pages normally.src/core/search/intent.ts— Query intent classifier (entity/temporal/event/general → auto-selects detail level).src/core/search/eval.ts— Retrieval eval harness: P@k, R@k, MRR, nDCG@k metrics + runEval() orchestrator.src/core/search/source-boost.ts— Source-type boost map keyed by slug prefix.DEFAULT_SOURCE_BOOSTS(originals/ 1.5, concepts/ 1.3, writing/ 1.4, people/companies/deals/ 1.2, daily/ 0.8, media/x/ 0.7, /chat/ 0.5, archive/ 0.5, extracts/ 0.3) andDEFAULT_HARD_EXCLUDES(test/, attachments/, .raw/).archive/is DEMOTED (findable, ranked below curated), not hard-excluded — archive holds high-signal history users expect to retrieve; the demote is a prior at the SQL/fusion layer and the cross-encoder reranker can still promote a strongly-matching archive page.parseSourceBoostEnv/parseHardExcludesEnvparse comma-separatedprefix:factorpairs fromGBRAIN_SOURCE_BOOST/GBRAIN_SEARCH_EXCLUDE.resolveBoostMapandresolveHardExcludesmerge defaults + env + callerSearchOpts.exclude_slug_prefixes/include_slug_prefixes. The surviving exclude policy is auditable via thehidden_by_search_policydoctor check (src/commands/doctor.ts, local + remote paths) which counts chunked pages withheld per active exclude prefix, reusingresolveHardExcludes+buildVisibilityClause+ the exportedescapeLikePattern.src/core/search/sql-ranking.ts— Pure SQL string builders.buildSourceFactorCase(slugColumn, boostMap, detail)emits a CASE with longest-prefix-match wins (returns literal'1.0'whendetail === 'high'for temporal-bypass parity with COMPILED_TRUTH_BOOST).buildHardExcludeClause(slugColumn, prefixes)emitsNOT (col LIKE 'p1%' OR col LIKE 'p2%')— OR-chain wrapped in NOT, NOTNOT LIKE ALL/ANY(those quantifiers don't express set-exclusion). LIKE meta-character escape covers all three of%,_, AND\(backslash is Postgres LIKE's default escape char). Single-quote doubling on SQL string literals so injection-style inputs are inert text.buildBestPerPagePoolCte(...)is the shared per-page max-pool CTE both engines'searchVectorinject — instead of returning the single best chunk per page from an innerORDER BY embedding <=> vec LIMIT N(which let a page lose to a neighbor on ONE weak chunk while its strong chunk sat just below the inner cut), the CTE pools the BEST chunk score per(source_id, slug)composite key so a page surfaces on its strongest evidence; composite key (not bare slug) keeps multi-source brains correct; single source of truth so the two engines can't drift.src/core/search/title-match.ts— pure, zero-I/O title-phrase matcher shared by the production title boost AND NamedThingBench (no drift).isTitlePhraseMatch(query, title)returns true when the normalized query is a contiguous token run inside the title with>= MIN_CONTENT_TOKENS=2non-stopword tokens, OR an exact full-title match (covers deliberate 1-word chosen names like "Mingtang"). Token-boundary matching (never raw substring, so "art" doesn't match "Bartholomew"); small conservative English stopword set excluded from the content-token floor (guards against promoting generic pages on stopword-y queries); NFKC normalize so CJK / width variants converge. ExportstokenizeTitle+__test__internals.src/core/search/alias-normalize.ts— ONE normalizer shared by the WRITE path (ingest projects frontmatteraliases:intopage_aliases) and the READ path (search matches query againstpage_aliases), so stored aliases can't silently fail to match queries via divergent normalization (same single-source posture ascjk.ts).normalizeAlias(raw)does NFKC + lowercase + whitespace-collapse + trim + strip one layer of wrapping quotes/brackets; returns''for empty (callers MUST skip empty aliases).normalizeAliasList(value)coerces a frontmatter scalar / array / comma-list / garbage into a deduped list of normalized non-empty aliases — used by both the ingest projection and thereindex --aliasesbackfill.src/core/search/evidence.ts— the agent-facing why-it-matched contract (closes the root behavior where an agent read one blended score, decided "no strong match, safe to create", and wrote a duplicate over a fully-developed page).classifyEvidence(r)names the strongest signal (precedence:alias_hit>exact_title_match>high_vector_match(base ≥HIGH_MATCH_FLOOR=0.85) >keyword_exact(base ≥SOLID_MATCH_FLOOR=0.6) >weak_semantic).createSafetyFor(evidence)derives the don't-duplicate hint (exists/probable/unknown) the agent keys off INSTEAD of a raw threshold (a blended RRF/cosine score is not a calibrated probability).stampEvidence(results)stampsevidence+create_safetyin place once at pipeline end (after the alias hop, before slice); idempotent.src/core/search/mode.tsextension —title_boost: number | undefinedknob inModeBundle(default1.25for all three modes; multiplier for the post-fusion title-phrase boost). Override chain: per-callSearchOpts→search.title_boostconfig (clamped[1.0, 5.0]) → bundle.KNOBS_HASH_VERSIONappends atib=parts entry so a title-boost-on cache write can't be served to a title-boost-off lookup.SEARCH_MODE_CONFIG_KEYSgainssearch.title_boost.src/commands/search-diagnose.ts—gbrain search diagnose "<query>" --target <slug> [--json] [--source <id>]: Phase-0 retrieval diagnostic. Traces WHERE a target page surfaces (or fails to) across keyword / vector (per-page max-pool) / alias / hybrid layers and names the layer responsible for an incident, so an operator can pin whether the fix is max-pool/innerLimit (vector) vs title/alias. The verdict names the layer that DOES surface the target (or "none"). Pinned bytest/search/search-diagnose.test.ts.src/commands/reindex-aliases.ts—gbrain reindex --aliases [--limit N] [--dry-run] [--json] [--source <id>]: backfills the free-text alias layer for EXISTING pages whose frontmatteraliases:predate the alias table (the import-time projection covers new + changed pages). Reads each page's frontmatteraliases:, writes viaengine.setPageAliases. Idempotent + convergent (setPageAliases replaces a page's alias set) so no op-checkpoint needed; walkslistAllPageRefs(cheap cross-source enumeration),--sourcenarrows. Pinned bytest/search/reindex-aliases.test.ts.src/eval/retrieval-quality/harness.ts+src/commands/eval-retrieval-quality.ts+test/fixtures/retrieval-quality/namedthing.jsonl— NamedThingBench, the retrieval-quality eval that makes the named-thing-miss incident impossible to reintroduce silently. Seven query families, each a distinct failure class:title-substring(the direct regression),generic-to-named(tourist label → named thing),alias-synonym(declared alias / romanization → canonical),multi-chunk-dilution(one strong chunk among many weak — stresses max-pool),short-vs-rich,graph-relationship(guardrail),hard-negative(precision guard, must NOT return a page).gbrain eval retrieval-quality <fixture.jsonl>runs it with hard gates (e.g. title-substring Hit@1 ≥ 0.95, alias Hit@1 ≥ 0.98, multi-chunk-dilution Hit@3 = 1.0). Pure: caller injects aSearchFn(CLI useshybridSearch, tests stub) so it's engine-agnostic. Metric glossary entries (hit@1/hit@3) added tosrc/core/eval/metric-glossary.ts. Pinned bytest/eval-retrieval-quality.test.ts+test/retrieval-quality-harness.test.ts.docs/architecture/RETRIEVAL.md+docs/architecture/RETRIEVAL_MAXPOOL_INCIDENT.md— retrieval-pipeline architecture reference + the named-thing-miss incident write-up (root cause, the five-layer fix, the eval that pins it).src/core/types.tsextension +src/core/operations.ts:search+src/core/import-file.ts+src/cli.ts+src/core/search/telemetry.ts— the wiring layer for the retrieval cathedral.SearchResultgainsevidence,create_safety,title_match_boost,alias_hit(all optional; evidence/create_safety reference the union types inevidence.ts). ThesearchMCP op uses a cheap-hybrid path by default and accepts a per-callmode(conservative|balanced|tokenmax) honored ONLY for trusted/local callers (resolvePerCallMode(ctx, ...)— remote callers use the configured mode so a remote provider can't force tokenmax spend); every search path stamps evidence fail-soft.importFromContentprojects frontmatteraliases:intopage_aliasesvianormalizeAliasList+engine.setPageAliasesso new + changed pages register aliases at ingest.src/cli.tsadds thegbrain search diagnosedispatch (lazy import) and reconciles thesearchCLI path with the cheap-hybrid op.src/core/search/telemetry.tsextends the rollup with the rank-1 base_score drift signal (sum/count + 3 coarse buckets, aggregate not per-query), surfaced viagbrain search stats, backed by migration v111'ssearch_telemetrycolumns. Tests:test/cli-search-dispatch.test.ts,test/search/per-call-mode.test.ts,test/search/telemetry-rank1.test.ts,test/search/title-boost-stage.test.ts,test/search/alias-hop.test.ts,test/search/evidence.test.ts,test/search/searchvector-maxpool.test.ts,test/search/pre-migration-failopen.test.ts.src/commands/eval.ts—gbrain evalcommand: single-run table + A/B config comparison. Sub-subcommand dispatch onargs[0]routesgbrain eval export+gbrain eval prune+gbrain eval replayinto session-capture handlers; baregbrain eval --qrels …fall-through preserves the legacy IR-metrics flow.gbrain eval cross-modalis in the dispatch (the user-facing path is the cli.ts no-DB branch —src/commands/eval.ts:cross-modalonly fires when callers re-enter with an existing engine).src/commands/eval-cross-modal.ts— multi-model quality gate. Three different-provider frontier models score the OUTPUT against the TASK on a 5-dim list. Verdictpass(exit 0) /fail(exit 1) /inconclusive(exit 2; <2/3 model successes). Reusessrc/core/ai/gateway.ts:chat()so config/auth/aliasing comes from the gateway recipe registry — no parallel provider stack. Self-configures the gateway (configureGateway(loadConfig() + process.env)) since the cli.ts dispatch bypassesconnectEngine(). Default cycles 3 in TTY, 1 in non-TTY (partial cost guardrail) via the sharedresolveCycleDefault(explicit, isTty)insrc/core/eval/cycle-default.ts; the cost-estimate banner appendscycleDefaultSuffix(...)(for 1 cycle(s) (non-interactive default; --cycles N for more)) when the value is the silent non-TTY fallback, so the 1-vs-3 difference isn't hidden. Receipts land atgbrainPath('eval-receipts')/<slug>-<sha8-of-output>.json.--batch <jsonl> [--limit N] [--concurrent N] [--max-usd FLOAT] [--yes]fans out cross-modal scoring across a LongMemEval-shape JSONL; mutually exclusive with--task(fail-fast usage error if both set); filterskind: "by_type_summary"rows; pre-flight cost estimate refuses if> --max-usdwithout--yes(default cap 5.00 USD). Semaphore-bounded fan-out via inlinerunWithLimit<T>(items, limit, fn)(exported for unit tests): max N questions in-flight × 3 model slots = ceiling of 3N parallel API calls (default--concurrent 3→ 9). Per-question receipts land in a per-batch tempdir and are deleted at end of run; the summary receipt inlines per-question verdicts as JSON, not file paths. Exit precedence (batch-level policy, NOT inherited from aggregate.ts): ERROR > FAIL > INCONCLUSIVE > PASS. DI seam:runEvalCrossModal(args, opts?: {runEval?: typeof runEval})mirrorsrunEvalLongMemEval(args, {client?}); tests passopts.runEvalto bypass real LLM calls AND the gateway availability check. Pinned bytest/eval-cross-modal-batch.test.ts.src/core/eval/cycle-default.ts— single source of truth for the eval cycle-count default. ExportsDEFAULT_CYCLES_TTY = 3,DEFAULT_CYCLES_NONTTY = 1,resolveCycleDefault(explicit, isTty): {cycles, usedNonTtyDefault}, andcycleDefaultSuffix(r)(returns(non-interactive default; --cycles N for more)only when the non-TTY default was applied, else''). Consumed byeval-cross-modal.ts,eval-takes-quality.ts(run + regress), andtakes-quality-eval/runner.ts(core uses only the constant — library stays TTY-agnostic; the CLI owns the TTY=3 upgrade + banner annotation).eval-suspected-contradictions.tsapplies the same transparency to its$5/$1budget default via abudgetUsdExplicitflag (the budget is overwritten in-place so explicitness can't be inferred post-hoc). Not shared withresolveWorkersWithClamp(different domain, no engine, no dedup). Pinned bytest/eval/cycle-default.test.ts,test/eval-suspected-contradictions-budget-default.test.ts.src/core/cross-modal-eval/json-repair.ts—parseModelJSON(raw)named export with a 4-strategy fallback chain (direct parse → fence-strip → trailing-comma + single-quote + embedded-newline repair → regex nuclear option). Adversarial input throws rather than fabricating scores — the aggregator treats a throw as "this model contributed nothing this cycle" so the gate stays correct at >=2/3 successes.src/core/cross-modal-eval/aggregate.ts— pure verdict logic. Pass criterion:(successes >= 2) AND (every dim mean >= 7) AND (every dim min across models >= 5). Inconclusive when <2/3 models returned parseable scores (regression guard for the v1Object.values({}).every(...) === trueempty-array PASS bug).src/core/cross-modal-eval/runner.ts— orchestrator. Each cycle runsPromise.allSettled([gwChat(slotA), gwChat(slotB), gwChat(slotC)])(bare allSettled, no rate-leases for the CLI path). Stops early on PASS or INCONCLUSIVE; runs up to 3 cycles. Default slots:openai:gpt-4o/anthropic:claude-opus-4-7/google:gemini-1.5-pro.estimateCost()exports a small per-model pricing table (drifts; refresh alongside model-family bumps).src/core/cross-modal-eval/receipt-name.ts— receipt filename binds (slug, SKILL.md sha-8).findReceiptForSkill(skillPath, receiptDir)returns'found' | 'stale' | 'missing'. Skillify-check surfaces the status as informational; the audit does NOT fail on missing/stale receipts.src/core/cross-modal-eval/receipt-write.ts— wrapsfs.writeFileSyncwithmkdirSync({recursive:true})ahead of every write (gbrainPath()does NOT auto-mkdir).src/commands/eval-export.ts— streamseval_candidatesrows as NDJSON to stdout withschema_version: 1prefix on every line. EPIPE-safe, progress heartbeats on stderr, stable id-desc tiebreaker so--sincewindows never dupe/miss rows.src/commands/eval-prune.ts— explicit retention cleanup. Requires--older-than DUR.--dry-runreports would-delete count.src/commands/eval-replay.ts— contributor-facing replay tool. Reads NDJSON fromgbrain eval export, re-runs each capturedquery/searchop against the current brain, computes set-Jaccard@k between captured + currentretrieved_slugs, top-1 stability rate, and latency Δ. Stable JSON shape (schema_version: 1) for CI gating; human mode prints a regression table. Pure Bun, zero new deps. The dev-loop half of BrainBench-Real. Seedocs/eval-bench.md.parseNdjsonskips lines where_kind === 'baseline_metadata'sogbrain bench publishbaselines parse cleanly without the metadata header polluting row counts. ExportsreplayCore(engine, opts): Promise<{summary, results}>+ReplaySummarytype sogbrain eval gatecalls replay in-process (NOT subprocess — avoids gbrain-version-drift for source-tree CI). CLIrunEvalReplaywrapsreplayCore.src/core/bench/baseline-file.ts+src/core/bench/qrels-file.ts+src/core/bench/correctness-gate.ts+src/commands/bench-publish.ts+src/commands/eval-gate.ts— the eval-loop wave.gbrain bench publish --from <captured.ndjson> --to <X.baseline.ndjson>writes a baseline (stamps stablequery_hashper row; metadata header carries_kind: 'baseline_metadata'+ thresholds +source_hash+baseline_mean_latency_ms; deterministic sort by(tool_name, query_hash); strict: empty=fail, dupes=fail with paste-ready hint,--toexists=refuse without--force).gbrain eval gate [--baseline X] [--qrels Y]is the two-gate dispatcher (regression gate via in-processreplayCore, correctness gate via barehybridSearchfor determinism, both must pass when both flags set, exit 0 PASS / 1 FAIL / 2 USAGE). Source-id-aware:bench publishdedup key is(tool_name, source_ids, query_hash); qrels compare keys are${source_id}::${slug}everywhere (closes the multi-source bug class at the file-shape layer). Latency math:(baseline + delta) / baseline <= multiplier. Fail-closed: ANY in-process throw flips verdict to fail with named breach inbreaches[]— never silently exit 0..qrels.jsonpreserves the 12-rowtest/fixtures/eval-baselines/qrels-search.jsonfixture (slug-onlyrelevant_slugs+first_relevant_slugauto-promote tosource_id='default') AND supports the federated shape (explicitrelevant: [{source_id, slug}]+expected_top1).correctness-gate.tsruns each qrels query via barehybridSearch; per-query throw recorded aserrored: trueand flagged as gate failure. Audit JSONL at~/.gbrain/audit/bench-publish-YYYY-Www.jsonl. Pinned bytest/bench/baseline-file.test.ts,test/bench/qrels-file.test.ts,test/bench/correctness-gate.test.ts,test/bench-publish.test.ts,test/eval-gate.test.ts,test/eval-replay-metadata-skip.test.ts,test/cycle/nightly-probe-adapters.test.ts,test/autopilot-nightly-probe-wiring.test.ts,test/e2e/eval-loop.test.ts.src/core/cycle/nightly-probe-adapters.ts— bridges the autopilot's object-shapeNightlyProbeDepsto the argv-shaperunEvalLongMemEval+runEvalCrossModalCLI functions. Cross-modal adapter argv MUST include--output summaryPath(without it the summary lands at the default receipt path and the adapter reads nothing fromsummaryPath). In-process invocation (NOT subprocess) — avoids gbrain-version-drift for source-tree CI. Pinned bytest/cycle/nightly-probe-adapters.test.ts(incl. argv-shape regression for the--outputrequirement).src/commands/autopilot.tsextension — tick body invokesrunNightlyQualityProbewhencfg.autopilot.nightly_quality_probe.enabled === true(default OFF — opt-in to protect API spend). NO scheduler-side rate-limit check —runNightlyQualityProbe's internalshouldRunNightly(reading the audit JSONL) is the single source of truth. Probe call wrapped in try/catch that logs vialogErrorand does NOT bumpconsecutiveErrors(probe failure is informational, never crashes the loop). Defaultmax_usdcap = 5. Pinned bytest/autopilot-nightly-probe-wiring.test.ts.test/eval-replay-gate.test.ts+test/fixtures/eval-baselines/qrels-search.json— hermetic retrieval qrels gate running in the standard PR unit-shard CI matrix (.github/workflows/test.yml, NOT the fixed-file E2E workflow). Uses the canonical PGLite block (test-isolation R3+R4) and the basis-vector embedding pattern fromtest/e2e/search-quality.test.ts:23-28for fully hermetic retrieval. The qrels fixture (12 queries) uses PLACEHOLDER names only (alice-example, widget-co-example, etc. — privacy rule) and embeds each query at a deterministic basis dimension so retrieval is reproducible. Each query listsrelevant_slugs[]+first_relevant_slug; the test computestop1_match_rate(top-1 == first_relevant) andrecall@10(fraction of relevant_slugs in top-10), asserting both meet floors (defaults>= 0.80and>= 0.85). Env-overridable floorsGBRAIN_REPLAY_GATE_TOP1_FLOOR/GBRAIN_REPLAY_GATE_RECALL_FLOOR(viawithEnv()per R1). Refresh discipline: when ranking changes intentionally move expected slugs, editqrels-search.jsondirectly with aWhy:line in the commit body or the gate degrades to rubber-stamp. Pinned bytest/eval-replay-gate.test.ts(incl. a privacy-grep regression guard against real-name reintroduction).src/core/cycle/nightly-quality-probe.ts+src/core/audit-quality-probe.ts+test/fixtures/longmemeval-nightly.jsonl+test/nightly-quality-probe.test.ts— opt-in nightly cross-modal quality probe. The phase runsgbrain eval longmemeval --by-typeagainst the committed 10-question placeholder fixture, pipes output throughgbrain eval cross-modal --batch --max-usd 5 --yes, and writes one event per run to~/.gbrain/audit/quality-probe-YYYY-Www.jsonl(ISO-week-rotated, mirrorsaudit-slug-fallback.ts; honorsGBRAIN_AUDIT_DIR). Default DISABLED — opt-in viagbrain config set autopilot.nightly_quality_probe.enabled true(prevents surprise API spend). 24h rate limit (pureshouldRunNightly(now, recentEvents, windowMs?)) skips with audit rowoutcome: rate_limited. Embedding-key short-circuit: longmemeval needsgateway.embedQuery(), so the phase exits early withoutcome: no_embedding_key+ stderr warn when no provider configured. Full DI surface viaNightlyProbeDeps(isEnabled,hasEmbeddingProvider,resolveMaxUsd,resolveRepoRoot,runLongMemEval,runCrossModalBatch,now) so the unit test stubs every external effect. Cost ceiling: $5/run × 30 nights ≈ $150/month worst-case; expected ~$10.50/month. Newnightly_quality_probe_healthdoctor check (src/commands/doctor.ts, right afterslug_fallback_audit) reads last 7 days: SKIPPED when flag off (with enable command); OK when enabled + all PASS; WARN on any FAIL / ERROR / BUDGET_EXCEEDED with per-outcome counts. Pinned bytest/nightly-quality-probe.test.ts.src/commands/eval-trajectory.ts+src/commands/founder-scorecard.ts+src/core/trajectory.ts— temporal trajectory + founder scorecard.gbrain eval trajectory <entity>shows the chronological typed-claim history (mrr/arr/team_size/etc) with regressions auto-flagged inline;gbrain founder scorecard <entity>rolls up claim_accuracy / consistency / growth_trajectory / red_flags into one JSON. Pure-function math intrajectory.ts:detectRegressions(points, threshold)walks consecutive metric-value pairs per metric (10% drop default, env overrideGBRAIN_TRAJECTORY_REGRESSION_THRESHOLD);computeDriftScore(points)returns1 - mean(cosine(emb[i], emb[i-1]))over existing embeddings (null when <3 embedded points). Backed byBrainEngine.findTrajectory(opts)— both Postgres and PGLite, single SQL query, deterministicORDER BY valid_from ASC, id ASC. Source-scoped via thesourceIdscalar /sourceIdsarray dual pattern; visibility-filtered for remote callers. MCP opfind_trajectory(read scope, NOT localOnly) registered afterfind_experts. Migration v67 adds optional typed-claim columns (claim_metric,claim_value,claim_unit,claim_period) + a partial index on(entity_slug, claim_metric, valid_from) WHERE claim_metric IS NOT NULL. Fence widens from 10 to 14 cells when any row has typed data; renderer stays at 10 cells when none do (no churn diff on existing fences). Metric labels normalize to lowercase snake_case vianormalizeMetricLabel(15-entry seed map). Theconsolidatecycle phase does semantic upsert keyed on(page_id, claim, since_date)(fixes the duplicate-takes bug where re-running the cycle afterextract_factsclearedconsolidated_atappended duplicates viaMAX(row_num)+1) and writes chronologicalvalid_untilon each cluster's older facts. Theextract_factscycle phase batch-embeds viagateway.embed()before insert AND threadspages.effective_dateas thepageEffectiveDatefallback forvalid_from(precedence: fence-row > pageEffectiveDate > now()). The contradiction probe MUST NOT writevalid_until— grep guard attest/eval-contradictions/no-valid-until-write.test.ts. Haiku extraction lives insrc/core/facts/extract.ts(not theextract-facts.tscycle phase);pageEffectiveDateis OPTIONAL becausefence-write.tscallers have no Page object. Migration v89 adds a nullableevent_type TEXTcolumn onfactsso the substrate carries event-shaped rows (event_type='meeting'/'job_change'/'location_change') alongside metric rows.TrajectoryPoint.event_type: string | nullprojected by both engines.TrajectoryOpts.kind?: 'metric' | 'event' | 'all'filter (default'all');founder-scorecard+eval-trajectorypasskind: 'metric'explicitly. Back-compat pinned bytest/regressions/v0_40_2_0-trajectory-backcompat.test.ts(byte-identicalcomputeFounderScorecard+computeTrajectoryStatswith and without event rows); engine parity intest/engine-parity-event-type.test.ts.src/core/trajectory-format.ts— sharedformatTrajectoryBlock(points, entitySlug, opts)consumed by bothgbrain think(production) and the LongMemEval harness (benchmark). Groups by(metric ?? event_type), per-metric cap 20, total cap 100, knowledge_update intent annotates value-change rows with(superseded prior). Emits a<trajectory entity="...">XML envelope —INJECTION_PATTERNSinsrc/core/think/sanitize.tsescapes</trajectory>,<trajectory ...>open tags, and attribute injection so adversarial fact text can't break out. Pinned bytest/trajectory-format.test.ts.src/core/think/intent.ts+src/core/think/entity-extract.ts— pureclassifyIntent(question)returns'temporal' | 'knowledge_update' | 'other'(regex-first, no LLM,'other'fast path short-circuits with zero SQL).extractCandidateEntities(question, retrievedSlugs)pulls high-precision candidates from retrieved entity-prefix slugs (people/,companies/,organizations/) and medium-precision noun phrases. Stop-word boundaries + leading-verb stripper handle "When did I last meet Marco" →marco. Both consumed byrunThinkand the LongMemEval harness so the two paths cannot drift. Pinned bytest/think-intent.test.tsandtest/think-entity-extract.test.ts.src/commands/eval-suspected-contradictions.ts+src/core/eval-contradictions/{judge,runner,types,date-filter,cost-tracker,cache,severity-classify,cross-source,trends,calibration,judge-errors,auto-supersession,fixture-redact}.ts—gbrain eval suspected-contradictions [run|trend|review]. Probe samples top-K retrieval pairs per query (cross-slug + intra-page chunk-vs-take), date pre-filters (3-rule layered — same-paragraph-dual-date overrides separation rule), LLM judge (query-conditioned; UTF-8-safe truncation; confidence-floor double-enforcement; resolution_kind output drives paste-ready commands), persistent cache keyed on(chunk_a_hash, chunk_b_hash, model_id, prompt_version, truncation_policy)(prompt edits cleanly invalidate prior verdicts), Wilson 95% CI calibration on the headline percentage withsmall_sample_notewhen n<30, judge_errors as first-class typed counters (parse_fail/refusal/timeout/http_5xx/unknown — avoids bias from silent skip), trend writes toeval_contradictions_runs, source-tier breakdown reusesDEFAULT_SOURCE_BOOSTSprefix logic, deterministic sampling (combined_score DESC + lex tiebreaker for stable cache hit-rate). Hermetic viajudgeFn+searchFnDI in the runner; never touches the real gateway in tests. Engine surface:BrainEngine.listActiveTakesForPages(batched),writeContradictionsRun+loadContradictionsTrend,getContradictionCacheEntry+putContradictionCacheEntry+sweepContradictionCache. Schema migrations v51 + v52. MCP opfind_contradictions(read scope, NOT localOnly, NOT in subagent allowlist — user-initiated only). Doctor check surfaces high-severity findings with paste-ready resolution commands; synthesize phase pre-fetches the latest probe's top-5-by-severity findings and threads them intobuildSynthesisPromptas an informational block. Architecture doc:docs/contradictions.md.src/core/think/index.ts—runThinkbuilds its internalLLMClientvia a small adapter wrappinggateway.chat()fromsrc/core/ai/gateway.ts(notnew Anthropic()directly) so stdio MCP launches (Claude Desktop, Cursor) that don't inherit shell env still find a key set viagbrain config set anthropic_api_key(the gateway reads~/.gbrain/config.jsonAND env). Test seam:opts.client?: ThinkLLMClientinjection works (test/think-pipeline.serial.test.ts,test/think-gateway-adapter.test.ts);opts.stubResponseshort-circuits before any LLM call. When neither key nor client is available, the "no LLM available" stub fires withNO_ANTHROPIC_API_KEY. Trajectory injection (default ON):runThinkorchestratesclassifyIntent(question)→extractCandidateEntities(question, retrievedSlugs)→findTrajectory(5sPromise.racetimeout per candidate, concurrency cap 3) →formatTrajectoryBlock.buildThinkUserMessage(insrc/core/think/prompt.ts) has atrajectory?: ThinkTrajectoryBlockOptsslot honoring BOTH prompt orderings (calibration mode: retrieval → calibration → trajectory → question; default mode: question → retrieval → trajectory → instruction). The MCPthinkop handler extractssourceScopeOpts(ctx)to scalarsourceId/allowedSources/remoteonRunThinkOptsso federated-read OAuth clients can't see trajectory rows outside their source scope. Config keythink.trajectory_enabled(defaulttrue). Any error in the trajectory path degrades to "no block injected" +TRAJECTORY_INJECTION_FAILEDwarning — the think call never crashes from trajectory. Production path skipsfallback_slugifyresolutions (avoid querying invented slugs); the LongMemEval harness accepts them. Pinned bytest/think-trajectory-injection.test.ts. Debug:GBRAIN_THINK_DEBUG=1 gbrain think "..."prints the spliced prompt to stderr.src/core/operations.tsextension (orphans fix) —findOrphanPages(both engines) filtersp.deleted_at IS NULLon the candidate side AND addsJOIN pages src ON src.id = l.from_page_id WHERE src.deleted_at IS NULLto the EXISTS subquery on the link-source side, so soft-deleted pages don't appear as orphans AND links from soft-deleted source pages don't suppress live pages from orphan results. Pinned bytest/orphans.test.ts's soft-delete cases.src/commands/eval-longmemeval.ts+src/eval/longmemeval/{harness,adapter,sanitize}.ts—gbrain eval longmemeval <dataset.jsonl>runs the public LongMemEval benchmark against gbrain's hybrid retrieval. One in-memory PGLite per run viacreateBenchmarkBrain+withBenchmarkBrain(NOEphemeralBrainclass). Between questions,TRUNCATEover runtime-enumeratedpg_tables(schema-migration-safe); infrastructure tables (sources,config,gbrain_cycle_locks,subagent_rate_leases) preserved.cli.tspre-dispatch bypass soeval longmemevalskipsconnectEngine()— the user's~/.gbrainbrain is never opened.--expansiondefaults OFF (deterministic, no per-query Haiku); pass--expansionto opt in. Default model viaresolveModel()6-tier chain withmodels.eval.longmemevalconfig key. Sanitization parity:harness.tsreusesINJECTION_PATTERNSfromsrc/core/think/sanitize.tsso adding a pattern covers takes AND benchmarks. Retrieved chat content wrapped in<chat_session id="..." date="...">; the answer-gen system prompt declares content UNTRUSTED. LLM injection seam:runEvalLongMemEval(args, {client?: ThinkLLMClient})lets tests stub the client without an API key. p50 25.9ms / p99 30.3ms warm reset+import+search on Apple Silicon (test/eval-longmemeval.test.tsperf gate). Hand the JSONL to LongMemEval'sevaluate_qa.pyto score (not bundled — needs OpenAI gpt-4o). Per-question JSONL row carriesquestion: string(additive;evaluate_qa.pyignores unknown fields) sogbrain eval cross-modal --batchhas thetasktext without joining; alsoquestion_type: stringandrecall_hit?: booleanso a--resume-fromrun rebuilds cumulativerecallByTypefrom the file alone.--by-typeflag emits a{schema_version:1, kind:"by_type_summary", recall_by_type:{...}, aggregate:{...}}line as the FINAL line; resume-replace strips any prior summary at the tail so 5 resumed runs produce 1 summary. Empty-bucket guard:aggregate.rateisnull(not NaN) when no questions had ground truth. Optional--by-type-floor F(0..1) exits non-zero with a stderr line per breachedquestion_type(default informational). PurebuildByTypeSummary(buckets)+emitByTypeSummary(path, summary)+seedRecallByTypeFromFile(path, bucket)exported for unit tests. Inline Haiku extractor + trajectory routing (methodology change):src/eval/longmemeval/extract.tsrunsextractAndInsertClaims()over each haystack session before retrieval, populating the benchmark brain'sfactstable inline at import. Single Haiku call per session with content-hash cache (cuts a 3-iteration run from $1.50 to $0.50 when sessions repeat). Per-question alias map (fresh per question, never leaks) collapses"Marco"+"Marco Smith"+"marco"to one canonical slug via first-mention-wins. Fail-open on every error path (malformed JSON, Haiku throw, insert collision, empty array →inserted: 0).getCacheStats()writes empirical hit rate to stderr.src/eval/longmemeval/intent.tsprefers the dataset'squestion_typelabel before falling back to the SHARED regex set fromsrc/core/think/intent.ts— single source of truth means think and longmemeval cannot drift.runOneQuestionroutes temporal/knowledge_update intents through sharedextractCandidateEntities→findTrajectory→ splice into the answer-gen prompt before the retrieved-sessions block.--no-trajectorybypasses BOTH extractor and intent routing (baseline default-on vs no-trajectory across 3 seeds with paired-bootstrap CI). JSON envelope adds 5 per-question fields when trajectory routing is on:intent,trajectory_points,entity_resolved,resolution_source,methodology_note. Themethodology_notewrites to stderr at run completion (extractor=haiku-preprocess-full-haystack-v1) — honest disclosure that the published number is "gbrain + Haiku-preprocess pipeline" vs "gbrain alone", NOT directly comparable to baseline LongMemEval scores without that note. Pinned bytest/longmemeval-extract.test.ts,test/longmemeval-intent.test.ts,test/longmemeval-trajectory-routing.test.ts(end-to-end throughrunEvalLongMemEvalwith both clients stubbed).docs/eval-bench.md— contributor guide for using captured data to benchmark retrieval changes before merging. Linked from CONTRIBUTING.md under "Running real-world eval benchmarks (touching retrieval code)".src/core/eval-capture.ts— op-layer capture wrapper called fromsrc/core/operations.tsquery+searchhandlers (catches MCP + CLI + subagent tool-bridge from one site). Fire-and-forget; failures route toengine.logEvalCaptureFailuresogbrain doctorsees drops cross-process. Capture is off by default —isEvalCaptureEnabledresolution: explicitconfig.eval.capture(true/false) wins, elseprocess.env.GBRAIN_CONTRIBUTOR_MODE === '1', else off. Contributors setexport GBRAIN_CONTRIBUTOR_MODE=1. PII scrubber gate is independent and defaults to true regardless of CONTRIBUTOR_MODE.src/core/eval-capture-scrub.ts— zero-deps PII scrubber: emails, phones, SSN, Luhn-verified credit cards, JWT-shaped tokens, bearer tokens.src/core/search/hybrid.ts— Cathedral IIPromise<SearchResult[]>return shape.onMeta?: (m: HybridSearchMeta) => voidcallback so op-layer capture records what hybridSearch actually did; existing callers leave it undefined.HybridSearchOpts.types?: PageType[](onSearchOpts) threads a multi-type filter into per-enginesearchKeyword+searchVector+searchKeywordChunksasAND p.type = ANY($N::text[])(primary consumergbrain whoknows, filters to['person','company']); AND-applies alongside the single-valuetypefilter.hybridSearchresolves the embedding column at the boundary viaresolveColumn(loadRegistry(cfg), opts.embedding_column, cfg)fromsrc/core/search/embedding-column.ts, threads theResolvedColumndescriptor (not a raw string) into per-enginesearchVector, and usesisCacheSafe(resolved, cfg)for the cache-skip decision so a repointedembeddingbuiltin doesn't leak across vector spaces.cosineReScorecallsengine.getEmbeddingsByChunkIds(ids, resolved.name)so rerank uses vectors from the active column, not the hardcoded OpenAIembedding. ThequeryMCP op acceptsembedding_columnfor per-call A/B;search(keyword-only) rejects it. Two post-fusion stages + evidence stamp:applyTitleBoost(results, query, titleBoost, floorThreshold)multiplies a result's score by the resolvedtitle_boostwhenisTitlePhraseMatchfires, stampstitle_match_boost, inherits the floor-ratio gate so a title match can't shove a much-stronger page below it;applyAliasHop(engine, results, query, opts)normalizes the query, callsengine.resolveAliases, and on exact normalized-alias match surfaces that page at top-of-organic + epsilon withalias_hit=true;stampEvidence(...)runs LAST (after the alias hop, before slice) on every path — keyword-only, no-embed, and full hybrid — so MCP callers and--explainread the sameevidence+create_safetycontract.title_boostresolved from the mode bundle and threaded in.docs/eval-capture.md— stable NDJSON schema reference for gbrain-evals consumers.test/public-exports.test.ts— runtime contract test (R2). Imports each of the 17 public subpaths via package name and pins a canary symbol per module. Paired withscripts/check-exports-count.sh.src/core/embedding.ts— OpenAI text-embedding-3-large, batch, retry, backoff.BATCH_SIZE=100(per-recipe pre-split + recursive halving + adaptive shrink-on-miss live in the gateway; the outer paginator is for progress-callback granularity, not batch protection).estimateEmbeddingCostUsd(tokens)prices against the currently-configured model's rate viacurrentEmbeddingPricePerMTok()(resolves the per-1M-token rate vialookupEmbeddingPrice(gatewayGetModel())fromembedding-pricing.ts, falling back to the OpenAI 3-large rate 0.13 only when the gateway is unconfigured or the model is unknown to the pricing table).EMBEDDING_COST_PER_1K_TOKENSretained for back-compat with direct importers/tests.currentEmbeddingSignature(): stringreturns the embedding-provenance signature<provider:model>:<dims>(e.g.openai:text-embedding-3-large:1536) stamped ontopages.embedding_signatureat every embed-write site; DELIBERATELY excludes the chunker version (tracked separately viapages.chunker_version) — this signature is strictly the EMBEDDING space, so a model OR dimension swap makes the stored signature differ from current and a page becomes stale. Same unconfigured-gateway fallback as the cost helpers.willEmbedSynchronously({v2Enabled, serialFlag, noEmbed}): SyncEmbedModeis the single source of truth for whethergbrain sync --allembeds at sync time ('inline') or defers to per-sourceembed-backfillminion jobs ('deferred') — mirrorssync.ts'seffectiveNoEmbedresolution exactly (v2Enabled && !serialFlag && !noEmbed → deferred) so cost gate and embed decision can't drift.shouldBlockSync(costUsd, floorUsd, mode): booleanis the pure cost-gate decision: blocks ONLY whenmode === 'inline' && costUsd > floorUsd— deferred mode never blocks (the backfill's $X/source/24h cap is the real money gate). Pinned bytest/sync-cost-preview.test.ts+test/embedding-signature-stale.test.ts.src/core/ai/dims.ts— per-providerproviderOptionsresolver for embed-time dimension passthrough; the single source of truth for "which provider needs which knob to producevector(N)". ExportsdimsProviderOptions(implementation, modelId, dims)(called byembed()ingateway.ts),VOYAGE_OUTPUT_DIMENSION_MODELS(private const — the 7 hosted Voyage models that acceptoutput_dimension:voyage-4-large,voyage-4,voyage-4-lite,voyage-3-large,voyage-3.5,voyage-3.5-lite,voyage-code-3— nano deliberately excluded),VOYAGE_VALID_OUTPUT_DIMS = [256, 512, 1024, 2048] as const,supportsVoyageOutputDimension(modelId),isValidVoyageOutputDim(dims). Voyage path uses the SDK-supporteddimensionsfield ({ openaiCompatible: { dimensions: N } }), NOT Voyage'soutput_dimensionwire-key — thevoyageCompatFetchshim ingateway.ts:541translatesdimensions → output_dimensionbefore the HTTP body is built (the AI SDK's openai-compatible adapter doesn't recognize the wire-key, so sending it from here would be silently dropped and Voyage would return its default 1024-dim). Runtime guard: when a Voyage flexible-dim model is configured withdimsoutsideVOYAGE_VALID_OUTPUT_DIMS, throwsAIConfigErrorwith a paste-readygbrain config set embedding_dimensions <256|512|1024|2048>hint at the embed boundary (most common trigger:embedding_model: voyage:voyage-4-largewithoutembedding_dimensions, falling back toDEFAULT_EMBEDDING_DIMENSIONS=1536, an OpenAI default not a Voyage one).src/core/ai/types.ts— provider/recipe types.EmbeddingTouchpointhas optionalchars_per_token(default 4, matching OpenAI tiktoken on English) andsafety_factor(default 0.8, budget-utilization ceiling), both consulted only whenmax_batch_tokensis also set; Voyage declareschars_per_token=1+safety_factor=0.5to handle dense payloads (CJK/JSON/base64). Pre-split budget =max_batch_tokens × safety_factor / chars_per_token.EmbeddingTouchpoint.multimodal_models?: string[]model-level allow-list for recipes mixing text-only + multimodal models under one touchpoint (Voyage's 12 models sharesupports_multimodal: truebut onlyvoyage-multimodal-3accepts/multimodalembeddings); when omitted, recipe-levelsupports_multimodalis sufficient.AIGatewayConfig.embedding_multimodal_model?: stringletsembedMultimodal()route to a different model thanembedding_model(OpenAI text + Voyage images without flipping the primary pipeline).Recipe.default_headers?: Record<string, string>(static) andRecipe.resolveDefaultHeaders?(env)(env-templated) seam for per-recipe headers riding alongside auth on every openai-compat touchpoint; mutually exclusive (declaring both throwsAIConfigErrorat gateway-configure time); keys conflicting with the resolved auth header (Authorization, the resolver's custom header) rejected atapplyResolveAuthcall time so defaults can't shadow auth. Used by OpenRouter for theHTTP-Referer+X-OpenRouter-Title+X-Titleattribution triple.src/core/ai/gateway.ts— unified seam for every AI call.embedQuery(text, opts?)andisAvailable(touchpoint, modelOverride?)accept a model override so the resolved-column path embeds via the column's provider (Voyage / ZeroEntropy / OpenAI) instead of the global default; the hybrid path passes{embeddingModel: resolved.provider, dimensions: resolved.dimensions}and the gateway resolves the matching recipe viainstantiateEmbedding().isAvailable('embedding', 'voyage:voyage-3-large')checks the override's recipe (not the default) so hybrid skips vector search only when the active column's provider is actually down.zeroEntropyCompatFetchshim (sibling tovoyageCompatFetch) handles ZE's non-OpenAI-compatible wire shape — rewrites the request URL/embeddings → /models/embed, injectsinput_type(default'document';'query'when threaded viaproviderOptions.openaiCompatible.input_type) and explicitencoding_format: 'float', and rewrites the response{results: [{embedding}], usage: {total_bytes, total_tokens}}→{data: [{embedding, index}], usage: {prompt_tokens, total_tokens}}so the SDK's openai-compatible Zod schema validates. Layer 1 (Content-Length) + Layer 2 (per-embedding) OOM caps via taggedZeroEntropyResponseTooLargeError(kept separate fromVoyageResponseTooLargeErrorbecausetest/voyage-response-cap.test.tsdoes structural source-text greps pinning the Voyage name). Wired ininstantiateEmbedding()via therecipe.id === 'zeroentropyai'branch.gateway.rerank()native HTTP path (no AI-SDK reranking abstraction): resolves the configured reranker viagetRerankerModel(), posts to${recipe.base_url}/models/rerankwith bearer auth, returnsRerankResult[]sorted by relevance.RerankError.reasonclassifier:auth | rate_limit | network | timeout | payload_too_large | unknown. 5s default timeout (search hot path). Pre-flight payload guard rejects bodies overrecipe.touchpoints.reranker.max_payload_byteswithreason: 'payload_too_large'._rerankTransporttest seam mirrors_embedTransport.embedQuery(text)threadsinputType: 'query'throughdimsProviderOptions()(4-arg).getRerankerModel()accessor +isAvailable('reranker')branch;configureGateway+reconfigureGatewayWithEnginethreadreranker_model;applyResolveAuth+defaultResolveAuthwiden touchpoint param to include'reranker'.embedMultimodalOpenAICompat()routes recipes withimplementation: 'openai-compatible'(LiteLLM, Anyscale, vLLM, Gemini multimodal via proxy) through the standard/embeddingsendpoint with content arrays carryingimage_urlentries; the Voyage/multimodalembeddingspath is unchanged (gateway selects by recipeimplementationtag). Runtime dimension validation throwsAIConfigError(with model id + observed + expected) before the vector reaches storage when the provider returns a width that doesn't match the recipe'sdefault_dimsor the brain'sembedding_dimensions. Pinned bytest/openai-compat-multimodal.test.ts. Module-scoped_embedTransportdefaults to AI SDKembedMany, with__setEmbedTransportForTests(fn)test seam so tests driveembed()with a stubbed transport.splitByTokenBudgetandisTokenLimitErrorexported@internal(pure functions reused by the test file). Module-level_shrinkState: Map<recipeId, {factor, consecutiveSuccesses}>halves the recipe's effectivesafety_factoron token-limit miss (floor 0.05) and heals back ×1.5 afterSHRINK_HEAL_AFTER=10consecutive successes.configureGateway()walks every registered recipe at construction and emits a once-per-process stderr warning for any embedding touchpoint missingmax_batch_tokens(excluding the canonical OpenAI fast-path).resetGateway()clears_shrinkState, the warned-set, and restores the real transport.embedMultimodal()readscfg.embedding_multimodal_modelfirst (falls back tocfg.embedding_model); after the recipe-levelsupports_multimodalfast-fail, validates the resolved model againsttouchpoint.multimodal_modelswhen declared (closes the Voyage-text-only-into-multimodal-endpoint footgun before any HTTP call).getMultimodalModel()accessor mirrorsgetEmbeddingModel/getChatModel. ExportedVoyageResponseTooLargeErrortagged class:voyageCompatFetch's two OOM-defense caps (Layer 1 Content-Length at:595, Layer 2 per-embedding base64 at:619) throw it; the inbound response-rewriter's try/catch (which swallows parse failures so misshaped responses fall through to the SDK parser) checksinstanceof VoyageResponseTooLargeErrorand rethrows so the cap is actually effective (regression assertion intest/voyage-response-cap.test.tspins theinstanceof ⇒ throw errline). AI SDK v6 toolLoop compat (gbrain skilloptrollouts AND production backgroundsubagentjobs both route throughchat()/toolLoop): inchat(), tool defs wrap the raw JSON Schema with the SDK'sjsonSchema()helper (inputSchema: jsonSchema(t.inputSchema)) — v6'sasSchema()treats a bare{jsonSchema: ...}object as a thunk and throws "schema is not a function"; new exported puretoModelMessages(messages: ChatMessage[]): unknown[]converts gbrain's provider-neutralChatMessage[]into v6ModelMessage[]— tool results (pushed bytoolLoopasrole:'user'with bare-value tool-result blocks) become a dedicatedrole:'tool'message with structuredoutput:{type:'json'|'text'|'error-text', value}parts;nulloutput preserved as{type:'json', value:null}(not dropped); text/tool-call blocks pass through with v6 field names (toolCallId/toolName/input); applied at thegenerateTextcall (messages: toModelMessages(opts.messages)). The converter is the load-bearing fix for the production subagent path, not just skillopt. Pinned bytest/gateway-model-messages.test.ts. Companion fix insrc/core/skillopt/rollout.ts: the inlineparamsToSchemadroppeditemson array params; it now uses the sharedparamDefToSchemafromsrc/mcp/tool-defs.ts(single source of truth, recursive on items/enum/default).src/core/ai/recipes/zeroentropyai.ts— ZeroEntropy openai-compatible recipe declaring BOTHembedding(zembed-1, 7 Matryoshka dims: 2560/1280/640/320/160/80/40) ANDreranker(zerank-2flagship +zerank-1+zerank-1-small, 5MB payload cap) touchpoints.implementation: 'openai-compatible'(pinned by regression intest/ai/zeroentropy-recipe.test.ts).base_url_default: 'https://api.zeroentropy.dev/v1'already ends with/v1, so thezeroEntropyCompatFetchURL rewrite/embeddings → /models/embedproduces…/v1/models/embed(NOT…/v1/v1/…— pinned by regression).chars_per_token: 1+safety_factor: 0.5match Voyage's dense-content hedge.src/core/ai/recipes/llama-server-reranker.ts— sibling ofllama-server(the embedding recipe) for llama.cpp in--rerankingmode. Distinct recipe rather than dual-touchpoint extension because--rerankingand--embeddingsare mutually exclusive at server-launch time, so the two backends need independent base URLs (default 8081 here vs 8080 there). Declaresrerankertouchpoint withmodels: [](user-provided id matching the--aliasthe user launched with),path: '/rerank'(leaf-only; consumesRerankerTouchpoint.pathoverride; gateway concatenates withbase_url_defaultwhich ends in/v1, producing…/v1/rerank),default_timeout_ms: 30_000(consumed bysrc/core/search/mode.ts's reranker timeout chain — CPU-only first-call warmup headroom; the 5s mode-bundle default would fail-open astimeout),cost_per_1m_tokens_usd: 0(recognized byFREE_LOCAL_RERANK_PROVIDERSinsrc/core/budget/budget-tracker.tsso--max-costcallers don't hard-fail on local rerank). Setup hint emphasizes--aliasbecause llama-server's/v1/modelsdefaults model id to the gguf file path without it. Covers Qwen3-Reranker via llama.cpp AND self-hosted ZE weights via llama.cpp — same recipe, different--modelat launch. Pinned bytest/ai/recipe-llama-server-reranker.test.ts. Voyage / Cohere / vLLM rerankers stay out of scope (different wire shapes). Same wave adds:path?: string+default_timeout_ms?: numberonRerankerTouchpointinsrc/core/ai/types.ts; consumed by the URL build atsrc/core/ai/gateway.ts:rerank()and by mode-resolution atsrc/core/search/mode.ts:resolveSearchMode(precedence: per-call > config-key > recipe touchpoint default > mode bundle);LLAMA_SERVER_RERANKER_BASE_URLenv passthrough insrc/cli.ts:buildGatewayConfig;FREE_LOCAL_RERANK_PROVIDERSset insrc/core/budget/budget-tracker.ts:lookupPricing(rerank-kind-only zero-pricing for the local provider prefix); doctor-fix atsrc/commands/models.ts:probeRerankerConfigreadssearch.reranker.modelvialoadSearchModeConfig+resolveSearchMode(closes file-plane / DB-plane divergence where doctor said "not configured" while live search was actively reranking — the field-planegetRerankerModel()read nothing writes);probeRerankerReachabilityreads the recipe'sdefault_timeout_msso CPU-only cold-start doesn't false-fail.src/core/ai/recipes/openrouter.ts— OpenRouter openai-compatible recipe: single key, many providers viaopenrouter:<provider>/<model>strings.base_url_default: 'https://openrouter.ai/api/v1'. Embedding touchpoint:openai/text-embedding-3-smallat 1536 dims with Matryoshkadims_options: [512, 768, 1024, 1536];max_batch_tokens: 300_000= OpenAI's aggregate-per-request token cap (NOT per-input). Chat touchpoint declares 8 curated entry points (gpt-5.2, gpt-5.2-chat, gpt-5.5, claude-haiku-4.5, claude-sonnet-4.6, claude-opus-4.7, gemini-3-flash-preview, deepseek-chat) but openai-compat tier accepts any model ID; deliberately nomax_context_tokensbecause OR's catalog spans 128K to 1M+.supports_subagent_loop: falseis INFORMATIONAL — the real gate isisAnthropicProvider()insrc/core/model-config.tswhich hard-pins gbrain's subagent infra to Anthropic-direct. DeclaresresolveDefaultHeaders(env)returning OR's three attribution headers:HTTP-Referer(required for OR app-attribution),X-OpenRouter-Title(preferred),X-Title(back-compat alias); defaults tohttps://gbrain.ai/gbrain; forks override viaOPENROUTER_REFERER/OPENROUTER_TITLEenv vars. Smoke-tested bytest/ai/recipe-openrouter.test.ts(incl. the shape-test regression guard: every model in the chat list matches^[a-z0-9-]+\/[a-z0-9._-]+$).src/core/rerank-audit.ts— failure-only JSONL audit at~/.gbrain/audit/rerank-failures-YYYY-Www.jsonl(ISO-week rotation, mirrorssrc/core/audit-slug-fallback.ts). ExportslogRerankFailure({reason, model, query_hash, doc_count, error_summary})+readRecentRerankFailures(days). Deliberately nologRerankSuccess: writing once per tokenmax search is hot-path I/O churn AND success events leak query volume + timing into a local audit file.gbrain doctor'sreranker_healthcheck readssearch.reranker.enabledfirst so "no events in window" is interpreted correctly (disabled → ok; enabled → ok). Query text SHA-256-prefix-hashed (8 hex chars) for privacy.GBRAIN_AUDIT_DIRenv override honored via the sharedresolveAuditDir().src/core/search/embedding-column.ts— single source of truth for "whichcontent_chunks.*column does this query rank against?" Pure functions, no engine I/O:loadRegistry(cfg)walks theembedding_columnsconfig (DB plane, JSON map keyed by column name with{provider, dimensions, type}entries), seeds the OpenAIembeddingbuiltin when unset, validates everything before it lands (column-name regex, type ∈vector | halfvec, dims in [1, 8192], provider format) usingObject.create(null)+Object.hasOwnso a key likeconstructorrejects instead of resolving toObject.prototype.constructor.resolveColumn(registry, override?, cfg)is the boundary call: returns a frozenResolvedColumndescriptor ({name, provider, dimensions, type}) honoring per-call override →search_embedding_columnconfig →'embedding'default; throwsUnknownEmbeddingColumnErrorwith the list of registered names on miss.isCacheSafe(resolved, cfg)compares the full embedding SPACE (provider + dimensions + name) against cfg's default so a repointedembeddingbuiltin doesn't serve OpenAI-shaped cache rows.validateResolvedColumn(descriptor)re-validates hand-rolled descriptors that bypass the registry (internal-SDK passthrough) so the SQL-injection escape hatch through the descriptor field is closed. Consumed byhybridSearch,gateway.embedQuery(text, {embeddingModel, dimensions}),cosineReScore, and thequeryMCP op (per-callembedding_columnparam). Pinned bytest/search/embedding-column.test.ts(prototype-pollution, descriptor passthrough, env-only Postgres install, empty-brain coverage gate, cache-space comparison).src/core/search/rerank.ts— the call-site abstraction.applyReranker(query, results, opts)slots betweendedupResults()andenforceTokenBudget()insrc/core/search/hybrid.ts. Slicesopts.topNIn(default 30) by current RRF order, sends togateway.rerank(), reorders byrelevanceScoredesc, appends the un-reranked tail unchanged (recall protection). Fail-open on everyRerankError.reason: any error logs vialogRerankFailureand returns the input array unchanged. Stampsrerank_scoreonto reordered items so downstream telemetry sees the new ordering signal.topNOut: nullis the explicit "don't truncate" signal — semantically distinct fromundefined("fall through to mode bundle"). Test seam:opts.rerankerFnstubsgateway.rerankwithout the network.src/core/search/return-policy.ts(default OFF) — intent-aware adaptive return-sizing. Pure, dependency-light module that trims the final ranked candidate set to an intent-driven cap instead of returning the full top-K.entityintent gets a tight cap;temporal/event/generalget a recall-preserving cap. AminKeepfailsafe (≥1) guarantees a human never gets a silent blank when candidates exist. WHY a cap, not a score-cliff detector: PrecisionMemBench instrumentation (gbrain-evals) measured the rank1→rank2 RRF gap is ~identical whether rank-1 is correct (0.602) or wrong (0.569) — mechanical decay, not a separatrix; rank-1 is right in 94% of single-answer cases, so "return a tight set" is the whole win and cliff-cutting just adds noise. ExportsAdaptiveReturnConfig,DEFAULT_ADAPTIVE_RETURN(frozen:enabled=false,entityMax=2,otherMax=6,minKeep=1),AdaptiveReturnDecision({applied, intent, cap, kept, total}),AdaptiveReturnInput(boolean | Partial<AdaptiveReturnConfig> | undefined),adaptiveReturnFromConfig(cfg),resolveAdaptiveReturn(perCall, fromConfig)(defaults → config → per-call merge),adaptiveReturnEnabled(...)(cache-skip gate check),applyAdaptiveReturn(results, intent, cfg)(the trim). Config knobs (DB or file plane):search.adaptive_return(master switch),search.adaptive_return_entity_max,search.adaptive_return_other_max,search.adaptive_return_min_keep(each clamped ≥1). Wired intohybridSearchAFTERapplyReranker, BEFORE thelimitslice, and ONLY on the first page (offset===0) — paginating a confidence-gated set is incoherent, so paginated calls fall through to the fixed limit. Stamps the decision ontoHybridSearchMeta.adaptive_returnforgbrain search --explain.hybridSearchCachedSKIPS the cache when the gate is on (a trimmed set must not be served to a gate-off lookup and vice versa).SearchOpts.adaptiveReturn+HybridSearchMeta.adaptive_returndeclared insrc/core/types.ts. Agent-facing: thequeryop (src/core/operations.ts) exposes anadaptive_returnboolean param whose description instructs the agent WHEN to set it (single-answer → on; breadth/exploration → off; passlimit:1for a hard single-answer cap), threaded intohybridSearchCached— end users never touch the config knob; their agent decides per query (same pattern assalience/recency). Pinned bytest/search/return-policy.test.ts(mechanism) +test/search/query-op-adaptive-return.test.ts(agent surface: param exists + description teaches both directions + the never-empty contract).src/core/search/autocut.ts(default ON in reranked modes) — Weaviate-style autocut: score-discontinuity result-sizing on the cross-encoder rerank separatrix.applyAutocut(results, scoreOf, cfg)normalizes the reranker scores, finds the largest consecutive gap, and cuts there when it clearsjumpRatio(default 0.20); robust to unsorted provider output (cuts on a sorted copy, keeps items in INPUT order via a score threshold), guardstop<=0/non-finite, never returns empty, and no-ops when <2 results carry a finitererank_score(covers the reranker fail-open path). WHY rerank_score and NOT RRF/cosine: gbrain measured (seereturn-policy.ts) that the RRF rank1→rank2 gap is ~flat whether rank-1 is right or wrong — not a separatrix; the cross-encoder score IS. So autocut runs ONLY where the reranker ran (the floor reachesbalanced+tokenmax;conservativeis a documented no-op). ExportsAutocutConfig,DEFAULT_AUTOCUT(frozen:enabled=true, jumpRatio=0.20, minKeep=1),AutocutDecision({applied, signal:'rerank'|'none', cut, kept, total, gapRatio}),AutocutInput,autocutFromConfig,resolveAutocut,applyAutocut. Cache-key integration (clean path, not the adaptive-return cache-skip hack): enable+sensitivity flow throughModeBundle→ResolvedSearchKnobs→knobsHashexactly likegraph_signals.mode.tsaddsautocut/autocut_jump(conservative false, balanced/tokenmax true@0.20) AND setsreranker_top_n_in = searchLimitfor reranked modes (so the reranker scores the full returned set; there is no un-scored tail for autocut to wrongly drop — closes the load-bearing recall finding).KNOBS_HASH_VERSIONis 8 (title_boost claimed 7; autocut appends 8 — one-time global cache cold-miss on upgrade). Wired intohybridSearchAFTER adaptive-return, BEFORE the limit slice, first page only; emitsHybridSearchMeta.autocut. BOTH the cache-missfinalMetaand cache-HITcachedMetarebuilds carryautocut+adaptive_return+mode+embedding_column. Preserves alias-hop exact matches:applyAutocuttakes an optionalpreservepredicate; hybrid passesr => r.alias_hit === trueso a canonical page injected byapplyAliasHopafter reranking (norerank_score) is never cut. Agent surface:queryopautocutboolean (ceiling override —falseforces full top-K);SearchOpts.autocut;--explainshows per-resultrerank_score,formatAutocutSummaryrenders the decision when search meta is threaded;gbrain search modesattribution; metric glossaryautocut.signal/autocut.gap_ratio. Config:search.autocut,search.autocut_jump. Default-ON backed by an in-repo eval gate —test/search/autocut-eval.test.ts(alsobun run eval:autocut) measures precision-lift-without-recall-regression over labeled qrels fixtures with modeled cross-encoder distributions (no API key, no sibling repo; runs in CI): mean precision 0.33→0.94, recall 1.00→0.95, ZERO recall regression on enumeration queries. Env-overridable floors. Pinned bytest/search/autocut.test.ts(pure-fn),test/search/query-op-autocut.test.ts(agent surface),test/search/autocut-integration.serial.test.ts(IRON-RULE behavioral viarerankerFnDI seam: cliff trims, flat doesn't, no-reranker no-ops,autocut:falseceiling, composes with adaptive-return),test/search/autocut-eval.test.ts(the precision/recall gate), and the v=8 knobsHash assertions intest/search-mode.test.ts.src/core/ai/recipes/voyage.ts— Voyage AI openai-compatible recipe. Declareschars_per_token=1+safety_factor=0.5so the gateway pre-splits Voyage batches at a 60K-character budget (50% of 120K-token cap with the dense-tokenizer ratio), avoiding the backfill loop where tiktoken-grounded budgeting undercounted Voyage's actual token usage. Declaresmultimodal_models: ['voyage-multimodal-3']so the gateway rejects text-only Voyage models pointed at the multimodal endpoint with a clearAIConfigErrorinstead of waiting for Voyage's HTTP 400. Recipe docstring at:7-16names the seven hosted flexible-dim models that acceptoutput_dimension(voyage-4-large,voyage-4,voyage-4-lite,voyage-3-large,voyage-3.5,voyage-3.5-lite,voyage-code-3) and notesvoyage-4-nanois the open-weight variant fixed at 1024-dim that does NOT accept the parameter (negative regression assertion intest/ai/gateway.test.ts:dimsProviderOptionsreturnsundefinedforvoyage-4-nano).voyage-code-3is the recommended embedding model for gstack per-worktree code brains (Topology 3 indocs/architecture/topologies.md); discoverability surfaces: decision-tree branch indocs/integrations/embedding-providers.md, Topology 3 "Recommended embedding model" subsection, runtime nudge fromgbrain reindex --codeagainst non-code-tuned models. Recipe-shape regression pinned bytest/ai/voyage-code-3-recipe.test.ts.src/core/ai/recipes/anthropic.ts— Anthropic recipe (chat + expansion touchpoints). Canonical id isclaude-sonnet-4-6(no date suffix); a reverse aliasclaude-sonnet-4-6-20250929 → claude-sonnet-4-6keeps stale user configs working (rescuesfacts.extraction_modelandmodels.dream.synthesize). Recipe-shape regression pinned bytest/anthropic-model-ids.test.ts.src/core/model-pricing.ts— single source of truth for paid-cloud CHAT/completion model pricing (USD per 1M tokens, input | output).CANONICAL_PRICINGis aprovider:model-keyed table (Anthropic Opus 4.8/4.7/4.6$5/$25, Sonnet 4.6$3/$15, Haiku 4.5$1/$5both dateless + dated, plus OpenAI / Google / Together / DeepSeek panel models).canonicalLookup(modelId)resolves bare (claude-opus-4-8), colon (anthropic:claude-opus-4-8), and slash (anthropic/...) forms — bare ids default to theanthropic:provider; nested OpenRouter ids (openrouter:anthropic/...) intentionally MISS so OpenRouter markup isn't repriced as the inner vendor. Every other chat-pricing table is a DERIVED view of this one (NOT a hand-copied duplicate), so cross-table price drift is structurally impossible. Embeddings live separately inembedding-pricing.ts(different unit). Pinned bytest/model-pricing.test.tswhose drift guard asserts each derived view equals canonical and that the cross-modal panel models are all present.src/core/anthropic-pricing.ts— bare-keyed Anthropic VIEW ofmodel-pricing.ts(theanthropic:canonical entries with the prefix stripped). Kept distinct because many callers look up by bare Claude id and becauseestimateMaxCostUsd(modelId, inTokens, maxOutTokens)carries the null-on-miss contract the dream-cycle budget gate depends on (non-Anthropic ids return null → caller warnsBUDGET_METER_NO_PRICINGonce and runs unbounded).estimateMaxCostUsdroutes bare/colon/slash ids throughsplitProviderModelId. Do NOT hand-edit prices here — the map is derived from canonical, so it cannot drift.ANTHROPIC_PRICINGis consumed bybudget/budget-tracker.ts,minions/batch-projection.ts, andcycle/budget-meter.ts.src/core/takes-quality-eval/pricing.ts— fail-closed budget pricing foreval takes-quality run --budget-usd N.MODEL_PRICINGis a curatedprovider:modelallowlist (default panel + likely overrides) whose VALUES are derived frommodel-pricing.tsviacanonicalLookup; an allowlisted id missing from canonical throws at module load. Schema is{input_per_1m, output_per_1m}. A model NOT on the allowlist aborts the run with an actionable error rather than guessing (distinct fromcross-modal-eval/runner.ts, which silently estimates zero on unknown models — both now source numbers from canonical).src/core/budget/budget-tracker.ts— keystone primitive for the brainstorm cost-cathedral wave. One typed error (BudgetExhaustedwithreason: 'cost' | 'runtime' | 'no_pricing'), one schema-stable audit JSONL at~/.gbrain/audit/budget-YYYY-Www.jsonl. Contracts:record()throws when cumulative spend exceeds cap (the cap is a real ceiling, not a suggestion);reserve()hard-fails withreason: 'no_pricing'whenmaxCostUsdis set AND the model is missing from pricing maps (warn-once preserved when cap is unset);extractUsageFromError(err, fallback)returnserr.usagewhen the SDK provides it, else the pessimistic fallback (caller passesmaxOutputTokens, not the optimistic pre-call estimate).onExhausted(cb)fires once synchronously BEFORE the throw propagates so callers can persist checkpoints. Replaces three parallel copies (inline brainstorm class, cycle/budget-meter, eval-contradictions). Adapts the oldBudgetMeter(public shape preserved +schema_version: 1stamped on every dream-budget audit line). Pinned by 18 unit cases.src/core/audit-week-file.ts— single source of truth for ISO-week audit JSONL filename math. ExportsisoWeek(d),isoWeekFilename(prefix, now?),resolveAuditDir()(honorsGBRAIN_AUDIT_DIR). Year-boundary correctness pinned by tests at 2020-W53 (the 53-week year), 2025-W01 rolling in from 2024-12-30 (Monday), 2026-W01. Four call sites migrated:src/core/minions/handlers/shell-audit.ts,src/core/facts/phantom-audit.ts,src/core/audit-slug-fallback.ts,src/core/cycle/budget-meter.ts. Each keeps itscompute<X>AuditFilenamethin wrapper for back-compat with existing tests.src/core/ai/gateway.ts:withBudgetTracker— gateway-layer enforcement viaAsyncLocalStorage<BudgetTracker>.withBudgetTracker(tracker, fn)installs the tracker on the module-internal store; everygateway.chat / embed / rerankcall inside the scope auto-composes (reserve before, record in try/finally). Outside-scope calls are budget no-ops. Nested scopes restore the outer tracker on exit.getCurrentBudgetTracker()is the test seam. The chat path uses the pessimistic fallback on error paths; the embed path estimates input tokens from char count × recipe'schars_per_tokenbecause the AI SDK doesn't surface per-batch embed token usage; the rerank path estimates char count of query+docs. Pinned by 6 unit cases.src/core/diarize/payload-fitter.ts— generic fit-arbitrarily-large-items-into-per-call-token-budget utility.'batch'strategy is deterministic token-budgeted chunking with no LLM calls.'summarize'strategy embed-clusters into ceil(items/4) groups via cheap deterministic nearest-neighbor on cosine, Haiku-summarizes each cluster viaPromise.allSettledat parallelism=4. Each Haiku call composes the active BudgetTracker via the AsyncLocalStorage. Quality gate: whensuccess_ratio < min_success_ratio(default 0.75), result is flaggeddegraded: true— the fitter preserves the successful subset; the caller decides whether to surface a partial result or abort.src/core/brainstorm/checkpoint.ts— crash-resilient checkpoint forgbrain brainstormandgbrain lsd. Persists FULL idea bodies (~50KB/run) so resume MERGES pre-crash ideas with post-resume ideas before the judge runs (a resume that produces only second-run output is silent partial output).run_id = sha256(question + profile + sort(close_slugs) + sort(far_slugs)).slice(0,16)— NO embedding bits, stable across embedding-model swaps. Atomic write via.tmp + rename. ONE resume flag (--resume <run_id>covers both failed AND never-attempted crosses);--list-runsprints run_ids mtime-newest-first;--force-resumebypasses the 7-day staleness gate. Cycle purge phase (gbrain dream --phase purge) GCs checkpoints older than 7 days viagcStaleCheckpoints(7). Pinned bytest/e2e/brainstorm-resume.test.ts(20 unit + 3 E2E cases incl. the merge contract).src/core/remediation-checkpoint.ts—doctor --remediatecheckpoint at~/.gbrain/remediation/<plan_hash>.json.plan_hash = sha256(JSON.stringify(sorted recommendation ids)).slice(0,16). Schema-versioned, atomic.tmp + rename.gbrain doctor --remediate --resume <plan_hash>(no arg picks newest matching) loads it and skips completed steps. Mismatched plan_hash refuses with a paste-ready message. Cleared on clean completion. Pinned by 13 unit cases.src/core/model-config.ts— Model-string resolution (the seam every internal LLM call walks through). Four-tier system (ModelTier = 'utility' | 'reasoning' | 'deep' | 'subagent') withTIER_DEFAULTS(utility→haiku-4-5, reasoning→sonnet-4-6, deep→opus-4-7, subagent→sonnet-4-6) andtier?: ModelTieronResolveModelOpts. 8-step resolution chain: cliFlag → deprecated key → config key →models.default→models.tier.<tier>→ env var →TIER_DEFAULTS[tier]→ caller fallback.isAnthropicProvider(modelString)checksprovider:modelprefix ORclaude-bare-id pattern (routes throughsplitProviderModelIdfromsrc/core/model-id.tsso slash-form ids likeanthropic/claude-sonnet-4-6classify correctly).enforceSubagentAnthropic()is the layer-2 runtime guard: whentier === 'subagent'resolves non-Anthropic, it emits a once-per-(source, model)stderr warn AND falls back toTIER_DEFAULTS.subagent(the Anthropic Messages API tool-loop can't run on OpenAI/Gemini)._resetDeprecationWarningsForTest()also clears_subagentTierWarningsEmitted. Pinned bytest/model-config.serial.test.ts.src/core/ai/model-resolver.ts— Recipe-touchpoint validator.assertTouchpoint(recipe, touchpoint, modelId, extendedModels?)takes an optional 4thextendedModels: ReadonlySet<string>: when the modelId is in that set the native-recipe allowlist throw is bypassed (user explicitly opted in via config, so provider rejection surfaces asmodel_not_foundat HTTP call time andgbrain models doctorcatches it earlier). Default code paths with hardcoded model strings MUST NOT passextendedModels— source typos still fail fast (the fail-fast contract for chat + expand + embed stays intact).src/core/ai/gateway.tsextension — module-scoped_extendedModels: Map<providerId, Set<modelId>>registry feedsassertTouchpoint's 4th-arg path.reconfigureGatewayWithEngine(engine)(async, called fromcli.tsafterengine.connect(), before every command exceptCLI_ONLYno-DB commands) re-resolves expansion + chat defaults throughresolveModel()somodels.tier.*andmodels.defaultoverrides apply to both.DEFAULT_CHAT_MODELisanthropic:claude-sonnet-4-6.__setChatTransportForTestsseam mirrors__setEmbedTransportForTestsso tests drivechat()with a stubbed transport.src/core/minions/queue.tsextension —MinionQueue.add()rejectssubagentjobs whosedata.modelresolves viaisAnthropicProvider()to a non-Anthropic provider. Lazy-importsmodel-config.tsto avoid pulling engine types into queue's eager-load surface. Layer 1 of the three-layer subagent provider enforcement (layers 2+3:model-config.ts:enforceSubagentAnthropicruntime fallback +src/commands/doctor.tssubagent_providercheck). Pinned bytest/agent-cli.test.ts.src/commands/models.ts—gbrain models [--json]read-only routing dashboard: prints tier defaults (utility/reasoning/deep/subagent), the resolved value for each (re-walking the resolution chain), every per-task override (11PER_TASK_KEYS:models.dream.synthesize,models.dream.patterns,models.drift,models.auto_think,models.think,models.subagent,facts.extraction_model,models.eval.longmemeval,models.expansion,models.chat,models.dream.synthesize_verdict), the alias map, and a source-of-truth column (default/config: <key>/env: <VAR>).gbrain models doctor [--skip=<provider>] [--json]fires a 1-tokengateway.chat()probe against each configured chat + expansion model and classifies failures into{model_not_found, auth, rate_limit, network, unknown}. Wired intocli.tsdispatch +CLI_ONLYset. A zero-tokenembedding_configprobe runs FIRST, before any chat/expansion probes spend money:probeEmbeddingConfig()readsgetEmbeddingModel()+getEmbeddingDimensions()and (for Voyage flexible-dim models) checksisValidVoyageOutputDim(dims)againstVOYAGE_VALID_OUTPUT_DIMS.ProbeStatusvariant'config'+ optionalfix?: stringonProbeResultsurface a paste-readygbrain config set ...line in human + JSON output; touchpoint label'embedding_config'joins'chat'and'expansion'.src/core/init-embed-check.ts— embedding-key validation atgbrain init.runInitEmbedCheck(opts)runs a config-onlydiagnoseEmbedding(catches a missing key for ANY provider) plus a best-effortliveTestEmbed(1-tokengateway.embed(['probe'], {inputType:'query', abortSignal}), 5sAbortControllertimeout, never throws — catches an invalid/expired key). Loud warning to stderr; init still exits 0 (--no-embeddingis the deferred-setup escape;--skip-embed-check/GBRAIN_INIT_SKIP_EMBED_CHECK=1skip the check). Builds the effective env (process.env+ file-planeopenai/anthropic/zeroentropy_api_keyfromloadConfigFileOnly()+opts.apiKey) and configures the gateway viabuildGatewayConfigbefore diagnose/probe, so the check sees the same keys AND provider base URLs runtime will (no false "missing key" for config.json-keyed users; the probe hits the right endpoint). Init-specific warning text names--no-embedding/--skip-embed-check, not the sync-flavored--no-embed. Wired intoinitPGLite+initPostgresinsrc/commands/init.ts, with the result added to the--jsonenvelope asembedding_check {ok, reason?, live_ok?}. Pinned bytest/init-embed-check.test.ts(hermetic via the gateway embed-transport seam +withEnv).src/core/ai/build-gateway-config.ts—buildGatewayConfig(c: GBrainConfig): AIGatewayConfig, extracted fromsrc/cli.ts(which re-exports it for back-compat). Lets core modules (init-embed-check.ts) reuse it without importing the CLI entrypoint. Single owner of folding file-plane API keys (openai/anthropic/zeroentropy) into the gateway env and threading local-server*_BASE_URLenv vars into base_urls;process.envwins. Pinned bytest/ai/build-gateway-config.test.ts.src/commands/doctor.tsextension —subagent_providercheck (layer 3 of 3). Warns whenmodels.tier.subagentis explicitly set non-Anthropic (message names the bad value + paste-ready fixgbrain config set models.tier.subagent anthropic:claude-sonnet-4-6); also warns whenmodels.defaultwould sneaksubagentinto a non-Anthropic provider via tier inheritance. OK when subagent tier resolves to Anthropic. Tests intest/doctor.test.ts.src/core/skill-trigger-index.ts— Shared loader that unions per-skill SKILL.md frontmattertriggers:with curated RESOLVER.md / AGENTS.md rows fromskillsDirAND the parent dir (preserves the OpenClaw workspace-root layout). UNION semantics: explicit RESOLVER.md rows ADD to frontmatter triggers (don't replace). Dedup keyed on(skillPath, trigger.trim().toLowerCase()). Three consumers fold through this primitive —checkResolvable,runRoutingEvalCli,mounts-cache.composeResolvers— so fixing frontmatter reaches all of them. ExportsloadSkillTriggerIndex(skillsDir): SkillTriggerEntry[],entriesToResolverContent(entries): string(synthesizes a markdown-table resolver string forrunRoutingEval's string-content API),findPrimaryResolverPath(skillsDir): string | null, theFRONTMATTER_SECTIONconstant, and_resetWarnedSkillsForTests. Skip rules: non-directory entries,_*/.*prefixes,conventions/+migrations/subdirs, skills with noSKILL.md(deprecatedinstall/graceful-skipped), notriggers:array, or malformed YAML (warn-once + skip). ReusesparseSkillFrontmatterfromsrc/core/skill-frontmatter.ts(regex-based, not full YAML). Pinned bytest/skill-trigger-index.test.ts(18 hermetic cases). CI gatebun run check:resolver(=bun src/cli.ts check-resolvable --strict --skills-dir skills/) wired intobun run verify.src/core/skill-catalog.ts— host-repo skill catalog backing the MCPlist_skills/get_skillops. Lets a thin MCP client (Codex desktop, Claude Code, Claude Cowork, Perplexity) DISCOVER + FOLLOW the agent repo's fat-markdown skills overgbrain serve— a skill is prose, so "using" one = fetching its body then calling the gbrain MCP tools the server already exposes. Read-scope, NOT localOnly (defensible only via the full mitigation stack): (1) publish gate —assertPublishEnabled(ctx, publishSkills); remote callers requiremcp.publish_skills === true, default-OFF so an upgrade never silently grants existing read tokens host-skill read; local callers (ctx.remote === false) always pass. (2) path confinement —assertSkillNameShaperejects separators/../null/space before any FS access; the clientnameis a manifest LOOKUP KEY (vialoadOrDeriveManifest), never a raw path segment;confineManifestPathdoes realpath + relative-containment +SKILL.md-regular-file check on EVERY entry (defeats poisoned manifest.jsonpath, symlink/..escape). (3) frontmatter allowlist —GetSkillResult.frontmatterprojects a safe subset; privatewrites_to+sourcesdropped. (4) prose-only + 256KB cap (MAX_SKILL_MD_BYTES, envGBRAIN_MAX_SKILL_MD_BYTES), size-checked twice (statSync + UTF-8 byte length). (5) no install_path serve for remote — remote callers useautoDetectSkillsDir(no install-path tier) so a hosted gbrain with no agent repo returnsstorage_error; local callers useautoDetectSkillsDirReadOnly. (6) MCP rate-limiter caps call rate. Config reads honor BOTH planes:readMcpPublishSkills/readMcpSkillsDirprefer the DB plane (engine.getConfig) over the file plane (ctx.config.mcp). Tool-honesty:crossReferenceTools(declared, ctx)splits a skill's declaredtools:intousable_toolsvsunavailable_tools;buildSkillCatalog'sinstructionsenvelope (SKILL_CATALOG_INSTRUCTIONS) carries the "these are prose, follow-then-call-tools" protocol. Skills are host-filesystem repo-global —sourceScopeOpts(ctx)/ctx.brainIddeliberately do NOT apply.buildSkillCatalogis resilient (one malformed/escaping skill is skipped, never throws). Config keys insrc/core/config.ts:GBrainConfig.mcp?: { publish_skills?, skills_dir? }+KNOWN_CONFIG_KEYSentriesmcp.publish_skills/mcp.publish_skills_prompted/mcp.skills_dir+mcp.prefix inKNOWN_CONFIG_KEY_PREFIXES.src/commands/init.tswritesconfig.mcp = { publish_skills: true, ... }for new installs (existing config wins on re-init).src/commands/upgrade.ts:runPostUpgradeadds a one-time consent prompt (gated bymcp.publish_skills_prompted; existing installs stay OFF until owner opts in). Two ops register insrc/core/operations.ts(list_skillswith optionalsectionfilter +cliHints:{name:'skills'};get_skilltakingname+cliHints:{name:'skill', positional:['name']}) and dynamically import this module to avoid the import cycle (skill-catalog statically imports theoperationsarray). Descriptions insrc/core/operations-descriptions.ts(LIST_SKILLS_DESCRIPTION,GET_SKILL_DESCRIPTION,SKILL_CATALOG_INSTRUCTIONS,SKILL_CLIENT_GUIDANCE), pinned bytest/operations-descriptions.test.ts. CLI:gbrain skills/gbrain skill <name>. Pinned bytest/skill-catalog.test.ts,test/skill-catalog-security.test.ts(path-confinement / poisoned-manifest / symlink-escape),test/skill-catalog-transports.test.ts(publish-gate + remote-vs-local) overtest/fixtures/skill-catalog/.src/core/check-resolvable.ts— Resolver validation: reachability, MECE overlap, DRY checks, structured fix objects.CROSS_CUTTING_PATTERNS.conventionsis an array (notability gate acceptsconventions/quality.mdand_brain-filing-rules.md).extractDelegationTargets()parses> **Convention:**,> **Filing rule:**, and inline backtick references. DRY suppression is proximity-based viaDRY_PROXIMITY_LINES = 40.parseResolverEntriesaccepts BOTH the markdown table AND a compact list format (- **skill-name**: trigger1 | trigger2 | trigger3or- skill-name: trigger1 | trigger2); shapes can mix in one file, folded by the multi-resolver merge. Skill name MUST be kebab-lowercase (regex[a-z][a-z0-9-]+) so prose bullets like- **Note**:/- **Convention**:/- **TODO**:don't false-match as skill rows.skillPathis ALWAYS derived asskills/<name>/SKILL.md: an optional→ \skills/path`(or ASCII->) suffix is stripped from the trigger but NOT honored as the path — two consumers (routing-eval.ts:skillSlugFromPath, the manifest lookup) assume the convention; use the table format for non-conventional paths. Multi-trigger rows fan out to one entry per trigger sharing the sameskillPath;checkResolvablededupes so the reachability count counts each skill once. Pinned bytest/check-resolvable.test.ts(11 cases: bold+plain forms, Unicode+ASCII suffix strip, ellipsis filter, empty pipe segments, mixed shapes, prose-bullet rejection) +test/check-resolvable-openclaw-compact.test.ts(8 cases overtest/fixtures/openclaw-compact-resolver/andtest/fixtures/openclaw-mixed-merge/). Tutorial:docs/guides/scaling-skills.md` (three-tier scaling: ~300-skill agent to ~4K tokens/turn from ~25K).src/core/repo-root.ts— SharedfindRepoRoot(startDir?): walks up fromstartDir(defaultprocess.cwd()) looking forskills/RESOLVER.md. Zero-dependency, imported bydoctor.tsandcheck-resolvable.ts; parameterizedstartDirmakes tests hermetic. Read-path / write-path split:autoDetectSkillsDir(shared, read+write-safe) has tier-0$GBRAIN_SKILLS_DIRoperator override ahead of the 4-tier chain.autoDetectSkillsDirReadOnlywraps it with a tier-5 install-path fallback that walks up fromfileURLToPath(import.meta.url)and gates onisGbrainRepoRootso unrelated repos can't false-positive. Read-path callers (doctor,check-resolvable,routing-eval) use the read-only variant; write-path callers (skillpack install,skillify scaffold,post-install-advisory) stay on the shared function so install-from-~can't retarget the bundled gbrainskills/instead of the user's workspace.SkillsDirSourcevariants'env_explicit','install_path';AUTO_DETECT_HINT_READ_ONLYdocuments the extra tier. The--fixsafety gate indoctor.ts+check-resolvable.tsrefuses auto-repair whendetected.source === 'install_path'.src/commands/check-resolvable.ts— Standalone CLI wrapper overcheckResolvable(). ExportsparseFlags,resolveSkillsDir,DEFERRED,runCheckResolvable. Exit rule: 1 on any issue (warnings OR errors), stricter than doctor'sokflag. Stable JSON envelope{ok, skillsDir, report, autoFix, deferred, error, message}— same shape on success and error.--fixrunsautoFixDryViolationsBEFOREcheckResolvable(same ordering as doctor).scripts/skillify-check.tssubprocess-callsgbrain check-resolvable --json(cached per process) and fails loud on binary-missing. AGENTS.md workspaces resolve natively (seesrc/core/resolver-filenames.ts).DEFERRED[]is empty. Resolver lookup is the multi-file merge insrc/core/check-resolvable.ts— entries collected from everyRESOLVER.md/AGENTS.mdacross the skills dir AND its parent, deduped byskillPath(first occurrence wins). UsesautoDetectSkillsDirReadOnlysocd ~ && gbrain check-resolvablefinds bundled skills via the install-path fallback;--fixcarries the same install-path safety gate (refuses to write whendetected.source === 'install_path').src/core/resolver-filenames.ts— central list of accepted routing filenames (RESOLVER.md,AGENTS.md). Shared byfindRepoRoot,check-resolvable, and skillpack install so every code path walks the same fallback chain.src/commands/skillify.ts+src/core/skillify/{generator,templates}.ts—gbrain skillify scaffold <name>creates all stubs for a new skill: SKILL.md, script, tests, routing-eval.jsonl, resolver entry, filing-rules pointer.gbrain skillify check <script>runs the 10-step checklist (LLM evals, routing evals, check-resolvable gate, filing audit) against a candidate skill before it lands.src/commands/skillify-check.ts—gbrain skillpack-checkagent-readable health report. Exit 0/1/2 for CI gating; JSON for debugging. Wrapscheck-resolvable --json,doctor --json, and migration ledger into one payload. Required item 12 (brain_first_compliance) callsanalyzeSkillBrainFirst()on the candidate SKILL.md; exits 1 when the verdict ismissing_brain_first(external-lookup pattern present, no callout, nobrain_first: exempt). The scaffold path insrc/core/skillify/templates.tspre-inserts the canonical Convention callout into new SKILL.md files so freshly-scaffolded skills pass item 12.src/commands/book-mirror.ts—gbrain book-mirror --chapters-dir <path> --slug <slug> [flags]. Submits N read-only subagent jobs (one per chapter;allowed_tools: ['get_page', 'search']), waits for all viawaitForCompletion, reads each child'sjob.result, assembles two-column markdown CLI-side, writes a single operator-trustput_pagetomedia/books/<slug>-personalized.md. Trust narrowing happens at the tool-allowlist layer (subagents can't call put_page) so untrusted EPUB content can't prompt-inject any people page. Cost-estimate prompt before launching; refuses to spend in non-TTY without--yes. Per-chapter idempotency keys (book-mirror:<slug>:ch-<N>) for retry-friendly re-runs. Partial-failure: assembles completed chapters + a## Failed chapterssection. Pinned bytest/book-mirror.test.ts(9 cases).src/commands/skillpack.ts+src/core/skillpack/{bundle,scaffold,reference,migrate-fence,scrub-legacy,harvest,harvest-lint,copy,apply-hunks,diff-text,installer}.ts— managed-block install model retired;install/uninstallremoved (exit non-zero with a hint to the replacement). Surface:scaffold(one-time additive copy viacopyArtifactsincopy.ts; refuses to overwrite; partial-state fills missing paired sources declared in SKILL.md frontmattersources:),reference(read-only diff lens +--apply-clean-hunkstwo-way auto-apply via pure-JS unified-diff parser/applier inapply-hunks.ts+diff-text.ts),migrate-fence(one-shot strip of legacy fence; cumulative-slugs receipt → row-parsing fallback; preserves rows verbatim as user-owned routing),scrub-legacy-fence-rows(opt-in row cleanup with skill-present + non-empty-triggers gate),harvest(host→gbrain inverse with symlink-reject + canonical-path containment via avalidateUploadPath-style gate + default-on privacy linter inharvest-lint.tsagainst~/.gbrain/harvest-private-patterns.txtplus built-in a built-in fork-name pattern + email + Slack-channel patterns; rollback on match). Paired-source declarations live in each SKILL.md's frontmattersources:array (validated byloadSkillSourcesinbundle.ts).autoDetectSkillsDir(insrc/core/repo-root.ts) has acwd_walk_uptier ahead of~/.openclaw/workspace($OPENCLAW_WORKSPACEprecedence preserved).gbrain skillpack check --strictexits non-zero on drift (CI gate); top-levelgbrain skillpack-checkkeeps exit-1-on-issues for cron. Companion editorial skillskills/skillpack-harvest/SKILL.mddrives the genericization checklist. Doc:docs/guides/skillpacks-as-scaffolding.md. Test coverage acrosstest/skillpack-{copy,scaffold,reference,reference-apply,apply-hunks,migrate-fence,scrub-legacy,harvest,harvest-lint,frontmatter-sources}.test.ts+ 9-case E2E intest/e2e/skillpack-flow.test.ts.installer.ts+test/skillpack-install.test.tssurvive —gbrain skillpack diffstill usesdiffSkillfrom there.src/core/skillpack/{manifest-v1,tarball,state,remote-source,trust-prompt,bootstrap-display,scaffold-third-party,registry-schema,registry-client,rubric,doctor,init-scaffold,pack-publish,endorse,audit}.ts+examples/skillpack-reference/+docs/skillpack-anatomy.md+scripts/build-skillpack-anatomy.ts— third-party skillpack ecosystem.gbrain skillpack scaffold <owner/repo|https-url|./tgz|./local-dir>resolves the spec viaclassifySpec, fetches through SSRF-hardenedgit-remote.ts(git) or extracts the tarball into~/.gbrain/skillpack-cache/<host>/<owner>/<repo>/<sha>/, validatesskillpack.json(api_versiongbrain-skillpack-v1), checksgbrain_min_version, surfaces a TOFU first-install identity-confirm prompt (author + source + pinned commit + tarball SHA + tier; non-TTY requires--trust), records the pin in machine-owned~/.gbrain/skillpack-state.json(schemagbrain-skillpack-state-v1, atomic.tmp + rename,isAlreadyTrustedskips re-prompt on author+pin match), runs throughenumerateScaffoldEntries→copyArtifacts(one-time additive, refuses to overwrite), then DISPLAYSrunbooks/bootstrap.mdWITHOUT executing (deliberately does not auto-execute). Registry catalog atgarrytan/gbrain-skillpack-registrysplit intoregistry.json(PR-able,gbrain-registry-v1) +endorsements.json(Garry-only overlay,gbrain-endorsements-v1);effectiveTiermerges.registry-client.tsfetches both viaIf-None-Matchetag with 1h soft-TTL + stale-fallback (originsfresh_fetch | cache_warm | cache_soft_stale | cache_hard_stale); hard-fail only on no-cache + no-network. CLI:gbrain skillpack {search,info,registry,doctor,init,pack,endorse}. Doctor walksSKILLPACK_RUBRIC_V1(10 binary dimensions: 5 required CORE — manifest_valid, skills_have_skill_md, routing_evals_present ≥5 intents, skills_have_unique_triggers MECE, changelog_present_and_current — and 5 quality BADGES — unit_tests_present, e2e_tests_present, llm_eval_present ≥3 cases, bootstrap_runbook_present, license_present); tier eligibility:endorsedneeds all 10,communityneeds core + ≥3 badges,experimentalneeds core only,blockedwhen any core fails.--quick~5s structural sweep;--fix --yesauto-scaffoldsauto_fixable: truedimensions and refuses to overwrite files whose mtime is newer thanskillpack.json.gbrain skillpack init <name>lands 11 files (skillpack.json, SKILL.md, routing-eval.jsonl, test/example.test.ts, e2e/example.e2e.test.ts, evals/example.judge.json, runbooks/{bootstrap,uninstall,upgrade-template}.md, CHANGELOG, README, LICENSE); freshly-init'd scores 10/10;--minimalskips test/e2e/evals.gbrain skillpack packpacks a deterministic tarball via GNU tar (--sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner+GZIP=-n+TZ=UTC); refuses ontier_eligibility === 'blocked'. Extract caps (5000 files / 100MB total / 1MB per file / 255-char paths / 100:1 compression ratio); rejects symlinks/hardlinks/devices/FIFOs.gbrain skillpack endorse <name> [--tier ...] [--push] [--dry-run]runs in a clone of the registry repo: validates the pack inregistry.json, mutatesendorsements.jsonvia pureapplyEndorsement, stable-key-orders the write, commitsendorse: <name> -> <tier>, optionally pushes. JSONL audit at~/.gbrain/audit/skillpack-YYYY-Www.jsonl(ISO-week rotated, honorsGBRAIN_AUDIT_DIR).examples/skillpack-reference/is a 10/10 reference pack pinned bytest/e2e/skillpack-third-party.test.ts.docs/skillpack-anatomy.mdauto-generated viascripts/build-skillpack-anatomy.ts(--checkfor CI drift). CLI dispatch insrc/commands/skillpack.tsdisambiguates third-party (contains/,://,.tgz) from bundled-skill kebab; kebab routes bundled-first, registry-fallback. Tests:test/skillpack-{manifest-v1,tarball,state,remote-source,trust-prompt,registry-schema,registry-client,rubric,doctor,init-scaffold,pack-publish,endorse,audit,scaffold-third-party}.test.ts+test/e2e/skillpack-third-party.test.ts. Spec atdocs/designs/SKILLPACK_REGISTRY_V1_SPEC.md.src/core/archive-crawler-config.ts— safety gate for thearchive-crawlerskill. Refuses to run unlessarchive-crawler.scan_paths:is explicitly set in the brain repo'sgbrain.yml. Mirrors the storage-config.ts parsing pattern (sibling file, separate concern from storage tiering).loadArchiveCrawlerConfig(repoPath)throwsArchiveCrawlerConfigError(missing_section | empty_scan_paths | invalid_path | parse_error).normalizeAndValidateArchiveCrawlerConfigrejects relative paths and..traversal;~is expanded; trailing-slash normalized for unambiguous prefix matching.isPathAllowed(candidate, config)is the runtime per-file gate (scan_paths prefix-match with directory-boundary correctness; deny_paths overrides). Pinned bytest/archive-crawler-config.test.ts(19 cases).test/helpers/cli-pty-runner.ts— generic real-PTY harness (~470 lines) using pureBun.spawn({terminal:})(Bun 1.3.10+; engines.bun pin in package.json). Generic primitives only, no plan-mode orchestrators. ExportslaunchPty,resolveBinary,stripAnsi,parseNumberedOptions,optionsSignature,isNumberedOptionListVisible,isTrustDialogVisible. Self-tests intest/cli-pty-runner.test.ts(24 cases).src/core/skill-manifest.ts— parser forskill-manifest.jsonrecords. Used by skillpack installer to detect drift between the shipped bundle and the user's local edits, so updates merge instead of overwriting.src/commands/routing-eval.ts+src/core/routing-eval.ts—gbrain routing-evalcatches user phrasings that route to the wrong skill. Readsskills/<name>/routing-eval.jsonlfixtures ({intent, expected_skill, ambiguous_with?}). Structural layer runs incheck-resolvableby default (zero API cost).--llmis a placeholder for a future LLM tie-break layer; today it emits a stderr notice and runs structural only. UsesautoDetectSkillsDirReadOnlyand the same multi-file resolver merge ascheck-resolvable, so on OpenClaw layouts (skills/RESOLVER.md+../AGENTS.md) all three commands see the same trigger index. RESOLVER.md rows carry the full frontmattertriggers:arrays so the structural matcher sees realistic phrasings; ambiguous-fixture annotations cover deliberate skill chains likeenrich → article-enrichment.src/core/filing-audit.ts+skills/_brain-filing-rules.json— Check 6 ofcheck-resolvable. Parseswrites_pages:/writes_to:frontmatter on skills and audits their filing claims against the filing-rules JSON (error severity). InternalparseFrontmatteris a thin wrapper over the sharedsrc/core/skill-frontmatter.tsparser so both filing-audit and skill-brain-first read the same shape (tools?,triggers?,brain_first?: 'exempt', typedbrain_first_typo) from one source of truth.src/core/skill-frontmatter.ts— shared content-based SKILL.md frontmatter parser. Recognizes thebrain_first: 'exempt'declarative opt-out and surfaces near-miss declarations (brain-first,BrainFirst, quoted values, unknown values) as a typedbrain_first_typofield so doctor can emit a paste-ready hint rather than fail silently. Single canonical form: snake_casebrain_first: exempt, lowercase, unquoted.src/core/skill-brain-first.ts— pure analyzer.analyzeSkillBrainFirst(skillPath, content): SkillBrainFirstResultwalks the compliance ladder for every SKILL.md: (1) absent external-lookup pattern →no_external; (2)brain_first: exemptfrontmatter →exempt_frontmatter; (3) canonical> **Convention:** see [conventions/brain-first.md](...)callout →compliant_callout; (4) explicit## Phase 1: Brainheading →compliant_phase; (5) firstgbrain search/query/get_pagereference precedes first external pattern in the BODY (frontmatter stripped) →compliant_position; (6) elsemissing_brain_firstwarn. External pattern set: word-boundary regex overweb_search,web_fetch,exa,perplexity,happenstance,crustdata,captain_api,firecrawl. Position scan is BODY-ONLY so atools: [web_search]frontmatter declaration doesn't false-flag the skill. The 40-nameFORMERLY_HARDCODED_EXEMPTlist is preserved so doctor can emit a "this used to be auto-exempt, declarebrain_first: exemptif still appropriate" hint. Consumed by 3 surfaces: doctor check, skillify-check item 12, dry-fix MISSING_RULE_PATTERNS.src/core/skill-fix-gates.ts— shared safety primitives extracted fromdry-fix.ts.getWorkingTreeStatus(file)3-state ('clean' | 'dirty' | 'not_a_repo');isInsideCodeFence(content, offset);findAfterH1Paragraph(content)(canonical insertion offset for the auto-inserted Convention callout). Both REPLACE expanders (DRY violations) and the INSERT expander (MISSING_RULE_PATTERNS) consume from here so the install-path refusal and dirty-tree gates apply uniformly.src/core/dry-fix.tsre-exports for back-compat with existing call sites.src/core/audit-skill-brain-first.ts— snapshot+diff JSONL audit at~/.gbrain/audit/skill-brain-first-YYYY-Www.jsonl(ISO-week rotated, honorsGBRAIN_AUDIT_DIRvia sharedresolveAuditDir()).recordBrainFirstRun(results)reads the previous snapshot at~/.gbrain/audit/skill-brain-first-snapshot.json, diffs against current results, writes transition events (detected | resolved | fixed) one line per change, then atomically overwrites the snapshot via.tmp + rename. Transition-only writes — a stable brain produces 0 audit lines per doctor run.readRecentBrainFirstEvents(days)is the readback path for the futureskill_brain_first_trenddoctor check. Snapshot file is last-writer-wins under concurrent doctor runs; subsequent runs reconcile.src/core/dry-fix.ts—gbrain doctor --fixengine.autoFixDryViolations(fixes, {dryRun})rewrites inlined rules to> **Convention:** see [path](path).callouts via three shape-aware expanders (bullet / blockquote / paragraph). Five guards: working-tree-dirty (getWorkingTreeStatus()3-state'clean' | 'dirty' | 'not_a_repo'), no-git-backup, inside-code-fence, already-delegated (40-line proximity, consistent with detector), ambiguous-multi-match, block-is-callout.execFileSyncarray args (no shell, no injection surface). EOF newline preserved. Safety primitives are insrc/core/skill-fix-gates.ts(back-compat re-exports preserved).MISSING_RULE_PATTERNSINSERT pattern type lives alongside REPLACE patterns — same auto-fix entry point + git-safety gates, but places a canonical callout at a target offset (after-h1-paragraphonly). First INSERT pattern isbrain_first, auto-inserting> **Convention:** see [conventions/brain-first.md](../conventions/brain-first.md) for the lookup chain (search → query → get_page → external).on any flagged SKILL.md whose verdict ismissing_brain_first. Idempotent — re-runs detect the existing callout and skip.src/core/backoff.ts— Adaptive load-aware throttling: CPU/memory checks, exponential backoff, active hours multiplier.src/core/retry.ts— canonical retry primitive for transient connection errors. ExportswithRetry<T>(fn, opts)execution wrapper +BULK_RETRY_OPTSconstant ({maxRetries:3, delayMs:1000, delayMaxMs:10000, jitter:'decorrelated'}, tuned for Supabase Supavisor's 5-10s circuit-breaker recovery) +BATCH_AUDIT_SITEStyped const (closed enum of every audit-emission site) +resolveBulkRetryOpts(env)(readsGBRAIN_BULK_MAX_RETRIES/GBRAIN_BULK_RETRY_BASE_MS/GBRAIN_BULK_RETRY_MAX_MSwith>=0validation, throws on bad input with a paste-ready hint) +abortableSleep(ms, signal?)+RetryAbortError(tagged error for clean shutdown) +computeNextDelay()(pure-fn for 3 jitter modes:'none','full','decorrelated'). The execution wrapper is consumed bypostgres-engine.ts+pglite-engine.tsbatch primitives (addLinksBatch/addTimelineEntriesBatch/upsertChunks) so every caller inherits retry as part of the data-primitive's contract. CI guardscripts/check-no-double-retry.shfails the build onwithRetry(...engine.batch...)patterns (prevents 3×3=9 retry amplification);scripts/check-batch-audit-site.shvalidates every string-literalauditSite: '...'against the closedBATCH_AUDIT_SITESenum. Decorrelated jitter (AWS-style:uniform(base, prevDelay*3)capped atdelayMaxMs) —'full'jitter allowed near-zero retries that re-hit the recovering breaker.WithRetryOptshas an optionalreconnect?: () => Promise<void>callback awaited in the catch branch AFTERisRetryableConnErrorclassification but BEFORE the inter-attempt sleep — lets engine-level callers rebuild a dead pool/singleton between attempts.PostgresEngine.batchRetryinjects() => this.reconnect()(the race-safe_reconnectingguard kicks in). Fail-loud: a reconnect throw PROPAGATES as the new error, replacing the symptomatic "No database connection".onRetrycallbacks are awaited (sync arrows work identically; async callbacks correctly delay the sleep). Pinned bytest/core/retry.test.ts(37 cases),test/core/retry-stress.slow.test.ts(5 cases, 100 batches × 30% blip rate, asserts zero row loss),test/core/retry-reconnect.test.ts(5 cases),test/e2e/db-singleton-shared-recovery.test.ts(3 DB-gated cases).src/core/process-watchdog.ts(#1633) — out-of-band hard-deadline killer forgbrain sync. A spinning sync (synchronous catastrophic-regex / ReDoS in pack link-inference) STARVES the main event loop, so the existing SIGTERM handler (process-cleanup.ts),--timeoutsetTimeout, and abort-flag checks can't fire — the process becomes unkillable-by-SIGTERM and, under cron, orphans pile up for 24h+ (the reported incident).installProcessWatchdog({deadlineMs, graceMs?, label?, heartbeatMs?, onWarn?}): WatchdogHandlespawns a Bunworker_threadsWorker vianew Worker(code, {eval: true, workerData})— its own OS thread + event loop fires even while main is in an unyielding sync loop. AtdeadlineMsitprocess.kill(process.pid, 'SIGTERM')(clean-shutdown chance if responsive); atdeadlineMs+graceMsprocess.kill(process.pid, 'SIGKILL')(uncatchable — guaranteed death under starvation). Signaling SELF has NO PID-reuse footgun (current PID never reused while alive — the reason the detached-child-watches-parent design was rejected).eval: truebakes the worker body into thebun build --compilebinary with no separate-file embedding. Empirically validated on Bun 1.3.13 (worker timer + SIGKILL killed awhile(true){}-starved process).handle.dispose()(clean-exitfinally)worker.terminate()s it;unref()'d so it never keeps the process alive. PurewatchdogDecision(elapsedMs, deadlineMs, graceMs) → 'wait'|'sigterm'|'sigkill'extracted for unit tests. OptionalheartbeatMsemits periodic[<label>] parent alive Ns, hard-kill in ~Mslines (visible in cron logs even under starvation — the diagnosis surface). Fallback: ifnew Workerthrows, degrades to an in-process timer with a loud warning that it can't fire under starvation. Reusable beyond sync (autopilot/cycle are follow-up adopters). Pinned bytest/process-watchdog.test.ts(pure decision matrix + handle contract) +test/process-watchdog.serial.test.ts(Bun-pinned spawn integration: a starved harness process IS killed ~deadline+grace, a no-watchdog control does NOT self-exit, clean dispose never kills). Wired intosrc/cli.tssync dispatch BEFOREconnectEngine(so a connect-phase hang is bounded too); deadline resolved byresolveSyncHardDeadlineinsync.ts(precedence:--no-hard-deadline>--hard-deadline>--timeout(non---all) >GBRAIN_SYNC_MAX_RUNTIME_SECONDSenv > non-TTY default 3600s > none).src/core/audit/batch-retry-audit.ts— JSONL audit primitive for batch-retry events, built onaudit-writer.ts. Schema:{ts, site, batch_size, attempt, outcome: 'success' | 'exhausted', delay_ms, error_message_summary, error_code?}. Privacy: NEVER logs slugs / page IDs / content (mirrorsshell-audit.ts).logBatchRetryfires per successful retry recovery;logBatchExhaustedfires when retries exhaust and rows are lost.readRecentBatchRetryEvents(hours=24)returns{events, corrupted_lines, files_scanned, files_unreadable}— corruption + permission errors surface to doctor, not silently swallowed.pruneOldBatchRetryAuditFiles(daysToKeep=30)deletes old files, called fromgbrain dream --phase purge. File:~/.gbrain/audit/batch-retry-YYYY-Www.jsonl(honorsGBRAIN_AUDIT_DIR).summarizeErrorroutes error messages through the sharedredactConnectionInfohelper fromsrc/core/audit/redact-connection-info.tsBEFORE truncation so DSNs / hostnames / credentials / IPv4 octets can't leak into operator-shared JSONL dumps. Pinned bytest/audit/batch-retry-audit.test.ts(12 cases) +test/audit/batch-retry-redaction.test.ts(3 privacy regressions).src/core/audit/lock-renewal-audit.ts— JSONL audit primitive for per-job lock-renewal faults. Sibling ofbatch-retry-audit.ts, built onaudit-writer.ts. Four outcomes:failure(single renewLock throw, counter incremented),success_after_failure(recovery; emits the recovery count),gave_up(time-based deadline exceeded; abort fired),executeJob_rejected(the second unhandledRejection vector — the storedexecuteJob(...).finally(...)promise itself rejected, e.g. failJob threw during the same DB outage). Schema:{ts, job_id, job_name, attempt?, outcome, error_message_summary?, error_code?}. Privacy: NEVER logslock_tokenorjob.data; error summaries route throughredactConnectionInfoBEFORE truncation. Defense-in-depth: every audit call inside the lock-renewal tick's catch block is wrapped in its own inner try/catch so a misbehaving audit-writer can't re-introduce the unhandledRejection bug class.readRecentLockRenewalEvents(hours=24)walks current + previous ISO week with corrupted-line tolerance.pruneOldLockRenewalAuditFiles(daysToKeep=30)is ready for future dream-cycle purge wiring. File:~/.gbrain/audit/lock-renewal-YYYY-Www.jsonl. Pinned bytest/audit/lock-renewal-audit.test.ts(11 cases).src/core/audit/redact-connection-info.ts— Shared pure helper.redactConnectionInfo(text: string): stringstrips Postgres connection info before any audit JSONL write:postgres:///postgresql://URLs,host=foo,user=foo,password=foo,pwd=foo, IPv4 octets — each match becomes<REDACTED:kind>. Negative-lookbehind/lookahead[\w.@-]on the IPv4 pattern defeats version-string false positives (v3.1.4.0,tree-sitter@0.26.3.1) while still matching real IPs in PG errors ((192.168.1.42)). Order-sensitive pattern set: URL forms first so substrings inside URLs don't get double-redacted. Idempotent, pure (no I/O), hot-path-safe (regex compiled at module load). Wired into BOTHlock-renewal-audit.tsANDbatch-retry-audit.ts. Known limitations: bare-quoted hostnames (at "db.example.com") and usernames (for user "postgres.foo") are NOT caught — the highest-value leak (the IP in those shapes) IS caught. Pinned bytest/audit/redact-connection-info.test.ts(15 cases: all 5 patterns + Supabase fixture + ENOTFOUND fixture + version-string false-positive defense).src/core/minions/lock-renewal-tick.ts— Pure extracted function fromMinionWorker.launchJob's setInterval body; the structural fix that closes the production unhandledRejection crash class. ExportsrunLockRenewalTick(deps, state) → Promise<TickResult>+resolveLockRenewalKnobs(env, lockDuration) → LockRenewalKnobs. Three env knobs, all parsed as positive integers with stderr-warn-once-per-process on bad input + default fallback:GBRAIN_LOCK_RENEWAL_MAX_FAILURES(default 3, audit-labeling only — abort triggering is time-based),GBRAIN_LOCK_RENEWAL_CALL_TIMEOUT_MS(defaultlockDuration/3, bounds the hung-renewLock vector viaPromise.race),GBRAIN_LOCK_RENEWAL_SAFETY_MARGIN_MS(defaultlockDuration/6, ensures release before stall-detector reclaim). The tick checksstate.cancelled()at three guard points (entry, after await resolves, after await throws) so a long renewLock that resolves after the job ended bails cleanly. Result type is a tagged union{kind: 'ok' | 'cancelled' | 'lock_lost' | 'should_abort', reason?}; allabort.abort()/clearIntervalside effects live in the worker's closure scope, not the pure function. Audit calls wrapped in inner try/catch (defense-in-depth).LockRenewalDepshas an optionalreconnect?callback; the renewal-tick's catch branch (after retryable-error classification + the time-based deadline check, before the inter-tick sleep) calls it ONCE, wrapped in its own try/catch. Recovers the mid-process-disconnect case (nulled_sql) where postgres.js auto-reconnect can't help because the engine's pool reference is gone. Deliberately NOT a backgroundwithRetry(a 12s background retry could refresh a lock AFTER the worker already decidedlock-renewal-failedand another worker reclaimed it → two holders); the reconnect is bounded by the samecallTimeoutMsrace + abort signal as renewLock itself. Pinned bytest/worker-lock-renewal.test.ts(18 hermetic state-machine cases — no fs, no PGLite, no real setInterval).src/core/minions/worker-exit-codes.ts— single source of truth for reserved worker process exit codes, shared by the worker (sets them) and supervisor/CLI (classify them). ExportsWORKER_EXIT_RSS_WATCHDOG = 12. The RSS watchdog drain must be self-identifying: a code-0 exit is indistinguishable from a healthy queue-drain, so the supervisor'scode===0 → clean_exitclassifier never counted it and a respawn loop stayed invisible. A distinct code makes the drainlikely_cause=rss_watchdog. Code 12 is deliberately outside {0 clean, 1 runtime_error} and the 128+N signal range.src/core/minions/rss-default.ts— cgroup-aware auto-sized default for the worker RSS watchdog cap.resolveDefaultMaxRssMb(opts?)/describeDefaultMaxRss(opts?)(provenance for the startup log) /readCgroupMemLimitBytes(readFile?). Replaces a flat 2048MB default at every spawn site (jobs work,jobs supervisor, autopilot,MinionSupervisor). Formulaclamp(round(0.5 × basisMB), 4096, 16384)wherebasis = min(cgroupLimit, totalmem). LOAD-BEARING NUANCE: plainos.totalmem()reports HOST RAM, so in a 4GB cgroup on a 126GB host it would pick 16GB, the watchdog would never fire, and the kernel OOM-killer would SIGKILL at 4GB. The cap MUST sit below the real ceiling so the graceful drain (distinct exit code, loud log) beats the kernel's silent kill; the 4096 floor applies only when it stays below the basis. Reads cgroup v2/sys/fs/cgroup/memory.max(literalmax= unlimited) then v1/sys/fs/cgroup/memory/memory.limit_in_bytes. Explicit--max-rss(including0to disable) always wins. Pinned bytest/rss-default.test.ts.src/core/cycle/extract-atoms-drain.ts— pure single-hold bounded drain for the silent lens-phase backlog.runExtractAtomsDrain(deps, opts)over injected deps (withLock,runBatch,countRemaining,now, optionalonBatch) loops bounded batches under ONE continuous lock hold, rediscovering eligibility each batch (idempotent NOT-EXISTS-on-source_hash, so content mutated by a concurrent process simply doesn't match — no cross-window stale cursor), until the backlog is empty OR the time window elapses. Returns{phase, status, extracted, skipped, remaining, batches, stopped}. Backsgbrain dream --phase extract_atoms --drain. Takes the SAMEcycleLockIdFor(sourceId)the routine cycle takes (a concurrent autopilot tick genuinely defers withcycle_already_running); NO release/reacquire-between-windows primitive. The shared wiring helperrunExtractAtomsDrainForSource(engine, {sourceId, windowSeconds, brainDir?, maxBatches?, onBatch?})owns the lock+batch+count+defer wiring (dynamic imports of db-lock/cycle/extract-atoms keep the pure loop cheap to unit-test) and is the ONE drain path for three callers —gbrain dream --drain(which calls it), theextract-atoms-drainMinion handler, and autopilot auto-drain — so lock id / window / defer-on-busy can't drift.sourceId: undefined→ legacygbrain-cyclelock +'default'extraction; a real id →gbrain-cycle:<id>.LockUnavailableErrorpropagates to the caller (each reports the busy case its own way). Pinned bytest/extract-atoms-drain.test.ts.scripts/check-worker-lock-renewal-shape.sh— CI guard wired intobun run verify. Two invariants onsrc/core/minions/worker.ts: (1) the bug patternlockTimer = setInterval(async ...)must NOT appear (narrowed vialockTimer =prefix so unrelatedsetInterval(async)calls — like the stall detector — don't false-fire), (2)runLockRenewalTickmust remain referenced so the pure-function test seam survives refactors. Bug-pattern-specific by design — a future refactor tosetTimeout-recursion orAbortController-based scheduling passes as long as the bug pattern stays absent. POSIX ERE +[[:space:]]for BSD-grep portability. HonorsGBRAIN_LOCK_RENEWAL_SHAPE_TARGETenv override for fixture-based meta-tests. Pinned bytest/scripts/check-worker-lock-renewal-shape.test.ts(5 cases).src/core/doctor-cause-rank.ts— pure cause-ranking forgbrain doctor.rankIssues(checks)returns non-ok checks ordered fail-before-warn then root-before-symptom then name (deterministic).ROOT_CAUSE_CHECKS/SYMPTOM_CHECKSare ORDERING ONLY — tier membership asserts no causality.downstream_ofis set ONLY from a small map of KNOWN grounded edges (queue_health/supervisor→worker_oom_loop, since they read the sameaborted: watchdog/rss_watchdogsource) AND only when the named root is itself failing — never a root×symptom cartesian (co-occurrence never implies causality).fixprefersdetails.fix_hintelse the message.CAUSE_GRAPH_NAMES+allKnownCheckNames()back a drift guard asserting every graphed name is a real check. Consumed bycomputeDoctorReport(top_issuesfield, additive, schema_version stays 2) + the "Top issues (ranked by cause)" header inoutputResults. Pinned bytest/doctor-cause-rank.test.ts.src/commands/doctor.tsextension —computeWorkerOomLoopCheck(engine)is the single authoritative OOM-loop signal, unioning supervisedsummarizeCrashes(readRecentSupervisorEvents(24)).by_cause.rss_watchdog(cross-week read viareadRecentSupervisorEventsso a Monday window can't lose Sunday) + bare-workerminion_jobs error_text='aborted: watchdog'count (Postgres-only; the same sourcequeue_healthsubcheck 3 reads). Cap comes from the latestrss_watchdog_loopbreaker alert'smax_rss_mb, elseresolveDefaultMaxRssMb()fallback. fail at breaker-tripped or oomKills≥5, warn at ≥1, null otherwise.computePoolReapHealthCheck(engine)is the Postgres-onlypool_reap_healthcheck readingreadRecentPoolRecoveries(1)— fail when reconnect failures>0 (reconnect throwing is the actionable signal), warn at ≥10 reaps/hr (pooler thrash), null otherwise. Both registered inbuildChecksafter thesupervisorblock. ThesupervisorcauseStr carriesrss=N (see worker_oom_loop)andqueue_health's watchdog message cross-referencesworker_oom_loop.DoctorReport.top_issues+ the cause-ranked render header.worker_oom_loop+pool_reap_healthregistered under ops indoctor-categories.ts. Pinned bytest/doctor-worker-oom-loop.test.ts,test/doctor-pool-reap-health.test.ts.src/commands/doctor.tsextension —supervisor_singletoncheck (#1849), a SEPARATE check fromsupervisor(same split precedent as the niceness check) so a singleton-divergence warn can't clobber the crash/liveness precedence. Runs only when astartedsupervisor event was seen in the last 24h and a live engine is available. Reads the queue-scoped DB lock row (gbrain_cycle_locks WHERE id = supervisorLockId(queue)) and compares the lock holder (holder_host:holder_pid) against the local pidfile holder via the pureclassifySupervisorSingleton.mismatch→ warn (a second supervisor may be running with a different--max-rss; message names both holders, the effective cap from thestartedevent'smax_rss_mb, and the fixgbrain jobs supervisor stop);single→ ok (names holder + cap);no_lock→ no check emitted. Best-effort try/catch (silent skip on brains without the lock table). Registered under ops indoctor-categories.tsassupervisor_singleton. Pinned bytest/supervisor-db-lock.test.ts+test/doctor.test.ts.src/core/audit/pool-recovery-audit.ts— reap/reconnect audit on the sharedaudit-writercathedral. Events:reap_detected(CONNECTION_ENDED),reconnect_other(network/auth/health-check),reconnect_succeeded,reconnect_failed.readRecentPoolRecoveries(hours=1)returns{reaps, recoveries, failures, others, events}. Error summaries route throughredactConnectionInfobefore truncation (DSN/host/IP safe). Emitted ONLY fromPostgresEngine.reconnect(ctx?)(the rare reap-retry path, near-zero hot-path cost);reconnect()classifies the threaded error viaisConnectionEndedError(in retry-matcher.ts) so only true pooler reaps are labeledreap_detected. The retry callback in retry.ts threads the triggering error as(ctx?: {error?}) => Promise<void>. Pinned bytest/audit/pool-recovery-audit.test.ts.src/commands/autopilot.tsextension — per-sourceextract_atomsauto-drain. Postgres-only block after the freshness fan-out: gated onautopilot.auto_drain.enabled(default true) AND!packDeclaresPhase(engine,'extract_atoms')(the silent-backlog condition) AND per-sourcecountExtractAtomsBacklog > threshold(default 25) AND a daily capfloor(max_usd_per_day / ~$0.30). EnumeratesloadAllSources. Submits the PROTECTEDextract-atoms-drainjob ({allowProtectedSubmit:true}) with a UTC-day time-sloted idempotency keyautopilot-extract-atoms-drain:<src.id>:<utcDay>(a static key would block the source after the first job completed).src/core/minions/protected-names.tsaddsextract-atoms-drain;src/commands/jobs.tsregisters the handler (thin wrapper overrunExtractAtomsDrainForSource,LockUnavailableError→{deferred:true});src/core/config.tsadds theautopilot.auto_drain.*config keys + theautopilot.key prefix. Pinned bytest/extract-atoms-drain-handler.test.ts,test/autopilot-auto-drain-wiring.test.ts.src/commands/doctor.ts:checkBatchRetryHealth—batch_retry_healthcheck surfacing Supavisor circuit-breaker incidents. Wired into bothrunDoctor(local) anddoctorReportRemote(thin-client). Reads last 24h. States:ok(zero exhausted in 24h OR <3 from a single site),warn(>=3 same-site OR >=5 cross-site),fail(>=20 sustained breaker). Surfaces badGBRAIN_BULK_*env at doctor startup. Corrupt-JSONL tolerant. Paste-ready fix hints in every warn/fail message. Also readsreadRecentDbDisconnects(24)and appendsDisconnect-call audit: N call(s) in 24h (most recent caller: <frame>).to ALL three message paths so connection-incident signal is greppable from onegbrain doctor --jsoncall (module-import wrapped in try/catch so older brains without the audit file degrade silently). Pinned bytest/doctor-batch-retry.test.ts(10 cases).src/core/audit/db-disconnect-audit.ts— JSONL audit for every call todb.disconnect()andPostgresEngine.disconnect(). Built onaudit-writer.ts. Schema:{ts, engine_kind: 'postgres'|'pglite'|'unknown', connection_style: 'module'|'instance'|'unknown', caller_stack, command, pid}.caller_stackcaptured vianew Error().stacktruncated to ~20 frames so operators identify the offending caller without inflating JSONL. Privacy: stack frames carry file paths but NO SQL content / row data / user strings. File:~/.gbrain/audit/db-disconnect-YYYY-Www.jsonl(honorsGBRAIN_AUDIT_DIR).readRecentDbDisconnects(hours=24)walks current + previous ISO week and returns{count, most_recent_caller, files_scanned}. Wired intosrc/core/db.ts:disconnectandsrc/core/postgres-engine.ts:disconnect, logging BEFORE the early-return branches so even no-op disconnects on never-connected engines are recorded (that case may itself be a caller-side bug). Pinned bytest/db-disconnect-audit.test.ts(6 cases: round-trip, stack truncation, sort order, empty-dir nulls, stable feature name, EROFS best-effort).src/core/facts/queue.ts:FactsQueue.drainPending— methoddrainPending({timeout?: number}): Promise<{drained, unfinished}>. Semantically distinct fromshutdown()(which callsthis.internalAbort.abort()and would abort the very facts:absorb worker trying to log its post-completion event). Drain lets in-flight finish; only the wait is bounded. Default timeout1000msso commands that don't enqueue facts pay one fast 0ms check before exit.src/cli.tsop-dispatch finally block awaitsgetFactsQueue().drainPending({timeout: 1000})BEFOREengine.disconnect(). Lazy-import keeps the facts-queue module off the hot path for ops that never touch it. Closes the trailing'No database connection'line aftergbrain capture(post-page-write facts:absorb outlived the CLI process). Pinned bytest/facts-queue-drain-pending.test.ts(4 cases: empty fast-path, in-flight settled without abort, unfinished count on timeout, default timeout = 1000ms).scripts/check-no-double-retry.sh+scripts/check-batch-audit-site.sh— CI lint guards wired intobun run verify. The former greps src/ forwithRetry(...engine.{addLinksBatch|addTimelineEntriesBatch|upsertChunks})patterns and fails the build on hit (prevents 3×3=9 retry amplification on incomplete reverts). The latter extracts every string-literalauditSite: '...'from src/ and validates each appears in theBATCH_AUDIT_SITESconst insrc/core/retry.ts(typo guard — prevents fragmented doctor output).src/core/fail-improve.ts— Deterministic-first, LLM-fallback loop with JSONL failure logging and auto-test generation.src/core/transcription.ts— Audio transcription: Groq Whisper (default), OpenAI fallback, ffmpeg segmentation for >25MB.src/core/enrichment-service.ts— Global enrichment service: entity slug generation, tier auto-escalation, batch throttling.src/commands/enrich.ts+src/core/enrich/thin.ts+src/core/cycle/enrich-thin.ts—gbrain enrich --thin: batch-develops stub (thin) pages via brain-internal grounded synthesis. gbrain's model tooling sees only brain-internal context (search / get_page / facts / backlinks), not the web, so enrich consolidates what the brain ALREADY knows about an entity (scattered across meetings, other pages, deals, facts) into one cited page via ONEgateway.chatcall per page; web research stays the agent-drivenenrichSKILL's job.runEnrichCore(engine, opts, signal)(strict per-source; multi-source iteration is the caller's job) drivesenrichOneper candidate:withRefreshingLock('enrich:<src>:<slug>')→getPage→ deterministic retrieve (hybridSearch + getBacklinks + facts + raw_data, source-scoped, sanitized viaINJECTION_PATTERNS) →assessGroundinggate (skip <MIN_CONTEXT_CHARS, no LLM) →buildEnrichPrompt(grounded dossier,[Source: slug]citations, SKIP sentinel) → synth →put_pagehandler (remote:false, auto-link + write-through) stampingenriched_at+enriched_by:'cli:enrich'. Candidate selection is the SQL-nativeengine.listEnrichCandidates(opts)(src/core/engine.tsinterface +EnrichCandidate/EnrichCandidatesOpts/ENRICH_ORDER_SQLinsrc/core/types.ts+ pg/pglite impls): thin-filter + per-page source-correct inbound count (to_page_id = p.id,mentionsexcluded) +enriched_atrecency guard + whitelisted ORDER BY + LIMIT, lightweight projection (NO bodies). Resume viasrc/core/op-checkpoint.ts(localenrichFingerprint); budget viaBudgetTracker+withBudgetTracker(best-effort under--workers > 1—runSlidingPoolaborts new claims onBUDGET_EXHAUSTEDbut does NOT cancel in-flightgateway.chat; pin--workers 1for a hard ceiling).sanitizeContext(thin.ts) neutralizes the<context>…</context>data-envelope delimiters (injection escape, mirrors the</trajectory>convention); the--backgroundmulti-source fan-out idempotency key carries the run fingerprint via exportedbackgroundIdempotencyKey(sid, args)(a bareenrich:${sid}would return stale completed jobs);runEnrichCoreflagsbudget_exhaustedpost-hoc whentracker.totalSpent > tracker.capeven when the gateway swallowed the final-call throw (via read-onlyBudgetTracker.capgetter);body()flushes the checkpoint onBudgetExhaustedbefore it propagates so resume doesn't re-charge. The opt-inenrich_thincycle phase (default OFF viacycle.enrich_thin.enabled) tricklesmax_pages_per_tick(default 3) per source with per-source cost cap enforced asmin(per_source_cap, brain_wide_remaining)+ brain-wide total + walltime caps. Wired intocycle.ts(CyclePhase/ALL_PHASESbetweenconversation_facts_backfillandskillopt/embed;PHASE_SCOPE='source';NEEDS_LOCK; dispatch),cli.ts(CLI_ONLY+CLI_ONLY_SELF_HELP+THIN_CLIENT_REFUSED_COMMANDS+ dispatch),jobs.ts(Minionenrichhandler, strict per-source, NOT inPROTECTED_JOB_NAMES). DI seamopts.synthesizeFnkeeps tests hermetic (no API key, no mock.module). Pinned bytest/enrich/thin.test.ts,test/enrich/idempotency.test.ts,test/enrich-cycle-phase.test.ts,test/e2e/enrich-pglite.test.ts(grew-cited, skip, ordering, multi-source, recency, resume, budget abort + checkpoint flush, final-call overage, lock-skip, provenance),test/e2e/engine-parity.test.ts(listEnrichCandidatespg↔pglite parity).src/core/data-research.ts— Recipe validation, field extraction (MRR/ARR regex), dedup, tracker parsing, HTML stripping.src/commands/embed.ts—gbrain embed [--stale|--all] [--slugs ...].--stalestarts withengine.countStaleChunks()(single SELECT count(*) WHERE embedding IS NULL, ~50 bytes wire) so a fully-embedded brain short-circuits with no further reads. When stale chunks exist,engine.listStaleChunks()returns just the chunks needing embeddings (slug + chunk_index + chunk_text + metadata, novector(1536)payload); caller groups by slug, embeds, re-upserts viaupsertChunks. Allconsole.log/console.errorcall sites useslog/serrfromsrc/core/console-prefix.tsso whenrunEmbedCoreruns inside a per-sourcewithSourcePrefixscope (installed by thegbrain sync --allworker pool) every line carries the[<source-id>]prefix; standalone callers see identical output because slog/serr fall through to bare console fns outside the wrap. Every embed-write path stampspages.embedding_signatureviaengine.setPageEmbeddingSignature(slug, {sourceId, signature: currentEmbeddingSignature()})so a later model/dims swap is detectable as stale. The per-slug path (embedPage, used bygbrain embed <slug>AND sync's post-import embed step) and the full-re-embed path (embedAll) stamp unconditionally per page. The stale path (embedAllStale) first callsinvalidateStaleSignatureEmbeddingson a live run so signature-drifted pages flow through the NULL cursor, then stamps each page — but ONLY when EVERY chunk was stale this pass (a partially-stale page keeps preserved chunks of unknown provenance, so it stays unstamped rather than falsely marked current;embed --allfully re-embeds + stamps those). dry-run never mutates: it counts signature-drift via the widenedcountStaleChunks({signature})predicate without NULLing anything.src/core/conversation-parser/— 14-pattern built-in chat-format registry + opt-in LLM polish/fallback. Modules:types.ts(PatternEntry + ParseResult + DateContext + CaptureMap + TimezonePolicy),builtins.ts(14 hand-vetted patterns sourced from public format docs — iMessage/Slack, Telegram bracket + text-export, bold-paren-time, bold-name-no-time, Discord classic + export, WhatsApp ISO + US, Signal, Matrix/Element, IRC classic + weechat, Teams export; module-load validation runs everytest_positive[]+test_negative[]sample at startup so a typo in any built-in regex makes gbrain refuse to start;DEFAULT_SPEAKER_CLEANexported as a module-level default),parse.ts(orchestrator with pattern-priority scoring across the first 10 lines + date derivation chainexplicit > frontmatter.date > effective_date > '1970-01-01'+ multi-line continuation + timezone warning),llm-base.ts(sharedrunLlmCall<T>with content-hash cache in-process + DB-persistent via migration v97 + 4-strategy JSON repair + Anthropic-key probe; polish and fallback are thin wrappers),llm-polish.ts(opt-IN; headroom guard skips when tracker within $0.10 of cap; pureapplyPolishfor merge/drop/edit ops),llm-fallback.ts(opt-IN; NO regex inference + NO persistence),eval.ts(scoreFixture+aggregateScores+parseFixtureJsonlfor the fixture-corpus CI gate),nightly-probe.ts(DI-stubbed; mode-gated default tokenmax=ON, conservative/balanced opt-in; adversarial false-positive detection). Patternbold-name-no-time(regex/^\*\*(?!\[)(.+?):\*\*\s*(.*)$/, index 3 afterbold-paren-time) parses**Speaker:** textwith NO per-line timestamp (Circleback/Granola/Zoom), anchoring every message atT00:00:00Zof the frontmatter date (line order preserves sequence, same no-time convention asirc-classic); the(?!\[)lookahead rejects telegram-bracket**[18:37] Name:**; non-shadow is the colon-INSIDE-bold regex (NOT declaration order —parse.tsscores every candidate independently, order is only the tie-break). Because**Label:** textis a common prose idiom, the pattern sets optionalPatternEntry.score_full_body: truesoparse.tsrecomputes the winner's acceptance score over the FULL body before theSCORING_MIN_ACCEPTANCEfloor, keeping a bold-label notes page atno_match. Patternbold-paren-timeparses**Speaker** (HH:MM): textand(HH:MM:SS)(date_source: frontmatter). Fallback gates:SCORING_HEAD_TRIGGER_THRESHOLD = 0.3triggers a full-body re-score when the head pass scores below that;SCORING_MIN_ACCEPTANCE = 0.05blocks essay false-positives. ExportedscorePatternFull(body, entry); privategetNonBlankLines(body, headCap?)+scoreFromLines(lines, entry)DRY the quick_reject+regex loop. CLI surfaces atsrc/commands/eval-conversation-parser.ts(gbrain eval conversation-parser <fixture.jsonl>exit 0/1/2, wired intobun run verifyviacheck:conversation-parser) andsrc/commands/conversation-parser.ts(scan <slug>debug,list-builtins,validate <file>). Doctor checks:conversation_format_coverage,progressive_batch_audit_health,conversation_parser_probe_health. Pinned bytest/conversation-parser/{parse,llm-base,llm-fallback,llm-polish,nightly-probe}.test.ts+ the 27-case baseline attest/extract-conversation-facts.test.ts(back-compat invariant). Migration v97 (conversation_parser_llm_cache_table). Fixtures attest/fixtures/conversation-formats/{imessage,telegram-bracket,whatsapp-iso,whatsapp-us,signal-export,irc-classic,irc-weechat,matrix-element,teams-export,all,adversarial,bold-name-no-time}.jsonlwithscripts/check-fixture-privacy.shbanning real-name leaks.src/core/progressive-batch/— shared ramp-up + cost-cap + verification primitive (trial 10 → ramp 100 → ramp 500 → full, with verification at each stage), with verifier+policy injection (callers describe HOW TO MEASURE SUCCESS, not WHEN TO WAIT FOR CTRL-C). Modules:types.ts(Stage, StageVerdict, AbortReason, discriminatedVerifierunionOutputCountVerifier | IdempotentMutationVerifier | NoopVerifier, Policy, StageReport),orchestrator.ts(runProgressiveBatch(items, verifier, policy, runner)— readsgetCurrentBudgetTracker()ahead ofPolicy.maxCostUsdfail-closed; null both ways triggersabort_cost_cap reason='no_budget_safety_net'),audit.ts(ISO-week JSONL at~/.gbrain/audit/progressive-batch-YYYY-Www.jsonlvia the sharedaudit-writerprimitive),stage-report.ts(ASCII formatter for the defaultPolicy.onStageReport). Env knobs:GBRAIN_PROGRESSIVE_BATCH_DISABLED=1,GBRAIN_PROGRESSIVE_BATCH_AUTO=1(skip Ctrl-C grace),GBRAIN_PROGRESSIVE_BATCH_STAGES=10,100,500. Sites that "jump straight to full" stay that way by default; ramp is opt-in per-site viaPolicy.interactiveAbortMs > 0. Pinned bytest/progressive-batch/orchestrator.test.ts(35 cases, every verdict path).src/commands/extract-conversation-facts.ts+src/core/cycle/conversation-facts-backfill.ts— bulk fact extraction for long-form conversation pages. Walks conversation/meeting/slack/email pages, splits into time-windowed segments (30-min gap or 30-msg cap), prepends a topical/temporal header, and runs throughextractFactsFromTurn()so anchor-rich facts surface ingbrain search. Invariants: strict per-source core (runExtractConversationFactsCore({sourceId, ...})always takes one sourceId; CLI + cycle phase each do their own multi-source iteration because PHASE_SCOPE='source' is taxonomy-only); two-phase enumeration (paginatedlistPages({type, sourceId, limit:10}); per-page body cap MAX_PAGE_BODY_BYTES=25MB withpages_skipped_too_largecounter surfaced in doctor); page-global row_num accumulator (facts unique index is(source_id, source_markdown_slug, row_num)per migration v51 — per-segment row_num would collide); page-level TERMINAL audit row to facts table after all segments commit (source='cli:extract-conversation-facts:terminal'; doctor's NOT EXISTS matches the terminal row so partial-extraction pages stay in backlog); optionalopts.budgetTracker?(when present, used as-is — nestedwithBudgetTrackerREPLACES; when absent, core auto-wraps withBudgetTracker({maxCostUsd})); body read covers compiled_truth + timeline; honorsfacts.extraction_enabledkill-switch with--override-disabledescape; --types LIST allowlist (conversation,meeting,slack,email) with CLI default readingcycle.conversation_facts_backfill.types; fingerprint on sourceId only; string-encoded op-checkpoint entries"<sourceId>|<slug>|<endIso>"for resume (durable audit is the facts terminal row);--backgroundvia maybeBackground (Minion handlerextract-conversation-factsre-creates BudgetTracker fromdata.max_cost_usd; onBudgetExhaustedmid-job catches + persists + markscompletedwithresult.budget_exhausted=true). The companion cycle phaseconversation_facts_backfill(default OFF) iterateslistSources(engine), creates ONE brain-wide tracker per tick + wraps the loop inwithBudgetTracker+ passes the tracker into every per-source call. Two-layer cost AND walltime caps: per-source (max_cost_usd=$1,max_walltime_min=20) AND brain-wide (max_total_cost_usd=$5,max_total_walltime_min=30). Pinned bytest/extract-conversation-facts.test.ts(27 cases). Migration v94 adds partial indexidx_facts_extract_conversation_session ON facts(source_id, source_session) WHERE source LIKE 'cli:extract-conversation-facts%'(transaction:false+ invalid-index pre-drop on Postgres; plain CREATE INDEX on PGLite).src/commands/doctor.ts:computeConversationFactsBacklogCheckis 3-state (SKIPPED when disabled; OK when caught up; WARN when >10 pages lack the terminal row, with paste-readygbrain doctor --remediatestep).src/commands/sources.ts:runAuditaddsfacts_backfill_estimate: {pages, est_segments, est_cost_usd, types}. Schema-packgbrain-base.yamlpromotesconversation(temporal, extractable) +atom(annotation, NOT extractable) into the base seed; backstop uses hardcodedELIGIBLE_TYPESinsrc/core/facts/eligibility.ts:51not pack extractable.ALL_PAGE_TYPESinsrc/core/types.tsextended with the two new types.src/core/link-extraction.ts— shared library for the graph layer.extractEntityRefs(canonical) matches[Name](people/slug)markdown links and Obsidian[[people/slug|Name]]wikilinks;extractPageLinks,inferLinkTypeheuristics (attended/works_at/invested_in/founded/advises/source/mentions),parseTimelineEntries,isAutoLinkEnabled.DIR_PATTERNcoverspeople,companies,deals,topics,concepts,projects,entities,tech,finance,personal,openclaw. Used by extract.ts, operations.ts auto-link post-hook, and backlinks.ts. Opt-in global-basename wikilink resolution (issue #972, default off):WIKILINK_GENERIC_REcatches bare[[name]]wikilinks outsideDIR_PATTERN(third pass2cinextractEntityRefs);EntityRef.needsResolution: truetags refs from this pass (the ref'sslugis the wikilink TARGET,namethe optional display alias).SlugResolvergains optionalresolveBasenameMatches(name): Promise<string[]>(multi-match by design — emits one edge per matching page). The single shared basename matcher isbuildBasenameIndex(slugs)+queryBasenameIndex(index, name)+normalizeBasename(keys raw/lower/slugified tail, stable-sorted shorter-first then lexical), used bymakeResolver, the FSresolveBasenameMatchesFromSlugs, AND the doctor check so they cannot drift.makeResolver(engine, {mode, sourceId})builds the index lazily viaengine.getAllSlugs({sourceId})— source-scoped so a bare[[name]]never resolves to a same-tail page in a different source.extractPageLinksgainsopts.globalBasename(routesneedsResolutionrefs throughresolveBasenameMatcheskeyed onref.slug, emits candidates taggedlinkType: 'wikilink_basename'+linkSource: 'wikilink-resolved', skips self-loops) andopts.skipFrontmatter(replaces the oldnullResolverternary). All three surfaces (FS extract, DB extract,put_pageauto-link) tag provenance withlink_source='wikilink-resolved';put_pageincludes it in its reconcilable-edge set so stale basename edges are removed when the wikilink or the flag goes away. ExportsWIKILINK_BASENAME_LINK_TYPE+isGlobalBasenameEnabled(engine)(resolution order: envGBRAIN_LINK_RESOLUTION_GLOBAL_BASENAME→ DB configlink_resolution.global_basename→ default false).gbrain doctor'slink_resolution_opportunitycheck surfaces a paste-ready enable hint when ≥5 bare wikilinks would resolve AND ≥20% match. Migration v113 widenslinks_link_source_checkto admit'wikilink-resolved'; v114 (#1941) then opens it to any kebab-case provenance (^[a-z][a-z0-9]*(-[a-z0-9]+)*$, ≤64 chars) so external derivers register their own tag (e.g.citation-graph) without a migration.LINK_EXTRACTOR_VERSION_TSalso lives here (bump likeCHUNKER_VERSIONto invalidate prior extract-stale stamps). Pinned bytest/link-extraction.test.ts,test/extract-fs.test.ts,test/doctor.test.ts,test/e2e/global-basename-pglite.test.ts.src/commands/extract.ts—gbrain extract links|timeline|all [--source fs|db] [--source-id <id>]: batch link/timeline extraction. fs walks markdown files, db walks pages from the engine (mutation-immune snapshot iteration; use for live brains with no local checkout). No in-memory dedup pre-load — candidates buffered 100 at a time and flushed viaaddLinksBatch/addTimelineEntriesBatch;ON CONFLICT DO NOTHINGenforces uniqueness at the DB layer,createdcounter returns real rows inserted.ExtractOpts.slugs?: string[]enables incremental extract viaextractForSlugs()(single combined links+timeline pass); the cycle path threads sync'spagesAffectedthrough.walkMarkdownFiles(brainDir)still runs to buildallSlugsfor link resolution.--source-id <id>scopes extraction to one source on federated brains (resolved viaresolveSourceWithTier()before any SQL; failures hintgbrain sources list).gbrain extract --stale [--source-id <id>] [--catch-up] [--dry-run] [--json]branch (extractStaleFromDB) — incremental DB-source link+timeline sweep over pages whosepages.links_extracted_atwatermark is stale. Stale predicate (shared by both engines + the doctor check):links_extracted_at IS NULL OR links_extracted_at < LINK_EXTRACTOR_VERSION_TS::timestamptz OR updated_at > links_extracted_at(theupdated_atarm catches MCPput_page/sync --no-extractedited-since-extract). Three newBrainEnginemethods (parity in postgres-engine.ts + pglite-engine.ts + bootstrap probes):countStalePagesForExtraction(opts?),listStalePagesForExtraction({batchSize, afterPageId?, sourceId?, versionTs?})(returns page CONTENT to avoid N+1getPage;rowToStalePagein utils.ts maps the row,StalePageRowin types.ts),markPagesExtractedBatch(refs, defaultExtractedAt)(3-array unnestslug[],source_id[],ts[]; each ref may carry its ownextractedAt).STALE_BATCH_SIZEdefault 25 (GBRAIN_EXTRACT_STALE_BATCH; small because page bodies are unbounded — the LIMIT is the only fetch-time memory bound);STALE_TIME_BUDGET_MS30min wall-clock (--catch-upremoves the cap). Non-swallowing flush: link/timeline flush throws propagate and abort the batch; stamp LAST so a crash leaves pages unstamped and they re-extract idempotently (addLinksBatchON CONFLICT DO NOTHING + timeline dedup). Race fix:extractStaleFromDBstamps with each row's READupdated_at(notnow()), so a concurrent edit during the sweep keeps the page stale and it re-extracts next run rather than marked fresh-with-old-content. Source-correct stamping at DB-extract sites viastampExtracted(best-effort, never throws);extractLinksFromDBonly stamps the combined watermark whensubcommand === 'all'(a links-only run must not hide timeline staleness).LINK_EXTRACTOR_VERSION_TSlives insrc/core/link-extraction.ts(bump likeCHUNKER_VERSIONto invalidate all prior stamps). Migration v112 (pages_links_extracted_at) adds nullableTIMESTAMPTZ+ composite(source_id, links_extracted_at)index (CONCURRENTLY + invalid-remnant pre-drop on Postgres, plain on PGLite), NO backfill so the real backlog surfaces on firstgbrain doctor. Schema parity in schema.sql + pglite-schema.ts + schema-embedded.ts +REQUIRED_BOOTSTRAP_COVERAGE.src/commands/doctor.ts:checkLinksExtractionLag(thelinks_extraction_lagcheck, also indoctorReportRemote) warn-only by default (>GBRAIN_EXTRACTION_LAG_WARN_PCT, default 20%; sharedEXTRACTION_LAG_WARN_PCT_DEFAULT+EXTRACTION_LAG_MIN_PAGES=100+ exported_resolveEnvNumber), hard-fails only whenGBRAIN_EXTRACTION_LAG_FAIL_PCTis set; vacuous-skips <100 pages (no--source); pre-v112 brains graceful-skip viaisUndefinedColumnError; strictly a SQL COUNT (safe on remote/thin-client).src/commands/sync.tsgains--no-extract(threaded through single-source +--all+syncOneSource), stampslinks_extracted_atforpagesAffectedat the inline-extract call site, andmaybeExtractionNudgeprints a one-line stderr nudge after asynced | first_sync | up_to_datesync that leaves a backlog (shouldNudgeAfterSyncpure predicate;GBRAIN_SYNC_NO_EXTRACT_NUDGEsuppresses).src/core/retry.tsadds'extract.stale'toBATCH_AUDIT_SITES;src/core/doctor-categories.tsaddslinks_extraction_lagtoBRAIN_CHECK_NAMES. Pinned bytest/extract-stale.test.ts(incl. edited-after-stamp regression + crash-contract),test/sync-inline-extract-stamps.serial.test.ts,test/sync-nudge-status-gate.test.ts,test/doctor-links-extraction-lag.test.ts, engine-parity (Postgres↔PGLite) for the 3 methods + v112 round-trip. The stale SELECT in both engines projects a deterministic full-µs UTC stringto_char(updated_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS updated_at_iso(carried onStalePageRow.updated_at_iso, populated byrowToStalePagein utils.ts with an ISO-only fallback — neverString(Date), which::timestamptzmisparses);extractStaleFromDBstamps that exact-precision value, not a JSDate(which truncates to milliseconds), so on Postgreslinks_extracted_atequals the row'supdated_atto the microsecond andlinks_extraction_lagclears — a ms-truncated stamp stays strictly below the µsupdated_atand leaves every page perpetually stale, whichextract --stalecould never satisfy.to_char(not raw::text, which isDateStyle-fragile) keeps the projection deterministic. ThemarkPagesExtractedBatchSQL is unchanged, so callers passing an explicit (e.g. backdated)extractedAtstill control the stamp and the edited-since arm is exact. A deterministic PGLite regression intest/extract-stale.test.tsinjects a µsupdated_at, runs--stale, and asserts the lag is 0 and stays 0.src/core/extract/receipt-writer.ts+src/core/extract/rollup-writer.ts+src/commands/extract-status.ts+src/commands/extract-explain.ts+src/commands/extract-benchmark.ts+src/core/schema-pack/scaffold-extractable.ts— unified extract operator surface. Every shipped extractor (deterministicfacts.conversationinsrc/commands/extract-conversation-facts.ts+ three LLM-backed cycle phases atsrc/core/cycle/{extract-atoms,synthesize-concepts,propose-takes,extract-facts}.ts) writes ONE receipt page per run (writeReceipt) + UPSERTs a row toextract_rollup_7d(upsertExtractRollup). Receipt slugextracts/{date}/{kind}/{source_id}/{run_id_short}/round-{N}.md; frontmatter stamps BOTHtype: extract_receiptANDdream_generated: true(belt+suspenders against extraction-loop guard drift).extract_receiptjoinsALL_PAGE_TYPESinsrc/core/types.ts;extracts/prefix gets a 0.3x source-boost demote insrc/core/search/source-boost.ts. Migration v104 addsextract_rollup_7d (kind, source_id, day, cost_usd, halt_count, eval_pass_count, eval_fail_count, round_completed_count, rollup_write_failures, updated_at)with PK(kind, source_id, day)+idx_extract_rollup_7d_day. Rollup writes best-effort with process-scoped error-dedup so transient DB failures bumprollup_write_failuresinstead of crashing the cycle.extract_healthdoctor check reads last 7 days, warns at halt-rate > 10% AND when rollup_write_failures > 0; pre-v104 brains reportok. CLI:gbrain extract status [--source-id ID] [--kind X] [--verbose] [--json](7-day rollup, sorted halt_rate desc + cost desc, top-5 + "more rows" hint, stableschema_version: 1);gbrain extract --explain <kind>(resolution chain pack-declared vs built-in cycle phase, prompt_template + fixture_corpus paths with✓/(missing), last 7d rollup);gbrain extract benchmark --pack X --kind Y(loads pack fixture corpus through strict path validation — rejects absolute paths,..traversal, null bytes, AND symlinks resolving outside pack root; ships as a stub-reporter).src/core/schema-pack/manifest-v1.tswidensextractablefromz.boolean()toz.union([z.boolean(), ExtractableSpecSchema])(carriesprompt_template,fixture_corpus,eval_dimensions,benchmark_min_recall, plus reservedverifier_path— parses but refuses at runtime);extractableSpecsFromPack+getExtractableSpec+refuseVerifierPathInV042insrc/core/schema-pack/extractable.ts;gbrain schema scaffold-extractable <type> --pack <pack>declares the type extractable, generates 5 placeholder fixtures + a prompt template stub underpacks/<pack>/{fixtures,prompts}/extract/, refuses to overwrite without--force. Pinned bytest/extractable-spec-widening.test.ts(22),test/extract/receipt-writer.test.ts(12, canonical PGLite block R3+R4),test/extract/benchmark.test.ts(17),test/extract/status.test.ts(15),test/schema-pack/scaffold-extractable.test.ts(15, privacy guards),test/doctor-extract-health.test.ts(8).src/commands/import.ts—gbrain import <path> [--source-id <id>]: page import with the path-set checkpoint.--source-id <id>routes pages to the named source (resolved viaresolveSourceWithTier()at the boundary; consistent acrossimport,extract,graph-query,sources current). Pinned bytest/import-source-id.test.ts.src/commands/graph-query.ts—gbrain graph-query <slug> [--type T] [--depth N] [--direction in|out|both] [--include-foreign]: typed-edge relationship traversal (renders indented tree). Foreign-edge footer always present (X foreign edges (use --include-foreign to traverse)) so cross-source edges never disappear silently;--include-foreignwidens the SQL filter to walk them. Pinned bytest/graph-query.test.ts.src/commands/sources.ts—gbrain sources {list,add,remove,archive,restore,archived,purge,current,status,audit}.current [--json]callsresolveSourceWithTier()and printssource_id,tier(flag | env | dotfile | local_path | brain_default | seed_default), and optionaldetail(decision table inskills/conventions/brain-routing.md).status [--json]— read-only per-source dashboard (last sync, staleness, page count, embedding coverage, unacked failures); thin wrapper aroundbuildSyncStatusReport+printSyncStatusReportfromsrc/commands/sync.ts;--jsonemits stable{schema_version: 1, sources, ...}on stdout; filters input tolocal_path IS NOT NULL AND archived IS NOT TRUE.audit <id> [--json]— read-only dry-run disk scan for size distribution + would-blocks + junk-pattern hits WITHOUT touching the DB; walkssources.local_path, reads each markdown file, runsassessContent()fromsrc/core/content-sanity.ts, aggregates by verdict (ok | warn_oversize | hard_block_junk_pattern). The liverunStatushealth table gains aBACKFILLcolumn betweenEMBEDandFAILS(active(N)beatsqueued(N)beatsidle, fromSourceMetrics.backfill_active/backfill_queuedinsrc/core/source-health.ts) so operators see deferredembed-backfillminion work aftersync --allexits 0;jobCountsBySourceinsource-health.tswidens itsminion_jobsSQL with twoCOUNT(*) FILTER (WHERE name = 'embed-backfill' AND ...)aggregates (best-effort, all-0 on pre-minions brains). Pinned bytest/content-sanity.test.ts,test/import-file-content-sanity.test.ts,test/source-health.test.ts.src/commands/reindex-frontmatter.ts—gbrain reindex-frontmatter. Query path wrapped in the standardwithEngine(...)lifecycle soengine.connect()runs before the first SQL call. Pinned bytest/reindex-frontmatter-connect.test.ts.src/core/source-resolver.ts— 6-tier source resolution.resolveSourceWithTier(engine, explicit, cwd)returns{ source_id, tier: SourceTier, detail? }alongsideresolveSourceId()(unchanged).SOURCE_TIER_NAMES = ['flag', 'env', 'dotfile', 'local_path', 'sole_non_default', 'brain_default', 'seed_default'](7 entries; order matches priority). Tiersole_non_defaultslots betweenlocal_pathandbrain_default: when NOsources.defaultconfig is set AND exactly one registered source haslocal_pathAND isn't'default', auto-route to it; archived sources excluded (try/catch for pre-v34 brains); privatepickSoleNonDefaultSource(engine)shared by both resolver entry points so they cannot drift. ExportedformatSoleNonDefaultNudge(sourceId): string | nullbuilds the user-facing stderr nudge (null whenGBRAIN_NO_SOLE_NON_DEFAULT_NUDGE=1).src/commands/sync.ts:1497-1519callsresolveSourceWithTierunconditionally so the tier fires;src/commands/import.ts:96-128mirrors with the tier-gated nudge. Consumed bygbrain sources current,import --source-id,extract --source-id, and thesource_routing_healthdoctor check. Pinned bytest/source-resolver-with-tier.test.ts(withEnv()per test-isolation lint),test/source-resolver-sole-non-default.test.ts(14 cases),test/sync-sole-non-default-routing.test.ts(3 PGLite cases driving realrunSync).src/core/sync.tsextension —isSyncablefactored through privateclassifySync(path, opts): SyncableReason | null; exported companionunsyncableReason(path, opts)returns the same tagged reason or null when syncable.SYNC_SKIP_FILESis a named export (the four canonical metafile basenamesschema.md,index.md,log.md,README.md).SyncableReasonunion:'metafile' | 'strategy' | 'pruned-dir' | 'include-glob-miss' | 'exclude-glob-hit'.src/commands/sync.ts:772cleanup loop guards onunsyncableReason(path) === 'metafile'so previously-indexed metafile pages survive every re-sync. Does NOT covermanifest.deleted(the upstream filter already strips metafiles). Pinned bytest/sync-isSyncable-shape.test.ts(15 cases, duality contract) +test/sync-metafile-skip.serial.test.ts(3 PGLite cases incl. the renamed.md → .txtnegative).src/core/import-file.tsextension — identity-based dedup pre-check at:427-490. Callsengine.findDuplicatePage?.(sourceId, {hash, frontmatterId})(optional?so test doubles compile). Posture: SKIP whenfrontmatter.idmatches (true external duplicate from overlapping ingest roots), WARN-ALWAYS on content_hash collision with different/missingfrontmatter.id(templates and daily logs may legitimately share text), FAIL CLOSED on lookup error, bypass via--force-rechunk. Soft-deleted pages excluded at the engine layer so tombstones don't block legitimate re-imports under new slugs. Pinned bytest/import-dedup-frontmatter-id.test.ts(11 cases).src/core/engine.tsextension — two interface members: (1) optionalfindDuplicatePage?(sourceId, {hash, frontmatterId?}): Promise<{slug, id} | null>(identity precedence is content_hash OR frontmatter->>'id', both withdeleted_at IS NULL); (2)resolveSlugs(partial, opts?)extended with{sourceId?, sourceIds?}so the MCP fuzzyget_pagepath scopes by source (field names matchsourceScopeOpts(ctx)output so handlers spread directly; back-compatible — no opts gives prior behavior). Plus a stable tiebreakerORDER BY score DESC, page_id ASC, chunk_id ASCinsearchVectorin both engines: on a score tie (basis-vector eval fixtures) olderpage_idwins, closing the planner-non-determinism class where a new index onpagescould flip ranking on tied scores.src/core/migrate.tsv95 —pages_dedup_partial_indexaddsCREATE INDEX pages_dedup_idx ON pages (source_id, content_hash) WHERE deleted_at IS NULL. Postgres usesCREATE INDEX CONCURRENTLYwithtransaction: false+ pre-drops any invalid remnant; PGLite uses plainCREATE INDEX. PowersfindDuplicatePagehot path (O(log n) instead of O(n)).src/cli.tsextension —dreamdispatch at lines 1063-1080 binds the caught engine-connect error and emits[dream] WARNING: could not connect to DB (...)to stderr before falling through to filesystem-only phases; therunDream(null, ...)no-DB fallback is preserved. Pinned bytest/cli-dream-engine-warn.test.ts(2 subprocess cases against good + bad DATABASE_URL).src/commands/autopilot.tsextension — federated-brain co-existence + launchd hygiene. (1)LOCK_PATHresolves viagbrainPath('autopilot.lock')so it honorsGBRAIN_HOME(two brains can run autopilot simultaneously without lock-stealing); lock file stores PID, startup checkskill -0 <pid>before refusing to start (stale lock from a crashed process no longer blocks). (2) exportedclassifyReconnectError(err)returns'recoverable' | 'unrecoverable'; unrecoverable causesprocess.exit(0)so launchd backs off instead of loopingconfig.database_url undefined. (3) exported puregenerateLaunchdPlist(wrapperPath, home)setsThrottleInterval=300so launchd respects the exit-0 backoff. Pinned bytest/autopilot-lock-path.test.ts+test/autopilot-reconnect-classifier.test.ts.src/core/oauth-provider.ts+src/commands/serve-http.tsextension — custom/tokenmiddleware that runs BEFORE the MCP SDK'sclientAuth. The SDK does plaintext compare against the request'sclient_secret; gbrain stores SHA-256 hashes only, so every confidential-client/tokenrequest would fail. The middleware detects confidential auth viaAuthorization: Basicheader ORclient_secret_postform body (both shapes per RFC 6749 §2.3.1), verifies viaverifyClient(client_id, presented_secret)(SHA-256 hash compare), and falls through to the SDK for public PKCE clients (which the SDK's clientAuth still accepts via NULL-client_secret_hashnormalization). Pinned bytest/oauth-confidential-client.test.ts(bothclient_secret_basicandclient_secret_post).src/core/sync.ts:pruneDirextension —pruneDir(name, parentDir?)extended with optionalparentDir. When provided, additionally rejects directories containing.gitas a FILE — the git submodule gitfile pattern (regular repos have.gitas a DIRECTORY; submodules as a file pointing into the parent's.git/modules/). Sync + extract walkers threadparentDirso the gitfile-as-FILE check fires per descend step. Best-effort:statSyncfailures fall through and treat as a normal dir. Closes the phantom-import bug class where syncing a worktree-with-submodules walked into submodule trees. Pinned bytest/sync-walker-submodule.test.ts.src/core/minions/handlers/subagent.tsextension — terminal-state short-circuit on resume. When a stored message thread already ends instop_reason: 'end_turn', the handler returns{ ok: true }immediately instead of issuing anothermessages.createcall (re-prompting pastend_turnwould get a 400 and dead-letter an already-successful job). Pinned bytest/subagent-handler.test.ts.src/commands/doctor.tsextension — three checks wired intorunDoctor()and the JSON envelope, all warn-only with paste-ready fix hints. (1)checkSourceRoutingHealth(engine)scans up to 200 pages on federated brains and flags pages whosesource_iddoesn't match whatresolveSourceWithTier()would have picked for theirsource_path; single-source brains short-circuit took; the 200-page cap is total across the brain so doctor stays under 5s. (2)checkOauthConfidentialHealth(engine)probes registered confidential clients for/tokenreachability. (3)checkAutopilotLockScope()(pure, no engine) compares the resolved lock path to$GBRAIN_HOME; warns when set but the lock lives elsewhere, with a PID-safe inspection hint (kill -0 <pid>before deletion). Pinned bytest/doctor-v0_37_7_checks.test.ts.skills/conventions/brain-routing.md— agent-facing convention skill documenting the canonical 6-tier source resolution chain (flag → env → dotfile → local_path → brain_default → seed_default) with paste-ready decision tables. Linked from CLAUDE.md's "Two organizational axes" section and fromgbrain sources current's hint output.src/commands/doctor.tsextension —buildChecks(engine, args, dbSource): Promise<Check[]>exported as a test seam.runDoctoris a thin wrapper:buildChecks → computeDoctorReport → render + process.exit. All 10process.exitsites stay in the wrapper; the two early-return paths (no engine, connection failure) return partial check lists instead of inline exits (observable output identical). Pinned bytest/doctor-behavioral.test.ts(13 cases: pure aggregation math overcomputeDoctorReport, orchestrator cases for--fastskip set +--jsonflag + no-engine partial path + snapshot of load-bearing check names) andtest/doctor-cli-smoke.serial.test.ts(1 subprocess case spawningbun run src/cli.ts doctor --jsonagainst a fresh PGLite tempdir, asserting schema_version=2 envelope, status enum, non-empty checks array — the render-path coverage buildChecks-only tests miss; quarantined.serialbecause PGLite write-locks don't play with parallel runners).src/core/cycle.tsextension —runPhaseLint+runPhaseBacklinkscarry theexportkeyword so behavioral tests can drive them directly (internal helpers exposed for test-only consumption; downstream code should NOT depend on them). Pinned bytest/cycle-legacy-phases.test.ts(11 cases across both phases: clean run → status='ok', partial fix → status='warn' withdryRunin details, dry-run path doesn't write, throw-from-lib → status='fail' with the wrapper's try/catch envelope populated). Future phase wrappers (sync, extract, embed, orphans, extract_facts, resolve_symbol_edges, recompute_emotional_weight) land as additional describes in the same file.test/operations-trust-boundary.test.ts+scripts/check-operations-filter-bypass.sh— operations trust-boundary contract coverage. Pure assertions over all 74 ops (every op has a scope annotation; every mutating op has a non-read scope;localOnly: trueops are excluded fromoperations.filter(op => !op.localOnly); the seven historically-sensitive localOnly ops snapshot-pinned by name) plus targeted handler-invocation regressions for the two historically-broken HTTP-callable classes:submit_jobwithname='shell'+ctx.remote=trueMUST reject (the HTTP MCP shell-job RCE class), andsearch_by_imagewithimage_path+ctx.remote=trueMUST reject (the P0 image-leak class).file_uploadandsync_brainomitted from handler-invocation tests because they'relocalOnly: true(that path would test an impossible production scenario). The shell guard grepssrc/for any module importing theoperationsvalue outside the canonical filter site atsrc/commands/serve-http.ts(three import shapes: destructured, aliased, namespace), with an explicit 10-entry allow-list + a literal-string check thatserve-http.tsstill containsoperations.filter(op => !op.localOnly). Wired intobun run verify.src/core/content-sanity.ts— pure assessor for the content-sanity defense.assessContent(content, opts): SanityVerdictreturns one ofok | warn_oversize | hard_block_junk_pattern | soft_block_oversizewith{reason, bytes, matched_pattern_name?}. Six built-in junk patterns (Cloudflare challenge dumps, CAPTCHAs, 403 dumps, bare error-page titles) compiled at module load; operator literal substrings vialoadOperatorLiterals()fromsrc/core/content-sanity-literals.ts.ContentSanityBlockErrortagged class is the typed throw shape every wrapper site (gbrain import,put_pageMCP op,gbrain sync,/ingestwebhook) catches via the existing exception flow. The bytes-parity contract pinsBuffer.byteLength(content, 'utf8')against the embedder's actual byte count so a 499K-byte page can't be soft-blocked on assessment then overflow on embed. Knob resolution chain env > file (~/.gbrain/config.json) > DB > defaults. Four knobs:content_sanity.bytes_warn(50_000),content_sanity.bytes_block(500_000),content_sanity.junk_patterns_enabled(true),content_sanity.disabled(false;GBRAIN_NO_SANITY=1is the loud-stderr kill-switch). NewassessContentSanity(opts): SanityAssessmentreturns the three-tier disposition (shouldQuarantine/shouldFlag+ reason/detail) consumed byimportFromContentandgbrain quarantine scan; adds the fuzzy prose-vs-markup ratio pass (markup chars / total abovemax_markup_ratio; code pages exempt; gated byprose_check_enabled) on top of the byte + junk-pattern passes. Three more knobs:content_sanity.junk_disposition(quarantinedefault |reject; no env override — a destructive flip belongs in explicit config),content_sanity.max_markup_ratio(0.85, envGBRAIN_MAX_MARKUP_RATIO, clamped(0,1]),content_sanity.prose_check_enabled(true). Pinned bytest/content-sanity.test.ts.src/core/content-sanity-literals.ts— operator literal-substring loader. Reads~/.gbrain/junk-substrings.txt, one literal per non-comment non-blank line; optional# name=<id>header pairs an identifier with the following literal so audit JSONL groups by site (linkedin_auth_wall,reddit_blocked, etc.). Fail-soft on ENOENT (missing file = empty array). Loaded on every ingest. Deliberately literal substrings (NOT regex) to defeat ReDoS. Pinned bytest/content-sanity-literals.test.ts.src/core/embed-skip.ts— 5-site shared predicate for the soft-block embed-skip filter. ExportsshouldSkipEmbedding(frontmatter): boolean(JS predicate for callers holding the page in memory),EMBED_SKIP_SQL_FRAGMENT(parameterized SQL clause shared by Postgres + PGLite viaexecuteRaw), andbuildEmbedSkipMarker(reason: string)(writesfrontmatter.embed_skip = {at: ISO_TIMESTAMP, reason}so the JSONB shape stays uniform). The 5 sites:embed.ts --stale,embed.ts --all, theembed-staleMinion helper, plus both engines'listStaleChunks+countStaleChunks. Single source of truth so the filter cannot drift. Pinned bytest/embed-skip.test.ts(cross-site invariant + JSONB shape).src/core/audit/content-sanity-audit.ts— ISO-week JSONL audit at~/.gbrain/audit/content-sanity-YYYY-Www.jsonlbuilt on theaudit-writer.tsprimitive. Records every hard-block, soft-block, warn-trip, and quarantine/flag event with{kind, source_id, slug, bytes, matched_pattern_name?, reason, ts}. Doctor reads the last 7 days, aggregates by(matched_pattern_name, source_id)so operators see which scraper is the problem. HonorsGBRAIN_AUDIT_DIRfor shared-filesystem multi-host setups. Pinned bytest/audit/content-sanity-audit.test.ts.src/commands/doctor.tsextension — three checks wired intorunDoctor()and the JSON envelope:oversized_pages(warns on pages exceedingcontent_sanity.bytes_warn),scraper_junk_pages(warns on live DB pages matching any junk pattern that escaped ingest), andcontent_sanity_audit_recent(reads the last 7 days of audit events, aggregates by pattern+source). Default scans the 1000 most-recent pages;--content-auditopts into a full scan. All three warn-only with paste-ready fix hints (junk →gbrain sources audit <id>+git rmsource-of-truth, oversize → split or accept).src/commands/lint.tsextension — lint ruleshuge-page(flags pages exceedingcontent_sanity.bytes_warn) andscraper-junk(flags pages matching any junk pattern). Both reuseassessContent()fromsrc/core/content-sanity.tsso lint, doctor, and ingest share one assessor.lint.tslifts DB config when~/.gbrain/is reachable; falls back to file/env on CI. Pinned bytest/lint-content-sanity.test.ts.src/commands/embed.tsextension — applies theembed-skipfilter at all 5 stale-chunk sites:runEmbedCore --stale,runEmbedCore --all, theembed-staleMinion helper, plus both engines'listStaleChunks+countStaleChunksviaEMBED_SKIP_SQL_FRAGMENT. A soft-blocked page is queryable by title/slug but its chunks never enter the embed sweep. The shared helper fromsrc/core/embed-skip.tsis the regression guard — no per-site ad-hoc filter allowed. Pinned bytest/embed-skip.test.ts.src/core/import-file.tsextension —importFromContentis the narrow waist every ingest path passes through (gbrain import,gbrain sync,put_pageMCP,/ingestwebhook). It runs a three-tier content-quality disposition viaassessContentSanityfromsrc/core/content-sanity.tsBEFORE chunking: (1) high-confidence junk (built-in Cloudflare/CAPTCHA interstitial patterns + operator literals) → QUARANTINE (stamps thequarantinefrontmatter marker, writes ZERO chunks, hides the page from search) OR REJECT (throw → sync-failure) whencontent_sanity.junk_dispositionisreject; (2) fuzzy markup-heavy (prose-vs-markup ratio abovecontent_sanity.max_markup_ratio, warn-tier byte window, code pages exempt) →content_flag:markup_heavymarker (page stays fully searchable, marker rides search results + get_page to warn the agent); (3) oversize →embed_skipsoft-block viabuildEmbedSkipMarker()PLUS acontent_flag:oversizedmarker, AND deletes any pre-existing chunks in the same transaction so search can't surface stale chunks. Gate-owned markers (quarantine,content_flag) are STRIPPED from untrusted (remote MCP,ctx.remote !== false) frontmatter so a write-scoped client can't hide pages or forge the warning channel; markers are excluded fromcontent_hashso a flagged page doesn't re-embed every sync.gbrain importhonorserrors > 0for non-zero exit.classifyErrorCodeinsrc/core/sync.tsrecognizes thePAGE_JUNK_PATTERNcode so sync-failures.jsonl grouping bins these.extractEntityRefs(canonical; matches both[Name](people/slug)markdown links and Obsidian[[people/slug|Name]]wikilinks),extractPageLinks,inferLinkTypeheuristics (attended/works_at/invested_in/founded/advises/source/mentions),parseTimelineEntries,isAutoLinkEnabledconfig helper.DIR_PATTERNcoverspeople,companies,deals,topics,concepts,projects,entities,tech,finance,personal,openclaw. Used by extract.ts, operations.ts auto-link post-hook, and backlinks.ts. Pinned bytest/import-file-content-sanity.test.ts.src/core/quarantine.ts— the two frontmatter markers the content-quality gate writes, sibling ofsrc/core/embed-skip.ts(same marker-as-JSONB-object pattern, same JSONB?existence check that works on Postgres AND PGLite; no schema migration — both are frontmatter JSONB keys).quarantine(keyQUARANTINE_KEY) HIDES: set ONLY for high-confidence junk, writes zero chunks, excluded from search viaquarantineFilterFragment(pageAlias)/QUARANTINE_FILTER_FRAGMENT(thep-aliased constant), the single source of truthbuildVisibilityClausecalls so the search filter and marker key can't drift.content_flag(keyCONTENT_FLAG_KEY) WARNS, does NOT hide: set for fuzzy markup-heavy / oversize, page stays searchable, marker is READ INTO search/get_page output — deliberately NO SQL filter fragment. Three distinct markers, three reasons (never overloaded):embed_skip= oversized-but-clean,quarantine= junk hidden,content_flag= odd-examine-still-here; a page can carry more than one (oversize → embed_skip + content_flag:oversized) and each clears independently. ExportsbuildQuarantineMarker/isQuarantined/filterOutQuarantined,buildContentFlagMarker/getContentFlag/hasContentFlag, plus the two key constants. Pinned bytest/quarantine.test.ts.src/core/content-sanity.tsextension — newassessContentSanity(opts): SanityAssessmentreturns the three-tier disposition (shouldQuarantine/shouldFlag+ reason/detail) consumed byimportFromContentandgbrain quarantine scan. Adds the fuzzy prose-vs-markup ratio pass (markup chars / total chars abovemax_markup_ratio, default 0.85; code pages exempt; gated byprose_check_enabled, default true) on top of the byte + junk-pattern passes. Three config knobs:content_sanity.junk_disposition(quarantinedefault |reject; no env override),content_sanity.max_markup_ratio(0.85, envGBRAIN_MAX_MARKUP_RATIO, clamped(0,1]),content_sanity.prose_check_enabled(true). Same env > file > DB > defaults resolution chain. Pinned bytest/content-sanity.test.ts.src/commands/quarantine.ts—gbrain quarantine <list|clear|scan>operator surface for the content-quality gate.list [--json] [--include-flagged]paginateslistPagesand reports quarantined (HIDDEN) pages, optionally alsocontent_flag(FLAGGED, searchable) pages.clear <slug> [--force] [--no-embed] [--json]drops both markers and re-imports through the normal pipeline so the page re-chunks + re-embeds and becomes searchable; the gate re-runs on import so genuinely-junk pages re-quarantine (exit 1) unless--forcesetsGBRAIN_NO_SANITY=1for that one import.scan [--limit N] [--apply] [--no-embed] [--json]re-assesses already-ingested pages so junk predating the gate gets marked (unchanged content short-circuits normal sync, so it never re-assesses otherwise); dry-run uses the SAME effectivecontent_sanityconfig thresholds--applywill use, idempotent (skips already-marked pages),--applyre-imports withforceRechunkto set markers + (for quarantine) drop chunks. Dispatched incli.ts. Pinned bytest/quarantine-cli.test.ts.src/core/search/hybrid.ts+src/core/search/sql-ranking.ts+src/core/operations.ts+src/core/types.tsextensions — agent-warning channel.SearchResult.content_flag?: {reason, detail}(new optional field intypes.ts) is stamped post-fusion bystampContentFlags(thestampEvidenceprecedent) inhybridSearchAND in the keyword-onlysearchMCP op so both retrieval paths surface the marker.get_pagereturns a top-levelcontent_flagparallel field viagetContentFlag(page.frontmatter).buildVisibilityClause(sql-ranking.ts) ANDs inQUARANTINE_FILTER_FRAGMENTso quarantined pages are excluded from all six search call sites (alongside soft-delete + archived-source filters). Pinned bytest/sql-ranking.test.ts+test/e2e/quarantine-search-exclusion.test.ts.src/commands/doctor.tsextension — two checks wired intorunDoctor()+ the JSON envelope:quarantined_pages(counts pages carrying thequarantinemarker viaengine.executeRawJSONB?existence, works on PGLite + Postgres; warn-only with agbrain quarantine listhint) andflagged_pages(countscontent_flagpages — searchable but odd; warn-only). Both skip gracefully (status ok, "Skipped") on engines/brains where the probe errors. Pinned bytest/doctor.test.ts.src/commands/lint.ts+src/commands/sources.tsextensions —gbrain lintgains amarkup-heavyrule (flags pages whose prose-vs-markup ratio exceedscontent_sanity.max_markup_ratio, reusingassessContentSanityso lint/gate/scan share one assessor); pinned bytest/lint-content-sanity.test.ts.gbrain sources audit <id>becomes disposition-aware: its dry-run disk scan reports would-quarantine / would-reject / would-flag counts driven by the effectivecontent_sanity.junk_disposition+ markup config, so an operator previews the gate's verdict before sync. Thecontent-sanity-auditJSONL (src/core/audit/content-sanity-audit.ts) records the new quarantine/flag dispositions.src/core/zombie-reap.ts— idempotentinstallSigchldHandler()so JS-spawned children get reaped via Bun's internalwaitpid(). Bun (like Node) only auto-reaps when a SIGCHLD listener is registered; without it, every child the worker spawns (shell jobs, embed batches, sub-agents) becomes a zombie on exit and holds connection slots. Called once at module load fromsrc/cli.ts(Windows platform guard — SIGCHLD doesn't exist on Windows). Cross-file leak guard via_uninstallSigchldHandlerForTests(). Layer 1 of the three-layer zombie defense; Layer 2 is tini-as-PID-1 wrapping the worker subtree (viasrc/core/minions/spawn-helpers.ts); Layer 3 is the container's own tini for hard Bun crashes.src/core/minions/— Minions job queue: BullMQ-inspired, Postgres-native (queue, worker, backoff, types, protected-names, quiet-hours, stagger, handlers/shell).src/core/minions/queue.ts— MinionQueue class (submit, claim, complete, fail, stall detection, parent-child, depth/child-cap, per-job timeouts, cascade-kill, attachments, idempotency keys, child_done inbox, removeOnComplete/Fail).add()takes a 4thtrustedarg (separate fromoptsto prevent spread leakage); protected names inPROTECTED_JOB_NAMESrequire{allowProtectedSubmit: true}and the check runs trim-normalized (whitespace-bypass safe).add()plumbsmax_stalledthrough with a[1, 100]clamp; omitted values let the schema DEFAULT (5) kick in.handleWallClockTimeouts(lockDurationMs)is Layer 3 kill shot for jobs whereFOR UPDATE SKIP LOCKEDstall detection and the timeout sweep both fail to evict (wedged worker holding a row lock via a pending transaction). ThemaxWaitingcoalesce path usespg_advisory_xact_lockkeyed on(name, queue)to serialize concurrent submits for the same key, and filters onqueuein addition tonameso cross-queue same-name jobs don't suppress each other.claimandrenewLockissue their UPDATE viaengine.executeRawDirect(notexecuteRaw) so the lock heartbeat runs on the direct session-mode pool that the transaction pooler won't recycle mid-hold; on PGLite this is identical toexecuteRaw. The two terminal dead-letter paths (handleWallClockTimeoutswall-clock kill and the stall dead-letter CTE in the stall sweep) BOTH incrementattempts_madeso a long job killed there reads as an honest attempt instead ofattempts 0 / started N; the stall path also bumpsstalled_counter, surfaced bygbrain jobs getasAttempts: M/N (started: X, stalled: S/MaxS). At submit,add()stamps a defaulttimeout_msviadefaultTimeoutMsFor(jobName)(fromhandler-timeouts.ts) when the caller passed none, so long handlers aren't wall-clock-killed mid-progress by the short null-default; an explicitopts.timeout_msalways wins. Guarded bytest/queue-lock-retry.test.ts(claim never falls back toexecuteRaw),test/postgres-execute-raw-direct.test.ts(routing decision matrix), andtest/minions.test.ts(attempt accounting + default-timeout stamping).src/core/minions/worker.ts— MinionWorker class (handler registry, lock renewal, graceful shutdown, timeout safety net). Aborted jobs callfailJobwith reason (timeout/cancel/lock-lost/shutdown);shutdownAbort(instance field) fires on SIGTERM/SIGINT and propagates toctx.shutdownSignal(shell handler listens; non-shell handlers don't). Per-job timeout firesabort.abort(new Error('timeout'))then a 30s grace-then-evict safety net force-evicts the job frominFlightand marks it dead if the handler ignores the abort signal (the moved-out generic abort listener now fires for ANY abort reason). ThelaunchJoblock-renewal block is a thin sync wrapper around the purerunLockRenewalTickfromsrc/core/minions/lock-renewal-tick.ts(NEVERsetInterval(async () => await renewLock(...))— that shape was the unhandledRejection crash class during PgBouncer rotation). Gaps closed: (1)cancelledflag captured in the timer closure stops in-flight IIFEs writing misleading audit events after the job ended; (2) re-entrancy guardtickInFlight+ per-callPromise.racetimeout (GBRAIN_LOCK_RENEWAL_CALL_TIMEOUT_MS, defaultlockDuration/3); (3) time-based abort (Date.now() - lastSuccessfulRenewalAt >= lockDuration - GBRAIN_LOCK_RENEWAL_SAFETY_MARGIN_MS) so we release the lock BEFORE another worker can reclaim; (4) explicit.catch()on the storedexecuteJob(...).finally(...)promise closes the second unhandledRejection vector; (5) exportedINFRASTRUCTURE_ABORT_REASONS = new Set(['lock-renewal-failed', 'lock-lost'])so executeJob's catch skipsfailJobfor these (PgBouncer blips don't dead-letter healthy jobs; the stall detector reclaims). CI guardscripts/check-worker-lock-renewal-shape.sh(inbun run verify) asserts the bug pattern stays absent ANDlaunchJobkeeps callingrunLockRenewalTick. Engine-ownership invariant:start()does NOT callengine.disconnect()on shutdown — the CLI handler insrc/commands/jobs.ts case 'work'owns engine lifecycle via try/finally with loud error logging. RSS watchdog uses non-file-backed pages on Linux: exportedparseRssFromProcStatus(status)(pure parser; field-presence regex soRssAnon: 0 + RssShmem: 512parses correctly) andgetAccurateRss(readStatus?)(reads/proc/self/statusforRssAnon + RssShmem, falls back toprocess.memoryUsage().rsson macOS / restricted containers / kernel <4.5); the defaultgetRssinWorkerOptsisgetAccurateRss.checkMemoryLimittracks peak RSS, fires an 80%-of-cap soft-warn (once per crossing, carrying peak + in-flight job kinds), and on exceed sets_rssWatchdogTriggered=true(exposed viaget rssWatchdogTriggered()) sojobs work's finally canprocess.exit(WORKER_EXIT_RSS_WATCHDOG)after disconnect (drain self-identifying instead of an opaque code-0 exit). The poll loop wrapsclaimin try/catch: on a retryable conn error it reconnects ONCE and continues to the next tick rather than blind-retrying (a retry afterUPDATE...RETURNINGcommitted but the socket died would double-claim).LockRenewalDepsis wired withreconnectwhen the engine supports it. The self-health DB-liveness probe now runs EVEN under a supervisor (GBRAIN_SUPERVISED=1): the outer guard isif (this.opts.healthCheckInterval > 0)and only the STALL-detection block is wrapped inif (!isSupervisedChild)— so a supervised worker whose own pool dies self-exitsunhealthy(db_dead)afterdbFailExitAfterprobes (the supervisor watches a different connection and can't see this worker's dead pool), while the supervisor's progress watchdog owns forward-progress (#1801). Pinned bytest/worker-lock-renewal.test.ts(18),test/audit/lock-renewal-audit.test.ts(11),test/scripts/check-worker-lock-renewal-shape.test.ts(5),test/worker-shutdown-disconnect.test.ts(assertsdisconnectSpy).not.toHaveBeenCalled()),test/worker-rss.test.ts(11),test/worker-supervised-db-probe.test.ts(3).src/core/minions/supervisor.ts— MinionSupervisor process manager. Spawnsgbrain jobs workas a child, restarts on crash with exponential backoff, periodic health check.consecutiveHealthFailurescounter; on 3 consecutive failures emitshealth_warnwithreason: 'db_connection_degraded'and callsengine.reconnect()to swap in a fresh pool, then resets. Worker exit classifier emitslikely_causeonworker_exitedevents:oom_or_external_kill(SIGKILL),graceful_shutdown(SIGTERM),runtime_error(code 1),clean_exit(code 0),unknown. ConsumesdetectTini()+buildSpawnInvocation()fromsrc/core/minions/spawn-helpers.tsto wrap the worker subtree in tini-as-PID-1 when tini is onPATH(handles native-addon zombie reaping the in-process SIGCHLD reaper can't reach); exposesisTiniDetectedread-only accessor. The spawn-and-respawn loop is the sharedChildWorkerSupervisorcore: MinionSupervisor composes it viarunSuperviseLoop()→new ChildWorkerSupervisor({...})and mapsChildSupervisorEventback throughemit()SupervisorEvent (JSONL audit consumers see byte-compatible output). PID lock, signal handlers, health check, andprocess.exiton max-crashes stay in MinionSupervisor.code=0leavescrashCountuntouched (so a worker alternating real crashes + watchdog drains still tripsmax_crashes);cleanRestartBudget(default 10 restarts per 60s) caps the macOS/non-Linux-fallback tight-loop viahealth_warn { reason: 'clean_restart_budget_exceeded' }+ backoff.shutdown()drains viachildSupervisor.killChild('SIGTERM')+awaitChildExit(35_000). Progress watchdog (#1801):healthCheck()restarts an alive-but-wedged child viachildSupervisor.restartCurrentChild(35_000)when a queue has claimable work, 0 live-lock active jobs, and stale completions acrosswedgeRestartChecks(default 3) consecutive checks pastwedgeRestartMinutes(default 15, 0 disables) + astartupGraceMswindow; bounded bywedgeRestartLoopBudget(default 3 /wedgeRestartLoopWindowMs) which switches to a one-shotwedge_restart_loopalert. The wedge query is the exportedqueryWedgeSignals(engine, queue, handlerNames)— name+queue-scoped,active_healthy= live-lock only (an expired-lock active row does NOT mask the wedge), due-delayed counted. Claimable names are derived at start via a throwawayregisterBuiltinHandlersworker (its newquietopt). Flags--wedge-restart-minutes/--wedge-restart-checks+ envGBRAIN_WEDGE_RESTART_MINUTES/GBRAIN_WEDGE_RESTART_CHECKS. The worker argv is built by the exported purebuildWorkerArgs(opts)(appends--nice Nwhenopts.nice_requestedis set, alongside--concurrency/--queue/--max-rss); the niceness apply RESULT (nice_requested/nice_effective/nice_error, computed by the CLI in jobs.ts — the supervisor doesn't call setPriority) rides on thestarted/worker_spawnedaudit emissions (#1815). Queue-scoped singleton (#1849): the real authority is a DB lock (tryAcquireDbLockfromsrc/core/db-lock.ts) keyed onsupervisorLockId(queue)=gbrain-supervisor:<queue>— keyed on the QUEUE ALONE because the lock row lives inside the target database, so the (database) half of the mutex is physical, not part of the key (an earlier revision mixed in a config-derived DB identity, which let two supervisors on the same physical DB via different-but-equivalent URLs compute different ids and both acquire — fixed). Two supervisors with different$HOME/--pid-fileagainst the same(database, queue)no longer both run with conflicting--max-rss; the second exitsLOCK_HELD. The pidfile-cleanupprocess.on('exit')listener is installed BEFORE the DB-lock acquisition so theLOCK_HELDearly-exit can't strand the pidfile this process just created. The default pidfile is now brain-scoped (supervisor-<brainId>.pid) so different brains under one HOME don't false-block. The lock refreshes on its ownsetInterval(TTL 5min, refresh 60s, max 3 failures = 180s < TTL); a refresh that fails past the threshold exitsLOCK_LOST(code 4) rather than risk a split-brain.shutdown()releases the lock so a clean restart re-acquires immediately. Thestartedaudit now recordsmax_rss_mbsogbrain doctor'ssupervisor_singletoncheck can surface the effective cap. ExportssupervisorLockId()and the pureclassifySupervisorSingleton({lockLive, lockHolderHost, lockHolderPid, localHost, localPid}) → 'no_lock'|'single'|'mismatch'(host+pid compare, bare pid meaningless cross-host) that doctor consumes. Pinned bytest/supervisor.test.ts(16 cases),test/supervisor-tini.test.ts,test/supervisor-wedge.test.ts,test/supervisor-build-worker-args.test.ts, andtest/supervisor-db-lock.test.ts.src/core/minions/child-worker-supervisor.ts— shared spawn-and-respawn core reused by bothMinionSupervisor(standalonegbrain jobs supervisordaemon) andsrc/commands/autopilot.ts(autopilot daemon), so the two consumers can't drift into parallel-loop bugs. Pure class: NO PID file, NO signal handlers, NOprocess.exit, NO health check. Lifecycle events fire via injectedonEvent: (ChildSupervisorEvent) => void. Exit classifier:code === 0leavescrashCountUNCHANGED (preserves flap detection across mixed exit sequences);code != 0followsrunDuration > stableRunResetMs ? 1 : ++crashCount. Clean-restart budget: sliding window of code=0 exits; when count exceedscleanRestartBudget(default 10) insidecleanRestartWindowMs(default 60s), emitshealth_warn { reason: 'clean_restart_budget_exceeded' }and appliescleanRestartBudgetBackoffMs(default 1s). The exit classifier special-casesWORKER_EXIT_RSS_WATCHDOG—likely_cause='rss_watchdog', and that exit does NOT bumpcrashCount(routes to its own breaker); a dedicated_watchdogExitTimestampssliding window trips a loudrss_watchdog_loophealth_warn naming the cap when N watchdog exits land inside the window, INDEPENDENT of the stable-run reset (which would otherwise hide a >5-min-run watchdog drain loop). New optswatchdogLoopBudget(3),watchdogLoopWindowMs(600000),watchdogBackoffMs(30000);ChildSupervisorEventextended. Public read-only accessorschildAlive,inBackoff,crashCount;killChild(signal)gates on liveness (exitCode === null && signalCode === null), NOT.killed—.killedflips true once a signal is sent, so the old!this._child.killedguard made a follow-up SIGKILL after an ignored SIGTERM a silent no-op (#1801; the bug also lived in theshutdown()drain).restartCurrentChild(graceMs)(#1801 wedge self-heal) captures the CURRENT child ref, SIGTERM→grace→SIGKILLs THAT ref (never the respawn — closes the timer-kills-fresh-worker race), and flags_intentionalRestartso the exit islikelyCause='wedge_restart', leavescrashCountUNTOUCHED (never tripsmax_crashes; likerss_watchdog), and respawns immediately (backoff ms:0 reason='wedge_restart').awaitChildExit(timeoutMs)short-circuits whenchild.exitCode !== null || child.signalCode !== nullso fast-SIGTERM responders don't cause a 35s shutdown hang. Test hooks_backoffFloorMs,_now.supervisor-audit.tsaddsrss_watchdogas a non-clean cause + its ownCrashSummary.by_causebucket, andwedge_restarttoCLEAN_EXIT_CAUSES(a self-heal, not a crash; denylist preserved so future causes route tolegacy). Pinned bytest/child-worker-supervisor.test.ts(12 cases).src/core/minions/spawn-helpers.ts— puredetectTini()+buildSpawnInvocation()consumed by bothsupervisor.tsandautopilot.ts(resolves the DRY violation between the two spawn sites and makes tini wrapping testable withoutmock.module(), rule R2 ofscripts/check-test-isolation.sh).detectTini()callsexecFileSync('which', ['tini'])with explicitenv: process.envso Bun sees runtime PATH mutations.buildSpawnInvocation(tiniPath, cmd, args)returns{cmd, args}with tini prepended when present, or the bare invocation otherwise. Pinned bytest/spawn-helpers.test.ts(5) andtest/supervisor-tini.test.ts(4).src/core/minions/niceness.ts— OS scheduling-priority (niceness) primitives for the--niceflag.parseNiceValue(raw)whole-string parses + range-validates to POSIX[-20, 19](rejects"3.5"/"10abc"thatparseIntwould silently truncate).applyNiceness(nice, setPriority?, getPriority?)callsos.setPriority(0, n)and ALWAYS re-readsos.getPriority(0)afterwards — in both the success and the catch paths — so a denied renice (EPERM) or anRLIMIT_NICEclamp records the real effective value (e.g.0), not null; returns{applied, requested, effective, error?}.getEffectiveNiceness(pid, getPriority?)reads an arbitrary pid's niceness (null on dead/unreadable).formatNice(n)→+10/0/-5. Applied only at the CLI layer (jobs.ts) so worker.ts/supervisor.ts stay embeddable. Pinned bytest/niceness.test.ts.src/core/minions/worker-registry.ts— live worker registry backing niceness observability. Each runninggbrain jobs workself-registersworker-<pid>.jsonundergbrainPath('workers')(brain-isolated viaGBRAIN_HOME; entries tagged withcurrentBrainId()so multiple DBs under one home don't cross-report).registerWorker(info)is best-effort (never blocks the worker) and returns a cleanup fn the caller wires to BOTH the shutdownfinallyANDprocess.on('exit')(the unhealthyprocess.exit(1)bypasses the awaited finally).readWorkers(getNice?)enumerates the dir, drops confirmed-dead pids (classifyLiveness: ESRCH = dead/prune, EPERM = alive/keep), applies a pid-reuse start-time guard (ps -o lstart, rejects a pid whose process started >5s after the entry was written), and re-measures each live worker's niceness now. Reports the worker's REAL pid, sidestepping the tini-wrapper-pid problem. Pinned bytest/worker-registry.test.ts.src/core/minions/supervisor-pid.ts—readSupervisorPid(pidFile) → {pid, running}: the sharedexistsSync → readFileSync → parseInt → process.kill(pid,0)PID-file + liveness reader extracted from the three copies injobs.ts(supervisor status),jobs.ts(stats), anddoctor.ts. EPERM from the liveness probe counts as running. Pinned bytest/supervisor-pid.test.ts.src/core/minions/handler-timeouts.ts(#1737) — per-handler default wall-clock budgets.HANDLER_DEFAULT_TIMEOUT_MSmaps the long handlers (subagent,subagent_aggregator,embed-backfill,autopilot-cycle) to a 30-min default (the valuecycle/patterns.tsalready passed for subagents, generalized).defaultTimeoutMsFor(jobName)returns that default ornullfor short handlers (keep the tight null-default wall-clock).MinionQueue.add()stamps this ontominion_jobs.timeout_msat submit time when the caller passed notimeout_ms, so a long job submitted without one isn't wall-clock-killed mid-progress; an explicit value always wins and already-queued jobs are NOT backfilled. Pinned bytest/minions.test.ts.src/core/minions/types.ts—MinionJobInput+MinionJobStatus+ handler context types.MinionJobInput.max_stalledis optional; omitted values let the schema DEFAULT (5) kick in, provided values are clamped to[1, 100].src/core/minions/protected-names.ts— side-effect-free constant module exportingPROTECTED_JOB_NAMES+isProtectedJobName(). Kept pure so queue core can import without loading handler modules.src/core/minions/handlers/shell.ts—shelljob handler. Spawns/bin/sh -c cmd(absolute path, PATH-override-safe) orargv[0] argv[1..](no shell). Env allowlistPATH, HOME, USER, LANG, TZ, NODE_ENV+ callerenv:overrides +inherit:-resolved keys. UTF-8-safe stdout/stderr tail viastring_decoder.StringDecoder. Abort (eitherctx.signalorctx.shutdownSignal) fires SIGTERM → 5s grace → SIGKILL on child. RequiresGBRAIN_ALLOW_SHELL_JOBS=1on worker (gated byregisterBuiltinHandlers).ShellJobParams.inherit?: string[]is a free-form list of snake_case config-key names; the worker resolves each vialoadConfig()and injects the value under the derived env key (database_url→GBRAIN_DATABASE_URL; else uppercased). Names persist inminion_jobs.data(and the shell-audit JSONL); values never do. The canonical validatorvalidateShellJobParams(siblingshell-validate.ts) runs PRE-ENQUEUE in both submit surfaces —gbrain jobs submit shell(jobs.ts:271) AND thesubmit_jobop forname='shell'(operations.ts:2085); the handler-entry re-validation here is defense-in-depth (closes the bug class where validation ran AFTERqueue.add()persisted the row). The validator does NOT police which config keys the agent inherits — same-uid trust model treats the agent as a peer of the worker.src/core/minions/handlers/shell-inherit.ts— three helpers.INHERIT_NAME_RE(/^[a-z][a-z0-9_]*$/) is the snake_case shape guard used by the validator; rejects__proto__, leading-underscore, uppercase, and path-traversal shapes so audit logs stay readable and prototype-pollution lookups can't smuggle through.deriveEnvKey(name)maps config-key → child-env-key (name.toUpperCase()with one override:database_url→GBRAIN_DATABASE_URLbecause plainDATABASE_URLis ambiguous).resolveInheritValue(cfg, name)is the value lookup; usesObject.hasOwnto defeat prototype-pollution lookups, returns undefined for missing / non-string / empty-string values. No closed enum — agent and worker share a uid, so refusing arbitrary config keys defends nothing in that trust model.src/core/minions/handlers/shell-validate.ts—validateShellJobParams(data, opts?)shared pre-enqueue validator. ThrowsUnrecoverableErrorwith paste-ready operator hints on every failure. Three rules: (1) cmd/argv/cwd/env shape, (2) inherit array shape + snake_case regex per element (prototype-pollution defense), (3) fail-fast on missing config value withgbrain config set <key>hint. Optionalredact_secrets?: booleanfor output-side scrubbing. Deliberately does NOT police WHICH secrets the agent passes — single-uid trust model. Test seam:opts.configdrives the validator hermetically without mocking. Re-called atshell.tshandler entry for defense-in-depth (catches rows submitted before the pre-enqueue validator existed).src/core/minions/handlers/shell-redact.ts— opt-in output-side scrubbing for shell-job stdout/stderr. PureredactSecretsInText(text, secrets): string-modereplaceAllso regex metacharacters in values stay literal. When the caller passesredact_secrets: true(or--redact-secrets), the handler builds a Map of inherit-name → resolved-value and post-processes both tails before throw/return so persistedresult.stdout_tail/result.stderr_tail/error_textcarry<REDACTED:name>. Onlyinherit:-resolved values are scrubbed; caller-suppliedenv:values pass through. Heuristic — defeatsecho "$GBRAIN_DATABASE_URL", not adversarial encode-then-print. Defaultfalse.src/core/config.ts:ensureGitignore— idempotent retroactive writer of~/.gbrain/.gitignore(single line*). Called fromsaveConfig()so every config-writing path lays it down, AND fromrunPostUpgrade()so existing users pick it up ongbrain upgrade. Never clobbers a user-customized.gitignore(checks file exists + content non-empty before writing). Scope: blocks casualgit add ~/.gbrainfrom inside an enclosing worktree, but does NOT cover already-tracked files, screenshots, backups (Time Machine / iCloud / Dropbox), orgit add -f. The doctor checkhome_dir_in_worktreesurfaces what.gitignorecan't.src/commands/doctor.ts:home_dir_in_worktree— filesystem check walking up fromgbrainPath()toward$HOMElooking for a.gitdirectory (main repo) or.gitfile (linked worktree pointer; Conductor + git-worktrees topology). Walk terminates at$HOMEso a.gitabove the user's home doesn't false-positive. HonorsGBRAIN_HOME(appends.gbrainto the override). Warn (not fail) with worktree-root path + paste-ready fix pointing atGBRAIN_HOMEoverride or moving the brain.src/core/minions/handlers/shell-audit.ts— per-submission JSONL audit trail at~/.gbrain/audit/shell-jobs-YYYY-Www.jsonl(ISO-week rotation; override viaGBRAIN_AUDIT_DIR). Best-effort:mkdirSync(recursive)+appendFileSync; failures logged to stderr, submission not blocked. Logs cmd (first 80 chars) or argv (JSON array). Never logs env values.src/core/minions/handlers/supervisor-audit.ts— supervisor lifecycle JSONL audit at~/.gbrain/audit/supervisor-YYYY-Www.jsonl(ISO-week rotation; sharescomputeIsoWeekName()withshell-audit.ts).writeSupervisorEvent(emission, supervisorPid)appends one line per event (started,worker_spawned,worker_exited,backoff,health_warn,health_error,max_crashes_exceeded,shutting_down,stopped,worker_spawn_failed).readSupervisorEvents({sinceMs})is the readback forgbrain doctor. ExportsisCrashExit(event),summarizeCrashes(events),CrashSummarytype, andCLEAN_EXIT_CAUSESdenylist ('clean_exit' | 'graceful_shutdown'). Single regression point — bothgbrain doctor(supervisor check atdoctor.ts:1011-1043) andgbrain jobs supervisor status(jobs.ts:803-826) import from here so the two surfaces can't drift.isCrashExitclassifies a singleworker_exitedagainst the denylist: clean/graceful are NON-crashes; everything else (incl. any futurelikely_causefromchild-worker-supervisor.ts) is a crash; audit lines lackinglikely_causefall back tocode !== 0.summarizeCrashesreturns{total, by_cause: {runtime_error, oom_or_external_kill, unknown, legacy}, clean_exits}— thelegacybucket catches both old fallback entries AND unrecognized future causes (fail-loud, not silent underreport); denylist-over-allowlist is deliberate. Pinned bytest/supervisor-audit.test.ts(14 cases) and 4 source-grep wiring assertions intest/doctor.test.ts.src/core/minions/backpressure-audit.ts— sibling of shell-audit.ts formaxWaitingcoalesce events. JSONL at~/.gbrain/audit/backpressure-YYYY-Www.jsonl. One line per coalesce with(queue, name, waiting_count, max_waiting, returned_job_id, ts). Closes the silent-drop vector the maxWaiting guard introduced.src/core/minions/handlers/subagent.ts— LLM-loop handler. Two-phase tool persistence (pending → complete/failed), replay reconciliation for mid-dispatch crashes, dual-signal abort (ctx.signal+ctx.shutdownSignal), Anthropic prompt caching on system + tool defs.makeSubagentHandler({engine, client?, ...})factory;MessagesClientis an injectable interface the real SDK implements structurally. ThrowsRateLeaseUnavailableError(renewable) when rate-lease capacity is full. Anthropic 400prompt is too longresponses (status 400 + body matches/prompt is too long|prompt_too_long|context.*length/i) classify asUnrecoverableErrorso the job goes straight todeadon first attempt instead of stalling three times. Catches both initial-prompt overflow and turn-N tool-loop accumulation thatsynthesize.ts's chunker can't bound ahead of time.src/core/minions/handlers/subagent-aggregator.ts—subagent_aggregatorhandler. Claims AFTER all children resolve (queue guarantees every terminal child posts achild_doneinbox message with outcome). Reads inbox viactx.readInbox(), builds a deterministic mixed-outcome markdown summary. No LLM call.src/core/minions/handlers/subagent-audit.ts— JSONL audit + heartbeat writer at~/.gbrain/audit/subagent-jobs-YYYY-Www.jsonl. Events:submission(one per submit) +heartbeat(per turn boundary:llm_call_started | llm_call_completed | tool_called | tool_result | tool_failed). Never logs prompts or tool inputs.readSubagentAuditForJob(jobId, {sinceIso})is the readback forgbrain agent logs.src/core/minions/rate-leases.ts— lease-based concurrency cap for outbound providers (default keyanthropic:messages, max viaGBRAIN_ANTHROPIC_MAX_INFLIGHT). Owner-tagged rows withexpires_atauto-prune on acquire;pg_advisory_xact_lockguards check-then-insert; CASCADE on owning job deletion.renewLeaseWithBackoffretries 3x (250/500/1000ms).src/core/minions/wait-for-completion.ts— poll-until-terminal helper for CLI callers.TimeoutErrordoes NOT cancel the job;AbortSignalexits without throwing. DefaultpollMs: 1000 on Postgres, 250 on PGLite inline.src/core/minions/transcript.ts— renderssubagent_messages+subagent_tool_executionsto markdown. Tool rows splice under their owning assistanttool_usebytool_use_id. UTF-8-safe truncation; unknown block types fall through to fenced JSON.src/core/minions/plugin-loader.ts—GBRAIN_PLUGIN_PATHdiscovery. Absolute paths only, left-wins collision,gbrain.plugin.jsonwithplugin_version: "gbrain-plugin-v1", plugins ship DEFS only (no new tools),allowed_tools:validated at load time against the derived registry.src/core/minions/tools/brain-allowlist.ts— derives the subagent tool registry fromsrc/core/operations.ts(13-name allow-list). By defaultput_pageschema is namespace-wrapped per subagent (^wiki/agents/<subagentId>/.+). WhenBuildBrainToolsOpts.allowedSlugPrefixesis set, the put_page schema describes the prefix list to the model and the OperationContext is threaded withallowedSlugPrefixes— trust comes fromPROTECTED_JOB_NAMESgating subagent submission (MCP cannot reach this field); only cycle.ts (synthesize/patterns) and direct CLI submitters set it. Allow-list includesget_recent_salience+find_anomaliesbut deliberately NOTget_recent_transcripts(all subagent calls runctx.remote === trueand the trust gate rejects remote callers, so it would always reject; the cycle synthesize phase callsdiscoverTranscriptsdirectly instead).paramsToInputSchema()consumesparamDefToSchemafromsrc/mcp/tool-defs.ts; required-aggregation at the tool-def level stays here (the shared helper is per-param).src/mcp/tool-defs.ts—buildToolDefs(ops)helper; MCP server + subagent tool registry both call it, byte-for-byte equivalence pinned bytest/mcp-tool-defs.test.ts. Exports the recursiveparamDefToSchema(p: ParamDef)— single source of truth for ParamDef→JSON Schema mapping shared by three consumers:buildToolDefs(stdio MCP),src/commands/serve-http.ts:837(HTTP MCPtools/list), andsrc/core/minions/tools/brain-allowlist.ts:84(subagent registry). Recursive onitemsso nested array-of-arrays preserves inner shape on the wire. Key ordering (type, description, enum, default, items) is intentional soJSON.stringifyoutput stays byte-stable.test/mcp-tool-defs.test.tshas afindArrayWithoutItemswalker that fails on anytype: 'array'lackingitems.type.src/core/minions/attachments.ts— Attachment validation (path traversal, null byte, oversize, base64, duplicate detection).src/commands/agent.ts—gbrain agent run <prompt> [flags]CLI. Submitssubagent(or N children + 1 aggregator) under{allowProtectedSubmit: true}. Single-entry--fanout-manifestshort-circuits. Children geton_child_fail: 'continue'+max_stalled: 3.--followis the default on TTY; streams logs + pollswaitForCompletionin parallel. Ctrl-C detaches, does not cancel.src/commands/agent-logs.ts—gbrain agent logs <job> [--follow] [--since]. Merges JSONL heartbeat audit +subagent_messagesinto a chronological timeline.parseSinceaccepts ISO-8601 or relative (5m,1h,2d). Transcript tail renders only for terminal jobs.src/commands/jobs.ts—gbrain jobsCLI subcommands +gbrain jobs workdaemon.case 'work'wrapsworker.start()in try/finally and owns engine lifecycle — callsengine.disconnect()on shutdown with loud error logging (the worker must not disconnect an engine it doesn't own; pool slots free immediately on shutdown rather than waiting for TCP keepalive).jobs submitsurfaces the fullMinionJobInputretry/backoff/timeout/idempotency surface as flags:--max-stalled,--backoff-type fixed|exponential,--backoff-delay,--backoff-jitter,--timeout-ms,--idempotency-key.jobs smoke --sigkill-rescueis the SIGKILL-rescue regression guard.registerBuiltinHandlersalways registerssubagent+subagent_aggregator(no env flag —ANTHROPIC_API_KEYis the cost gate, trust is viaPROTECTED_JOB_NAMES) and loadsGBRAIN_PLUGIN_PATHplugins at startup with a loud per-plugin line;shellhandler still gated byGBRAIN_ALLOW_SHELL_JOBS=1(RCE surface). Theautopilot-cyclehandler forwardsjob.data.phasestorunCycle, validated againstALL_PHASESfromsrc/core/cycle.ts(invalid names filtered; empty/missing falls back to the default cycle). Thesynchandler resolvessourceIdat entry fromsources.local_path(mirrorscycle.ts:480) so multi-source brains read the per-sourcelast_commitanchor; concurrency routes throughautoConcurrency()insrc/core/sync-concurrency.ts(PGLite stays serial);noEmbeddefault istrue.gbrain jobs supervisor statusatjobs.ts:803-826consumessummarizeCrashes()fromsrc/core/minions/handlers/supervisor-audit.tsfor parity withgbrain doctor: JSON addscrashes_by_cause: {runtime_error, oom_or_external_kill, unknown, legacy}+clean_exits_24h; human output gains per-cause + clean-exits lines. Pinned by 4 source-grep wiring assertions intest/doctor.test.tsrequiringcrashes_by_cause+clean_exits_24h=in bothdoctor.tsandjobs.ts.gbrain jobs watchdecouples its two output axes:--jsonpicks FORMAT (human default, never gated on isTTY),--followpicks LOOP (defaultisTTY && !json). Non-TTY with no flags prints ONE human snapshot then exits (clean for subagent/pipe/cron);--followopts into a continuous stream (human plain per tick, or JSONL with--json); a TTY with no flags keeps the live ANSI dashboard. Resolution is the pureresolveWatchMode(opts, isTTY): {json, follow, useAnsiDashboard}insrc/commands/jobs-watch.ts; the dispatch wires--follow. Pinned bytest/jobs-watch-mode.test.ts(format×loop matrix incl. the TTY+--json-one-shot case) +test/e2e/non-tty-output.serial.test.ts(thecmd </dev/nullnon-empty-stdout contract).src/commands/features.ts—gbrain features --json --auto-fix: usage scan + feature adoption salesman.src/commands/autopilot.ts—gbrain autopilot --install: self-maintaining brain daemon (sync+extract+embed). ConsumesdetectTini()fromsrc/core/minions/spawn-helpers.ts, resolved once at startup. Composes aChildWorkerSupervisorinstance for spawn-and-respawn (no inlinecrashCount/startWorker/child.on('exit'));--max-rss 2048andmaxCrashes: 5preserved.onMaxCrashesExceededroutes through autopilot's ownshutdown('max_crashes')so the autopilot lockfile gets cleaned up.shutdown()drains viachildSupervisor.killChild('SIGTERM')+awaitChildExit(35_000). Pinned bytest/autopilot-supervisor-wiring.test.ts(6 static-shape guards: composes ChildWorkerSupervisor not legacy names,--max-rss 2048in argv,maxCrashes: 5literal, shutdown-via-callback, no workerProc reference).src/mcp/server.ts— MCP stdio server (generated from operations). Tool-call handler delegates todispatchToolCallfromsrc/mcp/dispatch.tsso stdio + HTTP transports share one validation, context-build, and error-format path. Stdin'end'/'close'shutdown hooks are skipped whenprocess.env.MCP_STDIO === '1'— gateway-piped stdio MCP wrappers (OpenClaw'sbundle-mcp) pipe the handshake then close their stdin half, which would otherwise kill the server before the first tool call; signal handlers (SIGTERM/SIGINT/SIGHUP) + the parent-process watchdog still cover legitimate disconnects.src/commands/serve.tsexposesServeOptions.mcpStdio?: booleanas a test seam so the guard is exercisable without process.env mutation. Pinned bytest/serve-stdio-lifecycle.test.ts.src/mcp/dispatch.ts— shared tool-call dispatch consumed by both stdio (server.ts) and HTTP transports. ExportsdispatchToolCall(engine, name, params, opts),buildOperationContext(engine, params, opts),validateParams(op, params). Single source of truth for(ctx, params)handler arg order and the 5-fieldOperationContextshape (engine + config + logger + dryRun + remote). Defaultsremote: true(untrusted); local CLI callers passremote: false. Also exportssummarizeMcpParams(opName, params)— privacy-preserving redactor formcp_request_logand the admin SSE feed, returns{redacted, kind, declared_keys, unknown_key_count, approx_bytes}. Intersects submitted top-level keys against the operation's declaredparamsallow-list (declared keys preserved sorted; unknown keys counted but never named, closing the attacker-controlled-key-name leak). Byte counts bucketed up to nearest 1KB so an attacker can't binary-search secret-content sizes by probing. Raw payload visibility is opt-in viagbrain serve --http --log-full-params(loud stderr warning). New logging paths route through this helper, notJSON.stringify(params).src/mcp/rate-limit.ts— Bounded-LRU token-bucket limiter.buildDefaultLimiters()returns the two-bucket pipeline: pre-auth IP (30/60s, fires BEFORE the DB lookup so brute-force load againstaccess_tokensis capped) + post-auth token-id (60/60s). TrackslastTouchedMsseparately fromlastRefillMsso an exhausted key can't be reset by hammering past the TTL. LRU cap bounds memory under attacker-controlled key growth.src/commands/serve-http.ts— Express 5 HTTP MCP server with OAuth 2.1, admin dashboard, and SSE live activity feed. Started viagbrain serve --http [--port N] [--token-ttl N] [--enable-dcr] [--public-url URL] [--bind HOST] [--log-full-params]. Combines MCP SDK'smcpAuthRouter(authorize/token/register/revoke), a customclient_credentialshandler running BEFORE the router (SDK's token endpoint throwsUnsupportedGrantTypeErrorfor CC; custom handler falls through forauth_code/refresh_token),requireBearerAuthmiddleware for/mcpwith scope enforcement +localOnlyrejection before op dispatch, andexpress-rate-limitat 50 req / 15 min on/token. Serves the built admin SPA fromadmin/dist/with SPA fallback./admin/eventsSSE broadcasts every MCP request.cookie-parserwired (Express 5 has no built-in). Startup logging prints port, engine, issuer URL (honors--public-url), client count, DCR status, admin bootstrap token. The/mcprequest handler's OperationContext literal setsremote: trueexplicitly (without itsubmit_job's protected-name guard atoperations.ts:1391saw a falsy undefined and aread+write-scoped OAuth token could submitshelljobs — RCE).summarizeMcpParamsfromsrc/mcp/dispatch.tsfeeds bothmcp_request_logwrites and the SSE feed by default (raw via--log-full-params). CookieSecureflag set behind HTTPS or a public-URL proxy; magic-link nonce store LRU-bounded; DCR disable routes through theGBrainOAuthProviderdcrDisabledconstructor option (not a router monkey-patch);transport.handleRequestwrapped in try/catch to return a JSON-RPC 500 envelope; OperationError + unexpected exceptions unified throughbuildError/serializeErrorso/mcpalways returns the same envelope./healthis liveness-only viaprobeLiveness(sql, engineName, version, timeoutMs)racingsql\SELECT 1`against the exportedHEALTH_TIMEOUT_MS = 3000(returns the sameProbeHealthResulttagged-union asprobeHealth, single timer-cleanup site, single 503 envelope); body shape{status, version, engine}only. Full stats moved to admin-only/admin/api/full-stats(gated byrequireAdmin, callsprobeHealth(engine, ...)) — keepsgetStats()'s 6× count(*) off the public route so a saturated pool doesn't trigger orchestrator restart cascades. Every OAuth/admin/audit SQL call routes throughsqlQueryForEngine(engine)fromsrc/core/sql-query.tsso it works against PGLite; the fourmcp_request_log.paramsINSERT sites (success / auth_failed / scope_denied / server-error) go throughexecuteRawJsonb(engine, ...)so the column stores real objects (params->>'op'returnssearch, not the quoted string).--bind HOSTdefaults127.0.0.1(self-hosters pass--bind 0.0.0.0); a stderr WARN fires when--public-urlis set without--bind; the banner prints aBind:line.AuthInfo.sourceId+AuthInfo.allowedSourcesare the typed source of truth, populated byoauth-provider.ts:verifyAccessTokenfrom theoauth_clientsrow. The HTTP MCPtools/listhandler at:837-849usesparamDefToSchema(v)fromsrc/mcp/tool-defs.tsso array params keepitems` (strict-mode OAuth clients otherwise reject the whole tool list).src/core/sql-query.ts— engine-aware tagged-template SQL adapter for OAuth/admin/auth infrastructure.sqlQueryForEngine(engine)returns aSqlQuery((strings, ...values) => Promise<rows[]>) that walks the template, builds$Npositional SQL, asserts every value is aSqlValue(string | number | bigint | boolean | Date | null), and routes throughengine.executeRaw(sql, params)(Postgres via postgres.jsunsafe(sql, params), PGLite viadb.query(sql, params)). Deliberately narrower than postgres.js'ssqltag: no nested fragments,sql.json(),sql.unsafe(),sql.begin(), or array binding — the narrow scalar-only surface is the feature (keeps it from drifting into a partial postgres.js clone). JSONB writes go throughexecuteRawJsonb(engine, sql, scalarParams, jsonbParams)which composes positional$N::jsonbcasts and passes JS objects through; the double-encode bug class doesn't apply because positional binding throughunsafe()reaches the wire protocol with the correct type oid (verified bytest/sql-query.test.tson PGLite,test/e2e/auth-permissions.test.ts:67on Postgres).scripts/check-jsonb-pattern.shdoesn't fire becauseexecuteRawJsonb(...)is a method call, not the banned literal-template-tag interpolation. Consumed bysrc/commands/auth.ts,src/commands/serve-http.ts,src/core/oauth-provider.ts,src/commands/files.ts,src/mcp/http-transport.tsso all five work uniformly against PGLite and Postgres.src/commands/serve.ts—gbrain servestdio MCP entrypoint with idempotent shutdown across every parent-disconnect signal. Stdio EOF, SIGTERM, SIGINT, SIGHUP, and parent-process death (every reparent case — PID 1, launchd subreaper, systemd, tmux, or a parent shell withPR_SET_CHILD_SUBREAPER) all funnel into onecleanup(reason)that releases the engine and the PGLite write-lock dir within 5 seconds (otherwise the lock is held indefinitely after Claude Desktop / Cursor / launchd-managed gateways disconnect, forcing a 5-minute stale-lock wait on next start). Watchdog reparent check isgetParentPid() !== initialParentPid(the=== 1check missed the subreaper case under launchd/systemd). Bun'sprocess.ppidcache is stale across reparenting (oven-sh/bun#30305) sogetParentPid()runsspawnSync('ps', ['-o', 'ppid=', '-p', PID])per tick. Startup probe verifiespsis on PATH; if not (stripped containers, busybox), the watchdog skips installing AND emits a loud[gbrain serve] watchdog disabled: ps unavailable ...stderr line so operators see the degraded mode. Pinned bytest/serve-stdio-lifecycle.test.ts(22 cases). Credit @Aragorn2046 + @seungsu-kr.src/core/oauth-provider.ts—GBrainOAuthProviderimplementing the MCP SDK'sOAuthServerProvider+OAuthRegisteredClientsStore. Backed by raw SQL (works on both PGLite and Postgres — OAuth is infrastructure, not a BrainEngine concern). Full OAuth 2.1:authorize+exchangeAuthorizationCodewith PKCE,client_credentials,refresh_tokenwith rotation,revokeToken,registerClient(DCR validates redirect_uri ishttps://or loopback per RFC 6749 §3.1.2.1). All tokens + client secrets SHA-256 hashed before storage. Auth codes single-use with 10-minute TTL via atomicDELETE...RETURNING(closes RFC 6749 §10.5 TOCTOU); refresh rotation alsoDELETE...RETURNING(§10.4 stolen-token detection).pgArray()escapes commas/quotes/braces so a comma-bearing redirect_uri can't smuggle a second array element. Legacyaccess_tokensfallback inverifyAccessTokengrandfathers pre-v0.26 bearer tokens asread+write+admin.sweepExpiredTokens()runs on startup in try/catch and returns the count viaRETURNING 1+ array length. RFC hardening:client_idfolded atomically into theDELETE WHEREfor both auth-code exchange and refresh rotation (wrong-client paths don't burn the row); refresh-scope-subset enforced against the original grant on the row (RFC 6749 §6, so revoking a scope shrinks existing refresh tokens);client_idbound onrevokeToken(RFC 7009 §2.1);/tokenredirect_urivalidated against the/authorizevalue (RFC 6749 §4.1.3, empty-string treated as missing not wildcard); barecatch {}inverifyAccessToken/getClientreplaced byisUndefinedColumnErrorfromsrc/core/utils.ts(only SQLSTATE 42703 falls through to legacy; lock timeouts/network blips throw);dcrDisabledconstructor option letsserve-http.tsdisable/registerwithout monkey-patching the router. Module-privatecoerceTimestamp()normalizes postgres-driver-as-string BIGINT columns to JS numbers at 5 read sites (getClientfor RFC 7591 §3.2.1 numeric timestamps,exchangeRefreshToken+verifyAccessTokenfor the SDK'stypeof === 'number'check); throws on NaN/Infinity (fail loud at boundary), returns undefined for SQL NULL (callers treat NULL as expired). Not promoted toutils.ts— generic BIGINT precision-loss risk.registerClienthonorstoken_endpoint_auth_method: "none"(RFC 7591 §3.2.1): public PKCE clients storeclient_secret_hash = NULLand the response omitsclient_secret; confidential clients (client_secret_post/client_secret_basic) keep their one-time-reveal shape;getClientnormalizes NULLclient_secret_hashto JSundefinedso the SDK's clientAuth path accepts public clients.verifyAccessTokenJOINsoauth_clients.source_id(write scope, scalar) +oauth_clients.federated_read(read scope, TEXT[]) onto the returnedAuthInfo; legacy brains degrade viaisUndefinedColumnErrorfallback.admin/— React 19 + Vite + TypeScript admin SPA embedded in the binary viaadmin/dist/served byserve-http.ts. 7 screens: Login (bootstrap token → session cookie), Dashboard (metrics + SSE feed + token health), Agents (sortable table + sparklines + Register), Register (modal with scope checkboxes + grant type selector), Credentials reveal (Copy + Download JSON + one-time-only warning), Request Log (filterable paginated), Agent Detail drawer (Details / Activity / Config Export tabs + Revoke). Design tokens:#0a0a0fbg, Inter for UI, JetBrains Mono for data, 4-32px spacing scale, rounded pill badges. HTTP-only SameSite=Strict cookie auth. 65KB gzip. Build:cd admin && bun install && bun run build; output atadmin/dist/is committed for self-contained binaries.src/commands/auth.ts— token management.gbrain auth create/list/revoke/testfor legacy bearer tokens, plusgbrain auth register-clientandgbrain auth revoke-client <client_id>for OAuth 2.1 client lifecycle.revoke-clientruns an atomicDELETE...RETURNINGonoauth_clients; FKON DELETE CASCADEonoauth_tokens.client_idandoauth_codes.client_idpurges every active token + auth code in one transaction;process.exit(1)on no-such-client (idempotent). Legacy tokens stored as SHA-256 hashes inaccess_tokens; OAuth clients inoauth_clients; legacy tokens grandfather toread+write+adminscopes on the OAuth HTTP server (no migration). Every SQL site routes throughsqlQueryForEngine(engine)fromsrc/core/sql-query.ts(andexecuteRawJsonbfor the takes-holderspermissionsJSONB column) sogbrain authworks against PGLite; the takes-holders write goes throughexecuteRawJsonb(engine, sql, [name, hash], [{takes_holders:[...]}])which round-trips withjsonb_typeof = 'object'.register-clientaccepts--source <id>(write authority, scalar) and--federated-read <S1,S2,...>(read scope, array) and prints the resolvedWrite source+Federated reads; pre-v0.34 clients backfill tosource_id='default'via migration v60. The baregbrain auth create <name>form (no--takes-holders) mints a token via the exported pureparseAuthCreateArgs(rest)(the inline version usedrest[takesIdx + 1]resolving torest[0]whentakesIdx === -1, excluding the name from the positional search). Pinned bytest/auth-create-args.test.ts.src/commands/connect.ts+src/core/connect-probe.ts—gbrain connect <mcp-url> [--token <bearer>]one-command coding-agent onboarding from a bearer token. Turns an MCP URL + token into a paste-readyclaude mcp add ... -H "Authorization: Bearer ..."block (default) or, with--install, runs it directly and smoke-tests the token. Direct HTTP MCP — Claude Code talks straight to a remotegbrain serve --http, no local install needed. Token resolution:--token>$GBRAIN_REMOTE_TOKEN> placeholder (print) / error (install). The generated block tells the agent to callget_brain_identity+list_skills(theLEARN_INSTRUCTIONexport, which namesput_pagenotcapturesincecaptureis CLI-only, not an MCP tool) with a core-tools fallback for hosts without skill publishing. URL normalization appends/mcpto a bare host but REJECTS a scheme-less host; pure helpers (isLinkLocalOrMetadata, URL parse, render) are unit-tested. Flags:--token,--name <id>(defaultgbrain, validated againstNAME_RE),--agent claude-code|codex|perplexity|generic,--install,--yes(required for--installin non-TTY),--force,--json(token redacted unless--show-token),--timeout-ms.connectis inCLI_ONLY+CLI_ONLY_SELF_HELP; dispatched incli.ts:handleCliOnlywith no local DB connect.AGENT_SPECSdrives per-agent rendering +--install:claude-code→buildClaudeMcpAddArgv(literal-H "Authorization: Bearer <tok>");codex→buildCodexMcpAddArgv=codex mcp add <name> --url <url> --bearer-token-env-var GBRAIN_REMOTE_TOKEN(Codex reads the token from the env var at runtime, never written to config;--installruns it and prints anexport GBRAIN_REMOTE_TOKENhint when missing);perplexity+genericareinstallable:falseand reject--install.--oauth(supportsOAuth:true= perplexity/generic only) emits an OAuth 2.1 client-credentials connector block (Issuer URL viaissuerFromMcpUrl= mcp-url minus/mcp, Client ID, Client Secret) — least-privilege scopes + short-lived rotating tokens vs a long-lived full-access secret. Creds from--client-id/--client-secret(BYO) or--register(deps.registerOAuthClientshellsgbrain auth register-client <name> --grant-types client_credentials --scopes <DEFAULT_SCOPES="read write"> --token-endpoint-auth-method client_secret_postand parsesClient ID:/Client Secret:);--oauthrejected for claude-code/codex and incompatible with--install.buildJsonis a generic shape (agent,command/command_argvnull for perplexity/generic,header,env_var, oauth fields with redaction); the codexcommandcarries only the env-var name, never the token.cmdString(binary, argv)POSIX-single-quotes args.ConnectDeps={isTTY, promptYesNo, hasBinary(bin), runBinary(bin, argv), probe, env(name)}— binary-generic soclaudeandcodexshare the path;envinjectable for tests. Security: rendered command single-quotes the token so shell metacharacters can't run code when pasted; token validated before it lands in an HTTP header; link-local / cloud-metadata addresses (incl. IPv4-mapped IPv6::ffff:169.254.x.xand AWS IMDSv2-over-IPv6fd00:ec2::254) refused as a token-exfil guard while localhost/RFC1918/LAN stay allowed; token redacted from all error output.src/core/connect-probe.tsis the raw-bearer MCP smoke probe backing--install: connects the official MCP SDKClientoverStreamableHTTPClientTransportwith a STATICAuthorizationheader (no OAuth/discovery — distinct frommcp-client.ts:callRemoteToolwhich is OAuth-only andremote-mcp-probe.ts:smokeTestMcpwhich only sendsinitialize), runs the fullinitializehandshake viaclient.connect(), then callsget_brain_identity(read-scope, non-localOnly) to prove a tool call round-trips. Never throws — every failure maps to{ ok: false, reason: 'auth' | 'unreachable' | 'timeout' | 'tool_error' | 'unknown', message }so a wrong/expired token fails at setup, not on the agent's first request.DEFAULT_PROBE_TIMEOUT_MS = 15_000shared withconnect.ts.serve-http.tsadds exported pureskillPublishStatus(publishSkills)for the startup bannerSkills: published / not publishedline + a one-linegbrain config set mcp.publish_skills truestderr nudge when publishing is OFF. Docs:docs/mcp/CODEX.md,docs/mcp/PERPLEXITY.md,docs/mcp/CLAUDE_CODE.md,docs/tutorials/connect-coding-agent.md. Pinned bytest/connect.test.ts(pure-helper + render, all four agents) +test/e2e/connect-bearer.test.ts(raw-bearer probe + full OAuth chain register→connect→discovery→/tokenmint→get_brain_identity, client registered inbeforeAllbefore serve takes the PGLite single-writer lock; drives realclaude+codexbinaries throughconnect --installwith sandboxedHOME/CODEX_HOME, asserts registration + token never in Codex config, skips when a binary is absent) +test/e2e/serve-stdio-roundtrip.test.ts(spawns realgbrain servestdio against a freshinit --pglitebrain, drives the SDK client throughinitialize→tools/list→tools/call, asserts the advertised core-tool set and thatcaptureis NOT advertised) +test/serve-skills-publish-nudge.test.ts(thetest/audit/batch-retry-audit.test.tsENOENT case was made hermetic — it had read the real~/.gbrain/audit).src/commands/upgrade.ts— self-update CLI.runPostUpgrade()enumerates migrations from the TS registry (src/commands/migrations/index.ts) and tail-callsrunApplyMigrations(['--yes', '--non-interactive'])so the mechanical side of every outstanding migration runs unconditionally.src/commands/migrations/— TS migration registry (compiled into the binary; no runtime walk ofskills/migrations/*.md).index.tslists migrations in semver order.v0_11_0.ts= Minions adoption orchestrator (8 phases).v0_12_0.ts= Knowledge Graph auto-wire orchestrator (5 phases: schema → config check → backfill links → backfill timeline → verify);phaseASchemahas a 600s timeout for duplicate-heavy brains.v0_12_2.ts= JSONB double-encode repair orchestrator (4 phases: schema → repair-jsonb → verify → record).v0_14_0.ts= shell-jobs + autopilot cooperative (pending-host-work ping for skills/migrations/v0.14.0.md). All orchestrators are idempotent and resumable frompartialstatus. The RUNNER owns all ledger writes — orchestrators returnOrchestratorResultandapply-migrations.tspersists a canonical{version, status, phases}shape (orchestrators no longer callappendCompletedMigration).statusForVersionpreferscompleteoverpartial(never regresses); 3 consecutive partials → wedged →--force-retry <version>writes a'retry'reset marker. Schema-only migrations v14 (pages_updated_at_index) + v15 (minion_jobs_max_stalled_default_5with UPDATE backfill) live in theMIGRATIONSarray insrc/core/migrate.ts.in-process.tsexportsrunMigrateOnlyCore({timeoutMs?})— single source of truth for "bring schema to head" (configureGateway→createEngine→connect→initSchema→disconnect, idempotent, 600sMIGRATE_ONLY_TIMEOUT_MSguard, throwsMigrateOnlyErroron no-config / timeout); the orchestrators' 9 schema phases ANDinit.ts:initMigrateOnlyboth delegate to it so schema bring-up can't drift (running in-process removes the spawn that died withgetaddrinfo ENOTFOUNDon Windows + bun + Supabase pooler).runGbrainSubprocessis the diagnostic wrapper for the remaining non-schema spawns (extract/repair/stats): captures child stderr (64MB buffer) into the thrown error.v0_13_1.ts:phaseCGrandfatheris a CHUNKED bulk SQL pass keyed onpages.id(globally unique PK, NOT slug — slug uniqueness is(source_id, slug)), filtersdeleted_at IS NULL(no tombstones), chunked inCHUNK_SIZEbatches (DELETE_BATCH_SIZE convention) for bounded lock-hold; the rollback log carries{id, slug, source_id, pre_frontmatter}so rollback is unambiguous across sources; idempotent + resumable (each UPDATE flips its rows out ofGRANDFATHER_WHERE). Pinned bytest/migration-in-process.serial.test.tsandtest/migrations-v0_13_1-grandfather.test.ts.src/commands/repair-jsonb.ts—gbrain repair-jsonb [--dry-run] [--json]: rewritesjsonb_typeof='string'rows in place across 5 affected columns (pages.frontmatter, raw_data.data, ingest_log.pages_updated, files.metadata, page_versions.frontmatter). Fixes the double-encode bug on Postgres; PGLite no-ops. Idempotent.src/commands/orphans.ts—gbrain orphans [--json] [--count] [--include-pseudo] [--source <id>]: surfaces pages with zero inbound wikilinks, grouped by domain (auto-generated/raw/pseudo filtered by default). Also exposed asfind_orphansMCP op.findOrphans/getOrphansData(the canonical pure fn shared with doctor'sorphan_ratio) takes{ sourceId?, sourceIds? };BrainEngine.findOrphanPages(opts?)(both engines) scopes ONLY the candidate side (p.source_id = $1scalar, or= ANY($1::text[])federated) while still counting inbound links from ANY source — a page in source X linked FROM source Y is reachable, so NOT an orphan of X (deliberate definition; the stricter intra-source-only reading is rejected).--sourceis an explicit raw-flag parse (NOTresolveSourceWithTier, which would scope bare invocations to a default). Thetotal_linkabledenominator enumerates ALL live pages (scoped) and subtracts every excluded-by-slug page (templates/, scratch/, etc.) so excluded NON-orphan pages with inbound links don't inflate it and suppress warnings. Thefind_orphansMCP op threadssourceScopeOpts(ctx)so a source-bound OAuth client doesn't see brain-wide orphans.gbrain doctor --source <id>scopesorphan_ratioand, under explicit--sourcebelow 100 entity pages, reports the ratio with a low-scale caveat (thin-clientdoctor --sourceorphan_ratio remains brain-wide — TODO). Pinned bytest/orphans-source-scope.test.ts(PGLite) +test/e2e/engine-parity.test.ts(Postgres↔PGLite scalar + federated parity). Contributed by @knee5.src/commands/salience.ts—gbrain salience [--days N] [--limit N] [--kind PREFIX] [--json]: pages ranked by emotional + activity salience over a recency window. Mirrors orphans.ts shape (pure data fn + JSON formatter + human formatter). Callsengine.getRecentSalience(opts). Score formula:(emotional_weight × 5) + ln(1 + active_take_count) + 1/(1 + days_since_update).src/commands/anomalies.ts—gbrain anomalies [--since YYYY-MM-DD] [--lookback-days N] [--sigma N] [--json]: cohort-level activity outliers. Callsengine.findAnomalies(opts). Two cohort kinds: tag, type.src/commands/whoknows.ts—gbrain whoknows <topic> [--explain] [--limit N] [--json]: expertise + relationship-proximity routing. Mirrors salience/anomalies shape (purerankCandidates()+findExperts()orchestrator +runWhoknows()CLI dispatch + thin-client routing). MCP op =find_experts(scope: read, localOnly: false). Ranking formula:score = log(1 + raw_match) × max(0.1, exp(-days/180)) × (0.5 + 0.5 × salience)whereraw_matchis hybridSearch's RRF+source-boost score. Filters at SQL viaSearchOpts.types: ['person', 'company'](no post-filter waste); hybridSearch's internal salience+recency boosts are intentionally disabled so the locked formula applies on a clean signal. Floors prevent multiplicative-zero edge cases (cold-start people stay visible); ties break alphabetically by slug for determinism. 16 unit tests intest/whoknows.test.tspin the math.src/commands/eval-whoknows.ts—gbrain eval whoknows <fixture.jsonl> [--json] [--skip-replay]: two-layer eval gate. Layer 1 quality (hand-labeled fixture, top-3 hit rate ≥ 0.8). Layer 2 regression (eval_candidatesreplay set-Jaccard@3 ≥ 0.4). Sparseness fallback: < 20 replay-eligible rows → Layer 2 auto-skips with stderr warning. Stable JSON envelope withschema_version: 1; exit 0/1/2 for pass/fail/usage.WhoknowsFncallable abstraction makes the gates impl-agnostic;runEvalWhoknows(engine: BrainEngine | null, args)picks the impl at entry — thin-client mode (isThinClient(cfg)) routes per-query throughcallRemoteTool(cfg, 'find_experts', {topic, limit}), local mode callsfindExperts(engine, ...)directly. cli.ts adds a thin-client bypass beforeconnectEngine(dispatch shape undersrc/commands/eval.ts); the regression gate auto-skips in thin-client mode (no DB access toeval_candidates). Public exportsjaccardAtK,topKHit,readFixture,WhoknowsFn, threshold constants pinned bytest/eval-whoknows.test.ts(25 cases incl. null-engine signature contract).test/fixtures/whoknows-eval.jsonl— 10-row synthetic placeholder demonstrating the eval-fixture schema ({query, expected_top_3_slugs, notes?}JSONL). End users replace with their own real queries; placeholder uses obviously-example slugs (wiki/people/example-alice). Drivestest/e2e/whoknows.test.ts(seeds a matching synthetic brain, asserts the >=80% gate) and thewhoknows_healthdoctor check.src/core/skillopt/+src/commands/skillopt.ts+skills/skill-optimizer/— self-evolving skill optimization grounded in the SkillOpt paper (arXiv 2605.23904).gbrain skillopt <skill>treatsSKILL.mdas trainable parameters of a frozen agent: validation-gated (median-of-3 + epsilon=0.05), budget-capped (preflight estimator), per-skill DB-locked (tryAcquireDbLock('skillopt:<name>', 60min)), atomic-versioned (history-intent-first 5-step commit), body-only mutations (frontmatter forbidden). Rollouts usegateway.toolLoopdirectly with no-op persistence callbacks (zerosubagent_messagespollution) + a read-only tool allowlist derived fromBRAIN_TOOL_ALLOWLISTminusput_page/submit_job/file_upload. Two reflect calls per step; rejected-edit buffer LRU-bounded to 100; bundled-skill gate; bootstrap workflow (sentinel +--bootstrap-reviewed); D_sel floor (>=5 with--splitoverride); audit JSONL viaaudit-writer.ts. Added toALL_PHASESafterpatterns(default OFF; opt-in viagbrain config set cycle.skillopt.enabled true); cycle phase wrapper atsrc/core/skillopt/cycle-phase.tswalks stale skills with per-skill ($0.50) + brain-wide ($2.00) caps. Added toPROTECTED_JOB_NAMES. Surface: dream-cycle phase wrapper;--allbatch mode (src/core/skillopt/batch.ts:runBatchAll);--target-modelsfleet (runFleetparallel per-model receipts underskillopt/fleet/<slug>/); MCP oprun_skillopt(admin scope + per-skillskillopt.allowed_skillsallowlist, NOT localOnly, validatesskill_namekebab-only + confines caller-supplied benchmark/held-out paths to skillsDir for remote callers); Minionskillopthandler +--backgroundwithallowProtectedSubmit: true; write-flavored optimization viasrc/core/skillopt/write-capture.ts:buildWriteCaptureRegistry(virtualput_page/submit_job/file_uploadcaptured in-memory;--write-captureflag); held-out real-user test set viasrc/core/skillopt/held-out.ts(capture infra at~/.gbrain/skillopt-captures/<skill>/<run>.jsonl,--held-out <path>flag,runHeldOutGatecandidate >= baseline). Hermetic via DI seams (opts.chatFnfor optimizer + judge;opts.toolLoopFnfor rollouts; nomock.module).--bootstrap-from-skill→runBootstrapFromSkillinsrc/core/skillopt/bootstrap-benchmark.ts: readsSKILL.mddirectly (norouting-eval.jsonl), makes ONE LLM call emitting a full starter benchmark (tasks + rule judges) as JSONL, parsed line-by-line with skip-bad-line salvage and a min-2-valid-checks-per-task drop; provider/transport errors PROPAGATE (not collapsed tobootstrap_empty).--bootstrap-tasks N(default 15, capped 50);maxTokensscalesmin(8000, max(4000, N*220)). The stderr REVIEW line prints the literalgbrain skillopt <name> --bootstrap-reviewed --split 1:1:1— load-bearing because the default4:1:5split makes a 15-task starter'sD_sel = floor(15/10) = 1, below the>=5floor, so a 15-task benchmark needs--split 1:1:1. Both bootstrap generators shareassertBenchmarkAbsent+readSkillBodyOrThrow;--bootstrap-from-skillis mutually exclusive with--bootstrap-from-routing/--benchmark/--all/--target-models/--resume. Generated rule judges are explicitly WEAK DRAFTS to be strengthened during the review gate. The F11 held-out gate is wired:--held-out <path>is parsed and threaded through every caller (CLI main +--backgroundheld_out_path+ batch/fleetheldOutPath+ therun_skilloptheld_out_pathparam), running at CHECKPOINT ACCEPTANCE so no-mutate/fleet paths can't promote a held-out-failing candidate.assertBundledMutationHeldOutin bundled-skill-gate.ts: bundled +--allow-mutate-bundledrequires a NON-EMPTY held-out (MIN_HELD_OUT_SIZE = D_SEL_MIN_SIZE= 5, derived so they can't desync) or hard-refuses (exit 2), for ALL callers (they funnel throughrunSkillOpt); held-out must be task_id-DISJOINT from the benchmark (overlap rejected — can't catch overfitting).receipt.baseline_sel_scorepopulated + a real final-test eval (test_score+baseline_test_score) scoring best + baseline onsplit.test; sharedscoreSkillOnTasksprimitive (validate-gate.ts) backs baseline/final-test/held-out scoring.--no-mutatewrites proposed.md viawriteProposedin version-store.ts.maxRuntimeMinENFORCED (wall-clock deadline between steps →skillopt_runtime_exceeded→ outcome aborted). Three eval-internal ablation opts onSkillOptOpts(NOT on CLI):reflectMode('both'/'failure-only'),disableValidationGate(greedy-accept),optimizerMode('reflect'/'one-shot-rewrite'), recorded inRunReceipt+ auditrun_startfor replayability;ROLLOUT_SUCCESS_THRESHOLD = 0.5named constant for the partition; one-shot fence-strip is anchored (^```...```$) so an embedded code sample isn't truncated. Budget no-pricing fix: Claude Haiku 4.5's dateless canonical idclaude-haiku-4-5is insrc/core/anthropic-pricing.ts(aBudgetTracker-capped run on Haiku otherwise threwno_pricingon the FIRSTchat()of every rollout);runValidationGate(validate-gate.ts) scans settled results forisMustAbortError(error)(fromworker-pool.ts;BUDGET_EXHAUSTEDis inMUST_ABORT_ERROR_TAGS) and re-throws so the caller aborts loudly instead of recording a hollowselScore:0— ordinary non-abort rollout errors still fail-open toscore:0(judge-hiccup posture preserved). Pinned by 152 tests across 18 files (foundation + adversarial + v2 surface + E2E PGLite serial),test/skillopt/bootstrap-from-skill.test.ts(20 cases),test/skillopt/rollout.test.ts,test/skillopt/validate-gate-abort.test.ts(3 cases), held-out ENFORCE + one-shot-rewrite unit cases, and e2e (F11 block/allow, bundled no-mutate, runtime deadline, receipt honesty, held-out disjointness, no-DB-pollution). Drives the Track B SkillOpt benchmark suite in the siblinggbrain-evalsrepo.src/core/brainstorm/{domain-bank,orchestrator,judges}.ts+src/commands/{brainstorm,lsd,eval-brainstorm}.ts+src/core/last-retrieved.ts— bisociation-grounded idea generation pair:gbrain brainstorm <question>(defensible, cite-heavy, 4 close × 6 far, judge threshold 4.0/5, save by default) andgbrain lsd <question>(Lateral Synaptic Drift — inverted judge rejecting ideas with resistance >4.5 "too obvious", stale-page bias viapages.last_retrieved_at, 2 close × 12 far, axiomatic inversions required, ephemeral by default). The "domain bank" is prefix-stratified sampling from the user's own brain (SELECT DISTINCT substring(slug from '^[^/]+/[^/]+')cached 1h-TTL inconfigper source) tiebroken byJOIN page_linksconnection_count, with corpus-sampling fallback when fewer prefixes than M exist. Distance normalized to [0,1] via1 - clamp(cosine_distance, 0, 2) / 2.judges.tsexportsrunJudge(config, ideas)+ two configs (BRAINSTORM_JUDGE_CONFIGweighted originality/resistance/thesis_density/concrete_grounding/cognitive_load 0.25/0.20/0.20/0.20/0.15 vsLSD_JUDGE_CONFIGcognitive_load 0.50 + inversion rule). Calibration cold-start fallback: whencalibration_profiles.active_bias_tagsis empty, judge runs without anti-bias context AND stderr-warns. Op-layer write-back insrc/core/operations.tssearch/query/get_pagehandlers firesbumpLastRetrievedAt(engine, pageIds)(fire-and-forget, 5-min throttled via SQL clause, default-on withsearch.track_retrievalconfig escape hatch); internal callers (sync, migrations, dream cycle) bypass the op layer so the LSD stale signal stays clean. The fire-and-forget IIFE is tracked in a module-scopedSet<Promise<unknown>>;awaitPendingLastRetrievedWrites(timeoutMs?: number): Promise<{outcome, pending}>resolves once all tracked promises settle, bounded by a 5sPromise.racetimeout that stderr-warns the pending count.src/cli.tsawaits the drain unconditionally for every op in the op-dispatch finally block BEFOREengine.disconnect(), then a fallbackprocess.exit(0)fires ONLY whenoutcome === 'timeout'ANDshouldForceExitAfterMain(argv)(excludesserveso daemons stay alive) — closes the PGLite CLI search/query/get-hang class where the IIFE raced disconnect and PGLite's WASM kept Bun's event loop alive.pages.last_retrieved_at TIMESTAMPTZ NULLhas a full (NOT partial) B-tree index covering both NULL and range branches; full forward-reference bootstrap probe on both engines. Frontmattermode: lsdmakes the dream-cycle synthesize phase skip LSD output viaisLsdOutput()insrc/core/cycle/transcript-discovery.tsshort-circuitingisDreamOutput().gbrain eval brainstorm <fixture.jsonl>is a three-axis conjunctive gate (distance + usefulness + grounding — distance alone is gameable).gbrain doctorhas abrainstorm_healthcheck (migration applied,search.track_retrievalsetting, calibration cold-start status).judges.tscomputes the judge token budget viacomputeJudgeMaxTokens(ideaCount, modelId)(named constantsTOKEN_BUDGET_PER_IDEA,TOKEN_BUDGET_ENVELOPE,LEGACY_MIN_MAX_TOKENS,MAX_OUTPUT_TOKENS_CEIL;ANTHROPIC_OUTPUT_CAPSmap: Opus 4.7 32K, Sonnet 4.6 / Haiku 4.5 64K, legacy Claude 3.5 8K) so a large multi-call judge doesn't truncate mid-JSON; with nomodelOverridethe cap routes through the gateway's actual configured chat model viagetChatModel().--savefor both commands persists through the canonical ingestion path:persistSavedIdea(engine, {slug, content, provenanceVia})callsimportFromContent({noEmbed:true, sourcePath})(chunked + tagged + content_hash so search finds it, no embedding cost at save) THEN renders the saved row to disk via the sharedwritePageThroughhelper (file rendered FROM the row so the two sinks can't diverge andgbrain syncdoesn't churn it).formatSaveOutcome(outcome, ctx)returns an honest per-branch message (both-sinks, DB-only when nosync.repo_path/repo-not-a-dir, DB-saved-but-file-errored, total-failure → loudsave FAILED … NOT persistedon stderr + nonzero exit) — closes the silent-false-success class where--saveprinted "Saved" unconditionally even when the DB write failed.buildIdeaSlug(question, label, nonce?)adds a random nonce suffix (injectable for tests) so two same-day runs sharing the first 60 slug chars don't clobber.--jsoncallers stay DB-only.buildBrainstormFrontmatterObject(result)in orchestrator.ts returns the object form forserializeMarkdown(stringbuildBrainstormFrontmatteruntouched). Pinned bytest/last-retrieved.test.ts,test/e2e/pglite-cli-exit.serial.test.ts(IRON-RULE: realbun src/cli.tssubprocess against a hermetic PGLite tempdir asserts search/get/query exit 0 in <15s + daemon-survival),test/fix-wave-structural.test.ts(asserts the drainawaitis textually BEFOREengine.disconnect),test/brainstorm/{distance,lsd-mode-skip,eval-brainstorm,judges-maxtokens,save}.test.ts. Open Collider source:github.com/CL-ML/open-collider.src/core/write-through.ts— shared atomic disk write-through for the canonical ingestion path.writePageThrough(engine, slug, {sourceId?, frontmatterOverrides?, logger?})readssync.repo_path, re-reads the just-written DB row (getPage), renders it viaserializePageToMarkdown+resolvePageFilePath, and writes the.mdunder the repo so the brain has a committable artifact that round-trips throughgbrain sync. Rendering FROM the row means file and row cannot diverge. ATOMIC: writes to a unique temp sibling (<file>.tmp.<pid>.<rand>) +renameSync, cleaning up temp on any failure, so a crash or concurrentgbrain sync/autopilot walking the live git tree never reads a half-written.md(matches the.tmp + renameconvention in import-checkpoint.ts / op-checkpoint.ts). Never throws — returnsWriteThroughResult { written, path?, skipped?: 'no_repo_configured' | 'repo_not_found' | 'page_not_found_after_write', error? }so the caller decides messaging + exit codes. Trust gating (subagent sandbox, dry-run) stays at the CALLER. Consumers:put_pageop andgbrain brainstorm/lsd --saveviapersistSavedIdea. Pinned bytest/write-through.test.ts.src/core/model-id.ts—splitProviderModelId(input: string | null | undefined): {provider: string | null, model: string}shared parser for the pricing side. Splits on:first, then/. Defensive contract: null/undefined/empty/whitespace returns{provider: null, model: ''}. Five sites consume it (src/core/anthropic-pricing.ts:estimateMaxCostUsd,src/core/budget/budget-tracker.ts:lookupPricing,src/core/eval-contradictions/cost-tracker.ts:pricingFor,src/core/minions/batch-projection.tsat two call sites,src/core/model-config.ts:isAnthropicProvider) so the pricing + classification surface has no parallel re-implementations ofprovider:modelsplitting — slash-form ids (anthropic/claude-sonnet-4-6) classify correctly instead of falling through to "unknown model". Distinct from the gateway-sideparseModelIdinsrc/core/ai/model-resolver.ts, which throws on bare names because routing needs an explicit provider; this one returns{provider: null, model: 'bare'}because pricing lookups happen against bare model ids. Pinned bytest/model-id.test.ts.src/core/ai/model-resolver.ts:parseModelId— gateway-side resolver accepts both colon and slash form (provider:modelandprovider/model) so a slash-form id resolves to the same recipe at every gateway entry point (chat / embed / rerank) instead of throwingAIConfigError: model id must be in format provider:model. Bare names without ANY separator still throw — gateway routing always needs an explicit provider. Pinned bytest/ai/model-resolver-slash.test.tsincluding aresolveReciperound-trip asserting slash form resolves to the same recipe object as colon form.src/commands/transcripts.ts—gbrain transcripts recent [--days N] [--full] [--json]: recent raw.txttranscripts from the dream-cycle corpus dirs. ImportslistRecentTranscriptsfromsrc/core/transcripts.ts(the same library the gatedget_recent_transcriptsMCP op uses). Local-only by construction — the CLI always runs withctx.remote=false.src/commands/integrity.ts—gbrain integrity check|auto|review|extract: bare-tweet detection, dead-link detection, three-bucket repair (auto-repair / review-queue / skip).scanIntegrity()is the shared library function called fromgbrain doctor(sampled at limit=500) andcmdCheck(full scan). Batch-load fast path on Postgres uses a single SQL query (fixes the PgBouncer round-trip timeout, ~60s → ~6s), gated byengine.kind === 'postgres'at the call site so PGLite never enters batch; fallbackcatchlogs atGBRAIN_DEBUG=1. Batch projection isSELECT ... ORDER BY source_id, slug(NOTSELECT DISTINCT ON (slug), which collapsed same-slug-different-source pages into one scan) so multi-source brains scan each(source, slug)row independently. Sequential and auto-repair loops uselistAllPageRefs()to enumerate(slug, source_id)pairs and threadsourceIdtogetPage; batch + sequential paths report the same page count on multi-source brains.src/commands/doctor.ts—gbrain doctor [--json] [--fast] [--fix] [--dry-run] [--index-audit]: health checks. Checks includejsonb_integrity+markdown_body_completeness(reliability),schema_version(fails loudly whenversion=0, routes togbrain apply-migrations --yes),queue_health(Postgres-only: stalled-forever active jobs started_at > 1h, waiting-depth-per-name > threshold default 10 viaGBRAIN_QUEUE_WAITING_THRESHOLD, and dead-lettered subagent jobs withlast_errormatching theprompt_too_longclassifier in last 24h),sync_failures([CODE=N, ...]breakdown for unacked-warn + acked-ok; severity comes from the shareddecideSyncFailureSeverityinsrc/core/sync-failure-ledger.tsso the LOCAL and REMOTE/thin-client doctor surfaces can never drift — a stuck bookmark escalates to FAIL once an OPEN failure has blocked past the staleness window or ≥10 files block, while alreadyauto_skippedrows stay a visible WARN),rls_event_trigger(healthyevtenabledset is('O','A')only; fix hintgbrain apply-migrations --force-retry 35),graph_coverage(short-circuits to ok whenSELECT COUNT(*) FROM pages WHERE type IN ('entity','person','company','organization')returns 0; WARN hint isgbrain extract all),embedding_column_registry(probes each declared column via Postgresformat_type(atttypid, atttypmod)to catch dim mismatch with a paste-readygbrain config set embedding_columns '{...}'hint, probes HNSW index presence viapg_indexes, computes default-column population viaCOUNT(*) FILTER (WHERE <col> IS NOT NULL) / COUNT(*)warning below 90% except empty brains where chunk_count=0 short-circuits to ok; PGLite parity viaexecuteRaw), andskill_brain_first(walks SKILL.md viaautoDetectSkillsDirReadOnly, callsanalyzeSkillBrainFirst()fromsrc/core/skill-brain-first.tsper file with structuredCheck.issues[]; warn statesmissing_brain_first/brain_first_typo, ok statescompliant_callout/compliant_phase/compliant_position/exempt_frontmatter/no_external; snapshot+diff audit at~/.gbrain/audit/skill-brain-first-YYYY-Www.jsonl).--fixdelegates inlined cross-cutting rules to> **Convention:** see [path](path).callouts viasrc/core/dry-fix.ts(and MISSING_RULE_PATTERNS for the brain-first callout);--fix --dry-runpreviews.--index-audit(Postgres-only, informational, no auto-drop) reports zero-scan indexes frompg_stat_user_indexes. Every DB check runs under a progress phase;markdown_body_completenessruns under a 1s heartbeat.runDoctorusesautoDetectSkillsDirReadOnly(fromsrc/core/repo-root.ts; install-path fallback socd ~ && gbrain doctorfinds bundled skills);--fixcarries a D6 install-path safety gate that refuses auto-repair whendetected.source === 'install_path'(would rewrite the bundled tree). The Lane D supervisor check atdoctor.ts:1011-1043consumessummarizeCrashes(events)fromsrc/core/minions/handlers/supervisor-audit.ts(warn at>=1real crash; ok message hasclean_exits_24h=N; warn message hasruntime=A oom=B unknown=C legacy=Dper-cause breakdown) so OOM/runtime/unknown crashes are distinguishable from clean code=0 worker drains; cross-surface parity withgbrain jobs supervisor statusis pinned by source-grep wiring assertions requiring the breakdown substrings in BOTHdoctor.tsandjobs.ts.checkSyncFreshness(exported, inrunDoctorlocal +doctorReportRemotethin-client) is a staleness probe: warns at 24h, fails at 72h or never-synced; future-last_sync_atwarns ("clock skew") instead of falling through ok; env overridesGBRAIN_SYNC_FRESHNESS_WARN_HOURS/GBRAIN_SYNC_FRESHNESS_FAIL_HOURS(invalid fall back with once-per-process stderr warn via_resolveSyncFreshnessHours); failure messages embedsource.idso the printedgbrain sync --source <id>matches. It has alocalOnly-gated git short-circuit (runDoctorpasseslocalOnly: true;doctorReportRemoteruns in the HTTP MCP serversrc/commands/serve-http.tsand keeps defaultfalseso that path never walks DB-suppliedlocal_pathvia subprocess — trust boundary). The local predicate mirrors sync's "do work?" gate (HEAD ==last_commitAND working tree clean viarequireCleanWorkingTree: 'ignore-untracked'so a quiet repo with only untracked dirs isunchangednot SEVERE, ANDchunker_version === CURRENT); the inline SELECT carrieslast_commit + chunker_version + newest_content_at. The REMOTE path computes lag vialagFromContentMs(newest_content_at, lastSync, now)from the stored column, NO git subprocess; LOCAL fall-through and the< 0clock-skew check stay on raw wall-clock. Three-bucket count math populatesCheck.details = {unchanged_count, synced_recently_count, stale_count}with the invariantsum === sources.length.checkCycleFreshnessis DELIBERATELY NOT git-short-circuited or content-relativized (last_commit == HEADcan't answer "did the full cycle complete?"; a sync can succeed while later cycle phases fail; different axislast_full_cycle_at). Pinned bytest/doctor.test.ts(incl. IRON-RULE regression banning stale verb names, the sync_freshness boundary matrix, the D4 regression guard verifying git probes are NEVER called whenlocalOnlyis unset/false, the three-bucket invariant, and the untracked-folders / remote-never-shells-out trust-boundary cases).src/core/migrate.ts— schema-migration runner. Owns theMIGRATIONSarray (source of truth for schema DDL).Migrationinterface carriessqlFor?: { postgres?, pglite? }(engine-specific SQL overridessql) andtransaction?: boolean(false forCREATE INDEX CONCURRENTLY, which Postgres refuses in a transaction; ignored on PGLite). Key migrations: v14 (handler branches onengine.kindfor CONCURRENTLY-on-Postgres with invalid-remnant pre-drop viapg_index.indisvalid, plainCREATE INDEXon PGLite); v15 (minion_jobs.max_stalleddefault 1→5 + backfill non-terminal rows); v24rls_backfill_missing_tables(sqlFor: { pglite: '' }no-op — PGLite has no RLS engine, targets subagent tables absent from pglite-schema.ts); v30dream_verdicts (file_path TEXT, content_hash TEXT, worth_processing BOOL, reasons JSONB, judged_at TIMESTAMPTZ, PK(file_path, content_hash))(RLS-enabled under BYPASSRLS; synthesize reads/writes to avoid re-judging); v35 auto-RLS event triggerauto_rls_on_create_tablefires onddl_command_endforWHEN TAG IN ('CREATE TABLE','CREATE TABLE AS','SELECT INTO')runningALTER TABLE … ENABLE ROW LEVEL SECURITYon newpublic.*tables (no FORCE) + one-time backfill on every existingpublic.*base table whose comment doesn't match^GBRAIN:RLS_EXEMPT\s+reason=\S.{3,}(per-table failure aborts the offending CREATE TABLE; no EXCEPTION wrap; PGLite no-op viasqlFor.pglite: ''; breaking change: intentionally-RLS-off public tables need the GBRAIN:RLS_EXEMPT comment before upgrade); v40pages_emotional_weight(pages.emotional_weight REAL NOT NULL DEFAULT 0.0, column-only metadata-only); v46mcp_request_log_params_jsonb_normalize(UPDATE ... SET params = params::text::jsonb WHERE jsonb_typeof(params) = 'string', idempotent); v60-v65 six-migration chain wiring source-scoping intooauth_clients— v60 (oauth_clients_source_id_fk:source_id TEXTNULL→'default'backfill + FK tosources(id) ON DELETE SET NULL), v61 (federated_read TEXT[] NOT NULL DEFAULT '{}'), v62 (explicit-CASE backfill sosource_id IS NULL→'{}'), v63 (fail-loud check every row's source_id is in its federated_read array), v64 (FK flipped toON DELETE RESTRICT), v65 (GIN index for array-containment); v68eval_candidates_embedding_column(eval_candidates.embedding_column TEXT NULLper-row provenance forgbrain eval replayto reproduce the same retrieval space; NULL-tolerant); v108pages_embedding_signature(pages.embedding_signature TEXT NULL=<provider:model>:<dims>stamped viasetPageEmbeddingSignature; GRANDFATHER — stale predicate isembedding_signature IS NOT NULL AND embedding_signature <> $currentso NULL is NEVER stale and upgrade never re-embeds the whole corpus; no index; metadata-only); v109sources_newest_content_at(sources.newest_content_at TIMESTAMPTZdurable newest-COMMIT HEAD committer time written bywriteSyncAnchor, read by the REMOTE staleness path instead of shelling to git; mirror in pglite-schema.ts + schema.sql + bootstrap probe); v110page_aliases((id, source_id, alias_norm, slug, ...)withUNIQUE (source_id, alias_norm, slug)+ lookup indexes on(source_id, alias_norm)and(source_id, slug);alias_normisnormalizeAlias()output so WRITE/READ key on the same form; also insrc/core/pglite-schema.ts); v111search_telemetry_rank1_columns(ADD COLUMN IF NOT EXISTSon both engines:sum_rank1_score,count_rank1, three bucketsrank1_lt_solid/rank1_solid/rank1_highonsearch_telemetry— aggregate not per-query rows so rank-1 median drift is bounded-growth; ALTERs right after v57 which created the table); v114links_link_source_check_kebab_regex(#1941, openslink_sourcefrom the closed allowlist to a kebab-case format gate^[a-z][a-z0-9]*(-[a-z0-9]+)*$+char_length<=64; Postgres branch usesNOT VALID+VALIDATE CONSTRAINTwithtransaction:false, PGLite plain DROP+ADD; existing built-ins all satisfy the regex so VALIDATE never fails on existing data).src/core/progress.ts— Shared bulk-action progress reporter. Writes to stderr. Modes:auto(TTY\r-rewriting; non-TTY plain lines),human,json(JSONL),quiet. Rate-gated byminIntervalMsandminItems.startHeartbeat(reporter, note)for single long queries.child()composes phase paths. Singleton SIGINT/SIGTERM coordinator emitsabortevents for every live phase. EPIPE defense on both sync throws and stream'error'events. Zero dependencies.emitHumanLineis prefix-aware — inside awithSourcePrefix(id, ...)scope fromsrc/core/console-prefix.tsit prepends[id](and TTY-rewrite mode\r\x1b[2Kcarries the prefix inside the clear-to-EOL escape);emitJsonis intentionally NOT prefixed so NDJSON consumers don't choke on a[id] {...}shape.src/core/console-prefix.ts—AsyncLocalStorage<string>-backed per-source line-prefix helper. ExportswithSourcePrefix(id, fn)(runsfnwithidas active prefix; nested wraps replace then restore),getSourcePrefix()(read-only accessor; test seam), andslog(...)/serr(...)(prefix-awareconsole.log/console.error). Embedded-newline-safe: a multi-line string under prefix[foo]emits[foo] line1\n[foo] line2. Outside a wrap,slog/serrfall through to bareconsole.log/console.errorso single-source callers see identical output (back-compat invariant). Usesrc.id(slug-validated bysources add) NOTsrc.name(free-form) to defeat log-injection through newline/control-character names. Coverage:src/commands/sync.tsperformSync + callees,src/commands/embed.tsrunEmbedCore + helpers,src/core/progress.tsemitHumanLine.src/core/cli-options.ts— Global CLI flag parser.parseGlobalFlags(argv)returns{cliOpts, rest}with--quiet/--progress-json/--progress-interval=<ms>stripped.getCliOptions()/setCliOptions()expose a module-level singleton so commands reach resolved flags without parameter threading.cliOptsToProgressOptions()maps to reporter options.childGlobalFlags()returns the flag suffix to append toexecSync('gbrain ...')calls in migration orchestrators.OperationContext.cliOptsextends shared-op dispatch for MCP callers.src/core/db-lock.ts— generictryAcquireDbLock(engine, lockId, ttlMinutes)over thegbrain_cycle_lockstable. Parameterized lock id so scopes nest cleanly:gbrain-cyclefor the broad cycle (held bycycle.ts) andgbrain-sync(SYNC_LOCK_ID) forperformSync's narrower writer window. UPSERT-with-TTL semantics survive PgBouncer transaction pooling (unlike session-scopedpg_try_advisory_lock); crashed holders auto-release once their TTL expires. It also does automatic same-host dead-pid takeover: when the upsert finds a held, NOT-TTL-expired lock whose holder is on this host and provably dead, it reclaims via a guardedDELETE WHERE id=$1 AND holder_pid=$2+ one normal-upsert retry returning the standard handle (refresh/release intact). The liveness check is the exportedclassifyHolderLiveness(pid, host, ageMs, opts?)/isHolderDeadLocally(...)(injectableprocess.killseam;HOLDER_TAKEOVER_GRACE_MS = 60_000PID-reuse guard; EPERM classified asaliveso a live process you don't own is never stolen). TTL-expired locks stay the upsert's job; cross-host stays TTL-only.runBreakLock(src/commands/sync.ts) consumes the same predicate. Pinned bytest/db-lock-auto-takeover.test.ts.src/core/sync-concurrency.ts— single source of truth for the parallel-sync policy. ExportsautoConcurrency(engine, fileCount, override?)(PGLite always serial; explicit override clamped to >=1; auto path returnsDEFAULT_PARALLEL_WORKERS=4whenfileCount > AUTO_CONCURRENCY_FILE_THRESHOLD=100),shouldRunParallel(workers, fileCount, explicit)(explicit--workersbypasses the >50-file floor), andparseWorkers(s)(rejects'0','-3','foo','1.5', trailing chars). Used byperformSync,performFullSync,runImport, and the Minionsynchandler so the sites can't drift.DEFAULT_PARALLEL_SOURCES = 4is a SEPARATE constant for the per-source fan-out undergbrain sync --all— kept distinct fromDEFAULT_PARALLEL_WORKERSbecause total live Postgres connections per wave ≈DEFAULT_PARALLEL_SOURCES × DEFAULT_PARALLEL_WORKERS × 2 (per-file pool)= 32 at both defaults (each per-file worker opens its ownPostgresEnginewithpoolSize = min(2, resolvePoolSize(2)));sync.tswarns whenparallel × workers × 2 > 16.resolveWorkersWithClamp(engine, override, commandName, fileCount)wrapsautoConcurrencywith a per-command stderr clamp warning on PGLite (per-(command, requested) dedup via module-scoped warned-once set with_resetWorkersClampWarningsForTest()seam) and is the canonical surface for every bulk-command--workers Nflag (extract-conversation-facts, extract, edges-backfill, reindex-multimodal, reindex, reindex-code); embed.ts deliberately bypasses it and keepsGBRAIN_EMBED_CONCURRENCY || 20.resolveMaxConnections()(readsGBRAIN_MAX_CONNECTIONS, undefined when unset) +clampWorkersForConnectionBudget(workers, perWorkerPool, maxConnections, parentPool)back the opt-in single-sync connection-footprint clamp so a big sync stays under a low pooler cap (parent_pool + workers×perWorkerPool ≤ budget);gbrain doctor'spool_budgetcheck (computePoolBudgetCheck/checkPoolBudgetinsrc/commands/doctor.ts) warns when the budget leaves no room for a worker, pointing atGBRAIN_POOL_SIZE=2. Pinned bytest/pglite-workers-clamp.test.ts.src/core/worker-pool.ts— Canonical sliding-pool + bounded-semaphore primitive (extracted fromsrc/commands/embed.tssliding-pool sites andsrc/commands/eval-cross-modal.tsrunWithLimitsemaphore). Two exports:runSlidingPool<T>({items, workers, onItem, signal?, onError?, failureLabel?, onProgress?})+runWithLimit<TIn, TOut>({items, limit, fn, signal?}). Atomicity invariant:const idx = nextIdx++is one synchronous JS statement (noawaitbetween read and write — guaranteed by the single-threaded event loop), documented in the module header AND enforced byscripts/check-worker-pool-atomicity.sh(wired intobun run verify), which rejects importingworker_threadsin any consuming file and insertingawaitbetween thenextIdxread and write.MUST_ABORT_ERROR_TAGSset is seeded withBUDGET_EXHAUSTEDfromsrc/core/budget/budget-tracker.ts; tagged errors (matched viaerr.tag === 'BUDGET_EXHAUSTED'to avoid cross-module import) bypassonErrorand hard-abort the pool viaAbortController.abort()to in-flightonItem— the budget cap is a structural ceiling under concurrency.failures[]shape is{idx, label, error}records (NOT full items; callers supplyfailureLabel(item) => string) for bounded memory under huge brains. Pinned bytest/worker-pool.test.ts+test/scripts/check-worker-pool-atomicity.test.ts. Drives every--workers Nbulk command.src/commands/embed.tsextension — both inline sliding-pool sites (embedAllsimple at:458-467andembedAllStalepaginated + AbortSignal at:586-632) callrunSlidingPoolfrom the shared worker-pool helper. Invariant-level contract preserved (counts + cost + AbortSignal propagation + per-batch rate-limit retry viaembedBatchWithBackoff); byte-equality on progress-event ORDERING is NOT promised. TheGBRAIN_EMBED_CONCURRENCY || 20default is preserved and embed bypassesresolveWorkersWithClampbecause the 20-worker default would otherwise silently change every brain's embed hot path. Pinned bytest/embed-helper-migration.test.ts(asserts the helper is wired in AND the pre-migrationlet nextIdx = 0+Promise.all(Array.from({length: numWorkers}, ...))shapes are gone).src/commands/extract-conversation-facts.tsextension —--workers Nfor LLM-bound fact extraction over conversation pages, with a per-page advisory lock viasrc/core/db-lock.ts:withRefreshingLock(lock idextract-conversation-facts:<source>:<slug>, TTLPER_PAGE_LOCK_TTL_MINUTES=2with 20s refresh viaMath.max(15s, 120s/6);LockUnavailableErrortriggers skip-and-continue with rate-limited log per (source, minute) +pages_lock_skippedcounter + CLI exits 3 when non-zero AND no hard failures).deleteOrphanFactsForPage(engine, sourceId, slug)provides delete-orphans-first replay safety — wipes facts from a prior crashed run for this (sourceId, slug) before re-extracting, closing the "terminal audit row written after partial insertFacts failure" class.assertFactsEmbeddingDimMatchesConfig(engine)is the startup preflight (throwsFactsEmbeddingDimMismatchErrorwith paste-ready ALTER hint BEFORE the first insert; cached per engine via WeakMap). Result type carriespages_lock_skipped+orphan_facts_cleaned. Checkpoint state is a sharedcpMap: Map<slug, endIso>(NOT a per-page-mutatedcpEntries: string[]) so atomicMap.setsurvives parallel workers. Minion handlerextract-conversation-factsinsrc/commands/jobs.tsround-tripsworkersviajob.data.workersfor--background --workers 20. Cycle config keycycle.conversation_facts_backfill.workers(default 1; opt-in concurrency under brain-wide cost + walltime caps). Pinned bytest/extract-conversation-facts-workers.test.ts+ the existing extract-conversation-facts behavioral tests.src/core/embedding-dim-check.tsextension — facts.embedding dim drift surface.readFactsEmbeddingDim(engine): Promise<FactsColumnDimResult>covers bothvector(N)andhalfvec(N)shapes (migration v40 falls back tovectoron pgvector < 0.7); regex ordering is halfvec-before-vector (substring "vec" appears in "halfvec"; naive/vector/iwould shadow).buildFactsAlterRecipe(dims, configured, type)emits the paste-readyDROP INDEX IF EXISTS idx_facts_embedding_hnsw; ALTER TABLE facts ALTER COLUMN embedding TYPE halfvec(N) USING embedding::halfvec(N); CREATE INDEX idx_facts_embedding_hnsw ON facts USING hnsw (embedding halfvec_cosine_ops) WHERE ...flow (NOT bare REINDEX, which doesn't rewrite the index after a column-type change).assertFactsEmbeddingDimMatchesConfig(engine)is the preflight — throwsFactsEmbeddingDimMismatchError(taggedtag: 'FACTS_EMBEDDING_DIM_MISMATCH'for parity with the worker-pool MUST_ABORT semantics) when configured dim ≠ column width; cached per-engine viaWeakMap; PGLite engines silently skip. Doctor checkfacts_embedding_width_consistency(registered afterembedding_width_consistency) reuses the same helpers with an identical ALTER recipe. Pinned bytest/embedding-dim-check-facts.test.ts.src/core/postgres-engine.tsextension —insertFact+insertFactsno longer hardcodetx.unsafe(\'${embedLit}'::vector`)for the embedding column.resolveFactsEmbeddingCast()(private) probespg_attributeonce per engine instance (cached in_factsEmbeddingCastSuffix) and returns'::halfvec'when migration v40 created the column as halfvec, else'::vector'; both insert paths use the cached suffix so the cast matches the actual column type (works on older pgvector that lacks implicit auto-cast). Test seam__resetFactsEmbeddingCastCacheForTest()` clears the per-engine cache.src/core/cycle.ts+src/core/cycle/extract-atoms.ts+src/core/cycle/synthesize-concepts.ts+src/commands/extract.ts+src/commands/doctor.ts+src/core/op-checkpoint.tsextensions — six daily-driver ops fixes. (1) Batch idempotency:atomsExistingForHashes(engine, sourceId, hashes[])(exported fromsrc/core/cycle/extract-atoms.ts) replaces the per-hash loop (7K individual queries) with one batched SQL roundtrip returning already-extractedcontent_hash16values; fail-open (SQL error → empty set, extraction proceeds); powered by migration v104pages_atom_source_hash_idx(partial expression index onfrontmatter->>'source_hash'for atom rows wheredeleted_at IS NULL; PostgresCREATE INDEX CONCURRENTLYwith invalid-remnant pre-drop, PGLite plain). (2) Shorter cycle lock TTL + active in-phase refresh:LOCK_TTL_MINUTES = 5(was 30);buildYieldDuringPhase(lock, outer)(exported, withLockHandle) callslock.refresh()+ any external hook on every fire, throttled to 30s viamaybeYield, firing both in the main loop AND immediately after everyawait chat(...);synthesize_conceptsuses the same throttled hook. A crashed cycle releases its lock 6x faster while a healthy long-running cycle keeps it alive (residual: a singleawait chat()past 5 min can expire the lock mid-await — TODO-OPS-2). (3) Progress wiring:progress?: ProgressReporteropt onExtractAtomsOptsandSynthesizeConceptsOpts; cycle.ts passes its phase-level reporter down (NOT a child reporter, which would collide oncycle.extract_atoms.extract_atoms.work); phases only calltick()/heartbeat(), cycle.ts ownsstart()/finish(). (4)by-mentionresume:mentionsFingerprint({source, type, since, gazetteerHash})insrc/core/op-checkpoint.ts— the gazetteer hash is load-bearing (adding entity pages mid-pause shifts the hash → new fingerprint → fresh scan against the new gazetteer, never silent skip);gbrain extract links --by-mentionresumes viaop_checkpointswithflushAndCheckpointordering (links flush to DB FIRST, page keys commit to checkpoint SECOND, persist THIRD, so a crash mid-batch leaves the page un-checkpointed and resume re-scans it); persist every 1000 items OR 30s; clean exit clears the checkpoint;--dry-runskips both load and write. (5)sync_consolidationdoctor check (multi-source brains see a paste-readygbrain sync --all --parallel 4 --workers 4 --skip-failed; single-source "not applicable"; SQL errors returnwarnvia the check's own try/catch). (6) Test-isolation:test/cycle-last-full-cycle-at.test.ts+test/schema-cli.test.tsuse per-testGBRAIN_HOME=tempdir. Pinned bytest/cycle/extract-atoms-batch.test.ts,test/cycle/cycle-lock-ttl.test.ts(regression pin onLOCK_TTL_MINUTES === 5),test/op-checkpoint-mentions-fingerprint.test.ts,test/cycle/extract-atoms-progress.test.ts,test/cycle/synthesize-concepts-progress.test.ts,test/cycle/yield-during-phase-refresh.test.ts,test/cycle/yield-during-phase-throttle.test.ts,test/extract-by-mention-resume.test.ts,test/doctor-sync-consolidation.test.ts. Companionsync --allrecipe block inskills/cron-scheduler/SKILL.md.src/commands/sync.ts—gbrain syncCLI + theperformSync/performFullSynclibrary entrypoints (consumed by the autopilot cycle and the Minion sync handler).performSyncruns under a writer lock: per-sourcegbrain-sync:<sourceId>wheneveropts.sourceIdis set, wrapped inwithRefreshingLockfromsrc/core/db-lock.tsso long-running sources (250K+ chunks) don't lose the lock at the 30-min TTL mid-run; the bare no-source path uses the SAME refreshing lock (#1794 — it was previously a non-refreshingtryAcquireDbLock, stealable mid-run during an incident);SyncOpts.lockId?: stringis the explicit override. The lock refresh AND its health probe route through the DIRECT session pool so Supavisor transaction-pool exhaustion (EMAXCONNSESSION) can't kill renewal; takeover is heartbeat-aware (it will NOT steal a holder whoselast_refreshed_atis withinGBRAIN_LOCK_STEAL_GRACE_SECONDS, defending an alive-but-starved holder); the import loop yields the event loop everyGBRAIN_SYNC_YIELD_EVERYfiles (setTimeout(0), notsetImmediate— Bun starves the timers phase) so the refreshsetIntervalheartbeat fires mid-import. This lock-identity invariant prevents async --allper-source worker racingsync --source fooon the global lock from corrupting the same source.performSyncthrows a typedSyncLockBusyErrorwhen the writer lock is held; the Minionsynchandler (src/commands/jobs.ts) catches it and marks the job SKIPPED (not failed) so a cron/autopilot tick defers to the holder without polluting crash metrics.performSyncInneris RESUMABLE (incremental path): it drains a PINNED target commit (lastCommit..pin), banking drained file paths viaappendCompleted(append-only delta into theop_checkpoint_pathschild table, migration v115 — one row per path, O(delta) not the old O(N²) full-array rewrite), keyed bysyncFingerprint({sourceId, lastCommit})fromsrc/core/op-checkpoint.ts(paths underop:'sync'; the pinned target underop:'sync-target'), and advanceslast_commit/last_sync_atONLY at full import completion. Checkpoint writes route through the DIRECT session pool + bounded retry so they surviveEMAXCONNSESSION; the flush cadence is first-file then everyGBRAIN_SYNC_CHECKPOINT_EVERY(default 1000) files ORGBRAIN_SYNC_CHECKPOINT_SECONDS(default 10s), with a race-safependingCheckpointPathsdelta (single-flight swap, re-merge on failure) under parallel workers; a SIGTERM banks the in-flight delta via a no-retry one-shot (appendCompletedOnce, ordered before lock release throughregisterCleanup); and sustained flush failure aborts the run withreason:'checkpoint_unavailable'afterGBRAIN_SYNC_MAX_CHECKPOINT_FAILURESconsecutive fails rather than importing work it can never bank (every partial/blocked exit logs the banked-file count). A sync killed mid-import banks its progress, leaves the anchor unmoved (the source stays correctly stale to the autopilot scheduler —last_sync_atis never bumped on a partial), and the next runresumeFilters the same fixed diff to skip done files. The pin is the checkpoint's stored target when still reachable from HEAD, else live HEAD (a history rewrite / reset re-pins); completion advances to the pin, NOT live HEAD, so commits landing past the pin are a clean next-sync diff — this closes the cross-run staleness window. After import a pin-reachability gate (git merge-base --is-ancestor pin HEAD) replaces the old strict head-drift gate: forward commits on top of the pin (e.g. a background enrich process committing to the same repo every ~2 min) are SAFE and no longer block the run; only a real rewrite (pin not an ancestor of HEAD) blocks and discards the checkpoint. A file added inlastCommit..pinbut gone from disk (deleted by a commit after the pin) is SKIPPED and marked completed, not recorded as a failure. Downstream extract/facts/embed are size-gated: inline only fortotalChanges <= 100; large syncs defer to the resumableextract --stalewatermark +embed --stale/backfill + the facts cycle phases, so a 44K-page facts/embed pass never re-blocks import convergence (sync convergence == import convergence). Worker engines wrap in try/finally so disconnect always fires; both PGLite-detection sites useengine.kind === 'pglite'. CLI accepts--workers N(alias--concurrency N) validated viaparseWorkers(explicit bypasses the file-count floor; auto path defers toautoConcurrency()). The newest-first descending-lex order usessortNewestFirst(addsAndMods)fromsrc/core/sort-newest-first.ts(shared withgbrain import).gbrain sync --allruns a continuous worker pool:parseWorkers-validated--parallel N(defaultmin(sourceCount, --workers, DEFAULT_PARALLEL_SOURCES=4)), long-lived async workers pulling from a shared FIFO queue (no head-of-line blocking), per-sourcewithSourcePrefix(src.id, ...)so everyslog/serrline carries[<source-id>];--skip-failed/--retry-failedreject when combined with--parallel > 1(the brain-globalsync-failures.jsonlhas no per-source scope); a connection-budget stderr warning fires whenparallel × workers × 2 > 16(the× 2 per-file poolfactor: each per-file worker opens its ownPostgresEnginewithpoolSize=2). ExportsresolveParallelism,syncOneSource,buildSyncStatusReport,printSyncStatusReport,SyncStatusReportback thegbrain sources statusdashboard.--jsonenvelope{schema_version: 1, sources, parallel, ok_count, error_count}on stdout; human banners route to stderr viahumanSinksojqparses cleanly. Exit matrix: 0 all ok, 1 any error, 2 cost-prompt-not-confirmed. The dashboard SQL iscontent_chunks ch JOIN pages pg ON pg.id = ch.page_id WHERE pg.deleted_at IS NULLwitharchived = falseat the caller; embedding column resolved viaresolveEmbeddingColumn(undefined, cfg)fromsrc/core/search/embedding-column.tsso Voyage/multimodal/non-default-column brains count against the column they use; errors propagate (no swallow-catch). The sync delete loop is interleaved per-batch resolve+delete usingengine.resolveSlugsByPaths+engine.deletePagesfromsrc/core/engine.ts(73K-delete commit: ~146K SQL round-trips → ~292, closing the cascade-staleness class where one big-delete commit jammed every other source's sync); per-batch try-catch decomposes batch DELETE failures to per-slugdeletePagefallback, unrecoverable per-slug failures land infailedFiles;pagesAffectedfilters to confirmed-deleted slugs. The rename loop gets the same batched slug-resolve; a rename whose destination is un-syncable folds the source path into the delete set so the old page can't orphan (#1970). An entry-time bookmark-reachability guard distinguishes a gc'd anchor (cat-filefails →performFullSync) from a history-rewrite anchor that is merely no-longer-an-ancestor but still on disk: the latter is diffed tree-to-tree directly (git diff lastCommit..pinis an endpoint-tree compare, ancestry not required) so a force-push /master→mainconsolidation imports only the real delta instead of re-walking the whole tree forever (#1970); an oversized or failed diff degrades toperformFullSync.performFullSyncis itself authoritative for deletes — after an advancing full import it purges file-backed pages (source_path != nullAND strategy-awareisSyncable) whose source file no longer exists, sparingput_page/manual pages (nullsource_path) and metafiles.resolveSlugByPathOrSourcePathatsync.ts:267delegates toengine.resolveSlugsByPathswhensourceIdis set, keeping legacyexecuteRawfallback for the no-sourceId path.failedFilesis hoisted to the top ofperformSyncInnerso both delete-decompose and import loops feed the same bookmark gate. Thesync --allcost gate is mode-aware viawillEmbedSynchronously+shouldBlockSyncfromsrc/core/embedding.ts(federated_v2 resolved once up front): the DEFERRED path (v2 on, parallel) is INFORMATIONAL (embedding goes to per-sourceembed-backfilljobs with their own$X/source/24hcap, default $25 viaSPEND_CAP_CONFIG_KEYfromembed-backfill-submit.ts; prints the cap + backlogsumStaleChunkChars({signature})priced viaestimateCostFromCharsatcurrentEmbeddingPricePerMTok()+ queued-job count, NEVER exits 2); the INLINE path BLOCKS only when the new-content estimate exceedssync.cost_gate_min_usd(default $0.50). The new-content estimate (estimateInlineNewTokens) contributes ZERO for sources provably unchanged since last sync (isSourceUnchangedSinceSyncHEAD==last_commit + clean tree ANDchunker_version === CURRENT) and the full-tree ceiling for changed sources; the pre-existing stale backlog is shown informationally but NEVER gated on (sync doesn't sweep it inline). HelpersresolveCostGateFloorUsd(engine)(fail-open $0.50, accepts 0) +resolveBackfillCapUsd(engine). JSON envelopes gainmode+gatediscriminators (dry_run | deferred_notice | below_floor | confirmation_required);SyncStatusReportSourcegainsbackfill_queued/backfill_active/backfill_last_completed_at; cost previews readgetEmbeddingModelName()(no hardcoded OpenAI).SyncOpts.noSchemaPack(CLI--no-schema-pack, threaded throughperformSyncANDsyncOneSource) skipsloadActivePackso pages fall back to legacy prefix typing — an escape hatch when a suspect pack regex wedges a sync. A per-file BEGIN heartbeatif (process.env.GBRAIN_SYNC_TRACE) serr('[sync] begin import: <path>')fires BEFOREimportFile(theprogress.tickfires only AFTER) so a stuck file is a begin-line with no matching completion. Triage doc:docs/architecture/serve-sync-concurrency.md(PGLite single-writer serve↔sync contention + theGBRAIN_SYNC_TRACE+--no-schema-packrecipes). Pinned bytest/e2e/sync-status-pglite.test.ts(IRON-RULE: PGLite seeds 2 sources × pages × chunks, soft-deletes 1 page, archives 1 source, validates the SQL excludes both AND uses the active embedding column),test/sync-cost-gate.serial.test.ts,test/sync-cost-preview.test.ts. Runaway-sync protection:resolveSyncHardDeadline(args, {isTty, env, defaultNonTtySec?})resolves a wall-clock hard deadline (precedence--no-hard-deadline>--hard-deadline <s>>--timeout <s>(non---all, which auto-arms the backstop) >GBRAIN_SYNC_MAX_RUNTIME_SECONDSenv > non-TTY default 3600s > none;HARD_DEADLINE_GRACE_SEC=30).src/cli.tsinstalls the out-of-band watchdog (seesrc/core/process-watchdog.ts) for the sync command BEFOREconnectEngineand disposes it in the dispatchfinally, so even an event-loop-starved sync — or a connect-phase hang — is SIGTERM-then-SIGKILLed by the deadline instead of orphaning under cron.runSyncregisters a SIGINT handler that aborts an interruptAbortControllercomposed viacomposeAbortSignals(...)(anAbortSignal.anywrapper over the defined signals) with the per-source--timeoutsignal, so Ctrl-C returns a cleanpartialand releases the lock through the normalfinally(process-cleanup.ts owns SIGTERM lock-release; the watchdog owns the hard kill).withRefreshingLockunref()s its refreshsetInterval. The spin's own root cause is not yet pinned (leading lead is catastrophic-backtracking in a pack link-inference regex, bounded by the redos-guard); the watchdog heartbeat plus the existing[gbrain phase]breadcrumbs are the diagnosis surface. Pinned bytest/sync-hard-deadline.test.ts(resolution precedence +composeAbortSignals).src/commands/import.ts—gbrain importCLI +runImportlibrary entrypoint. Uses a path-set checkpoint viasrc/core/import-checkpoint.ts(the walk still appliessortNewestFirst()for embed-cost ordering, but checkpoint correctness no longer depends on sort order). A file enterscompleted: Set<relativePath>only when itsprocessFilereturns success (including content-hash short-circuit no-ops); failed files never enter the set so the next run retries them automatically with no manual~/.gbrain/import-checkpoint.jsondelete. This closes three classes: parallel-import-with-slow-worker dropping the slow file on crash-resume (the slow file isn't incompleteduntil its ownprocessFileresolves), failed-file-bumps-counter-past-itself (failures don't add tocompleted), and sort-flip-drops-newest-N-on-cross-version-resume (order is no longer part of the checkpoint). Old positional checkpoints are detected and discarded with a stderr line on first resume (re-walking is cheap becausecontent_hashshort-circuits unchanged files). Checkpoint persists every 100 successful adds, not every 100 processed files. ThemanagedBookmarkopt (set byperformFullSyncwhenrunImportis the full-sync engine) suppressesrunImport's ownsync.last_commitadvance so the sharedapplySyncFailureGate(src/core/sync-failure-ledger.ts) owns the bookmark + failure-ledger gating on that path — one gate decides advance/block/auto-skip across both sync paths. Pinned bytest/import-checkpoint.test.ts+test/import-resume.test.ts(incl. the SLUG_MISMATCH retry regression).src/core/import-checkpoint.ts—loadCheckpoint(brainDir),saveCheckpoint(brainDir, completed),resumeFilter(files, completed, brainDir),clearCheckpoint(), plus theImportCheckpointtype. Path-set format{schema_version, brainDir, completed: string[]}. Atomic write via.tmp+rename()so a mid-write crash never leaves a partial JSON.loadCheckpointreturnsnullon: missing file, malformed JSON, brainDir mismatch (ran against a different brain), and the old positional format (logged to stderr before discard).resumeFilterreturns{toProcess, skippedCount}— pure, no I/O, deterministic.clearCheckpointis no-op-on-missing for clean-exit cleanup. HonorsGBRAIN_HOMEviagbrainPath()sowithEnv({GBRAIN_HOME: tmpdir})test isolation works without monkey-patching fs. Best-effort persistence —saveCheckpointlogs warnings on write errors but never throws.src/core/sort-newest-first.ts— single source of truth for the descending-lex sort thatgbrain importandgbrain syncboth apply. Mutates in place (Array.prototype.sort semantics), returns the same array reference for fluent chaining. Empty/single-element inputs short-circuit. Future ordering changes flip one line here instead of touching two CLI commands. Pinned bytest/sort-newest-first.test.ts(descending order, mixed prefixes, empty, single-element, in-place-mutation contract).src/core/cycle.ts— brain maintenance cycle primitive (9 phases).runCycle(engine: BrainEngine | null, opts: CycleOpts): Promise<CycleReport>composes phases in semantic order: lint → backlinks → sync → synthesize → extract → patterns → recompute_emotional_weight → embed → orphans.synthesizeruns after sync (cross-references see a fresh brain) and before extract (auto-link materializes its writes);patternsruns after extract so it reads a fresh graph (subagent put_page setsctx.remote=trueand skips auto-link/timeline by default, so extract is the canonical materialization);recompute_emotional_weightsees the union ofsyncPagesAffected+synthesizeWrittenSlugsincrementally, or all pages when neither anchor is set (full backfill viagbrain dream --phase recompute_emotional_weight).CycleReport.schema_version: "1"is stable;totalsis additive (pages_emotional_weight_recomputed,transcripts_processed,synth_pages_written,patterns_written). Three callers:gbrain dreamCLI,gbrain autopilotdaemon inline path, the Minionsautopilot-cyclehandler. Coordination viagbrain_cycle_locksDB table +~/.gbrain/cycle.lockfile lock with PID-liveness for PGLite.yieldBetweenPhasesruns between phases;yieldDuringPhaseis in-phase keepalive (synthesize/patterns renew the cycle-lock TTL during long waits). Engine nullable; lock-skip on read-only phase selections.CycleOpts.signal?: AbortSignalpropagates the worker's abort signal withcheckAborted()between every phase.runPhaseSyncreturnspagesAffectedviaSyncPhaseResult(threaded torunPhaseExtractas the 4th arg) and takeswillRunExtractPhase: booleansettingnoExtract: phases.includes('extract')sogbrain dream --phase syncdoesn't silently lose extraction.resolveSourceForDir(engine, brainDir)threadssourceIdtoperformSync()so sync reads the per-sourcesources.last_commitanchor (not the drift-prone globalconfig.sync.last_commit).CycleOpts.brainDirisstring | null; when null (checkout-less postgres/Supabase brain) the 6 filesystem phases (lint/backlinks/sync/synthesize/extract/patterns) skip withdetails.reason: 'no_brain_dir'and the DB-only phases run;resolveSourceForDiris null-tolerant.cycleSourceId = opts.sourceId ?? resolveSourceForDir(engine, brainDir)is the canonical per-source scope forextract_facts/extract_atoms/calibration sogbrain dream --source repo-areconciles repo-a's facts even with no checkout (instead of scoping to'default'while stamping repo-a fresh).deriveStatuscountsedges_resolved/edges_ambiguousas work so an edges-only cycle reportsoknotclean; thejobs.tsautopilot-cycle+ phase-wrapper handlers passnull(not'.') when no repo is configured. Pinned bytest/dream-postgres.serial.test.ts+test/jobs-autopilot-cycle-braindir.serial.test.ts.src/core/cycle/synthesize.ts— Synthesize phase: conversation-transcript-to-brain pipeline. Readsdream.synthesize.session_corpus_dir, runs a cheap Haiku verdict (cached indream_verdicts), then fans out one Sonnet subagent per worth-processing transcript withallowed_slug_prefixes(sourced fromskills/_brain-filing-rules.jsondream_synthesize_paths.globs). Orchestrator collects slugs fromsubagent_tool_executions(NOTpages.updated_at) and reverse-renders DB → markdown viaserializeMarkdown. Cooldown viadream.synthesize.last_completion_ts, written ONLY on success. Idempotency keydream:synth:<file_path>:<content_hash>.--dry-runruns Haiku, skips Sonnet. Subagent never gets fs-write access.renderPageToMarkdown(exported) stampsdream_generated: true+dream_cycle_dateinto every reverse-write's frontmatter;writeSummaryPagedoes the same on the summary index — this marker is the explicit identity surfaceisDreamOutputchecks intranscript-discovery.ts.judgeSignificanceandJudgeClientare exported;judgeSignificancetakes averdictModelparam loaded fromdream.synthesize.verdict_modelvialoadSynthConfig.splitTranscriptByBudget(content, contentHash, maxChars)splits oversized transcripts at paragraph boundaries (## Topic:→---→\nladder) using a deterministic offset seeded from the first 32 bits ofcontentHashso retries chunk identically; per-chunk char budget =MODEL_CONTEXT_TOKENS[resolvedModel] × 0.9 × 3.5 chars/token(non-Anthropic ids fall back to a 180K-token safe default + once-per-process stderr warn); operator overridesdream.synthesize.max_prompt_tokens(floor 100K, wins) anddream.synthesize.max_chunks_per_transcript(default 24). Per-chunk idempotency keysdream:synth:<filePath>:<hash16>:c<i>of<n>; single-chunk transcripts preserve the legacydream:synth:<filePath>:<hash16>key byte-for-byte so existing brains skip withalready_synthesized_legacy_single_chunkinstead of re-spending Sonnet.collectChildPutPageSlugsraw-fetches every (job_id, slug) pair (notSELECT DISTINCT) and rewrites bare-hash6 slugs to<hash6>-c<idx>for chunked children (orchestrator-side, zero Sonnet trust). Cap-hit skips don't write todream_verdictsso raising the cap on next run re-attempts cleanly. Bounds INITIAL prompt size only; tool-loop turn-N accumulation is caught by terminal-error classification insubagent.ts. Verdict routing is gateway-routed:makeJudgeClient(verdictModel)(exported, replacingmakeHaikuClient()) mirrorstryBuildGatewayClientinsrc/core/think/index.ts— a construction-time provider/key probe returnsnullon a clear miss (unknown provider id viaresolveRecipeAIConfigError, or Anthropic provider with no key viahasAnthropicKey()). The verdict loop wrapsjudgeSignificancein try/catch forAIConfigErrorso mid-run provider failures surface as per-transcriptworth=false, reasons=['gateway error: ...']instead of crashing the phase. Canonical config keymodels.dream.synthesize_verdict(perPER_TASK_KEYSinsrc/core/model-config.ts);JudgeClientsignature preserved verbatim for test-seam stability; CI guardscripts/check-gateway-routed-no-direct-anthropic.shprevents reintroducingnew Anthropic()here or inthink/index.ts. At the queue.add boundary (lines 395-404) a conditionalanthropic:prefix is applied ONLY when the resolved model has no colon AND starts withclaude-(becauseresolveModelreturns bare ids fromTIER_DEFAULTS/DEFAULT_ALIASESand the subagent validator requiresprovider:modelform) — avoids changing the shared constants which would ripple across everyresolveModelcaller. Pinned bytest/cycle/synthesize-gateway-adapter.test.ts,test/e2e/dream-synthesize-pglite.test.ts(gateway-adapter mid-run AIConfigError catch),test/cycle/regression-pr-wave-r1-r2-r4.test.ts.scripts/check-gateway-routed-no-direct-anthropic.sh— CI guard that fails the build ifsrc/core/cycle/synthesize.tsorsrc/core/think/index.tsreintroduces a runtimenew Anthropic()constructor call or a value-shapedimport Anthropic from '@anthropic-ai/sdk'import. Type-only imports (import type Anthropic from '@anthropic-ai/sdk') stay allowed for adapter types; comment lines (//or*prefixes) are excluded so historical JSDoc doesn't false-fire. Mirrorsscripts/check-jsonb-pattern.sh. Wired intobun run verifyandbun run check:all. ExtendGUARDED_FILESwhen migrating another file off direct SDK construction.src/core/cycle/patterns.ts— Patterns phase: cross-session theme detection over reflections withindream.patterns.lookback_days(default 30). Names a pattern only when ≥dream.patterns.min_evidence(default 3) reflections support it. Single Sonnet subagent; same allow-list path as synthesize. Runs AFTERextractso the graph is fresh.src/core/cycle/extract-facts.ts— extract_facts cycle phase. Fence is canonical: per-page wipe (deleteFactsForPage) + reinsert fromparseFactsFence+extractFactsFromFenceText+engine.insertFacts. Empty-fence guard refuses when legacy rows (row_num IS NULL AND entity_slug IS NOT NULL) pend backfill (status: warn, hint:gbrain apply-migrations --yes). A phantom-redirect pre-pass runs AFTER the legacy-row guard, BEFORE the main reconcile loop: whenopts.brainDiris set,runPhantomRedirectPass(engine, brainDir, sourceId, dryRun)walks unprefixed-slug pages capped byGBRAIN_PHANTOM_REDIRECT_LIMIT(default 50). The pass returnstouched_canonicals— canonical slugs whose disk fence merged with phantom rows;runExtractFactsUNIONs them into the main reconcile slug set so canonical's DB facts derive from the merged fence in the same cycle (handles phantom-had-only-on-disk-fence).ExtractFactsResultgains six phantom fields:phantomsScanned,phantomsRedirected,phantomsAmbiguous,phantomsSkippedDrift,phantomsLockBusy,phantomsMorePending. Three bubble toCycleReport.totals(phantoms_redirected,phantoms_ambiguous,phantoms_skipped_drift).src/core/entities/resolve.ts— Free-form entity name → canonical slug resolution.resolveEntitySlug(engine, source_id, raw): exact slug → fuzzy (pg_trgm @ 0.4 threshold) → bare-name prefix expansion (people/<token>-%thencompanies/<token>-%,connection_countcorrelated-subquery tiebreaker) → deterministicslugifyfallback. Two helpers for the phantom-redirect pass:resolvePhantomCanonical(engine, sourceId, phantomSlug)SKIPS the exact-slug step (a phantom slug'alice'would exact-match itself and no-op the redirect); returns the canonical only when non-null AND contains/.findPrefixCandidates(engine, sourceId, token)is a standalone SQL query returning ALL candidates acrossPREFIX_EXPANSION_DIRS(hardcoded['people', 'companies']) viaslug LIKE ANY($N::text[])over patternsdir/token+dir/token-%, cap of 10 ordered byconnection_count DESC, slug ASC— NOT a wrapper aroundtryPrefixExpansion(that path returns per-dir top-1 and suppresses ambiguity by design). Pinned bytest/phantom-redirect.test.ts(resolvePhantomCanonical 3 cases + findPrefixCandidates 6 cases incl. multi-dir ambiguity and thepeople/aliceberg-doesn't-match-alicefalse-positive guard).src/core/cycle/phantom-redirect.ts— Phantom-redirect orchestrator. ExportsrunPhantomRedirectPass(engine, brainDir, sourceId, dryRun): Promise<PhantomPassResult>(per-cycle wrapper acquiring thegbrain-syncwriter lock once for the whole pass, 30s bounded retry, walks up toGBRAIN_PHANTOM_REDIRECT_LIMITunprefixed phantoms) +tryRedirectPhantom(engine, page, sourceId, brainDir, dryRun): Promise<RedirectResult>+stripFenceAndFrontmatterAndLeadingH1(pure body-shape gate helper — strips facts fence incl. preceding## Factsheading and the leading H1; zero residue = phantom). Handler order: body-shape gate →resolvePhantomCanonical(bypasses exact-self-match) →findPrefixCandidatesambiguity check →fenceDbDriftbi-directional check → dry-run early exit → materialize canonical viaserializeMarkdownif DB-only → append phantom fence rows to canonical's disk fence with(claim, valid_from)dedup-guard + row_num continuation →engine.refreshPageBodywith SHA-256 content_hash recomputed via the import-file shape →engine.migrateFactsToCanonical(lossless) →engine.rewriteLinks(DB FK rewrite; wiki-link text rewrite is a documented follow-up) →engine.softDeletePage+engine.deleteFactsForPage(phantom)+fs.unlinkSync(phantomPath).RedirectResult.canonicalpopulated on'redirected'(incl. dry-run preview) so the caller buildstouched_canonicals. Idempotent on re-run: phantom soft-deleted → predicate fails (deleted_at IS NULL); migrate UPDATE matches no rows; dedup-guard prevents double-append.src/core/facts/phantom-audit.ts— JSONL audit at${resolveAuditDir()}/phantoms-YYYY-Www.jsonl. Pattern copy ofsrc/core/audit-slug-fallback.ts(ISO-week rotation, honorsGBRAIN_AUDIT_DIR). ExportslogPhantomEvent(record)+readRecentPhantomEvents(days)+computePhantomAuditFilename(now?). Records every outcome:redirected | ambiguous | drift | no_canonical | not_phantom_has_residue | pass_skipped_lock_busy. Best-effort writes — stderr warn on failure, never throws. Separate file fromstub-guard-audit.ts(distinct consumer + lifecycle: stub-guard logs PREVENTIVE blocks; phantom-audit logs CLEANUP decisions, to be read by a futurephantoms_pendingdoctor check).src/core/cycle/emotional-weight.ts— Pure functioncomputeEmotionalWeight({tags, takes}, {highEmotionTags?, userHolder?}). Deterministic 0..1 score: tag-emotion boost (max 0.5, case-insensitive match againstHIGH_EMOTION_TAGSseed list), take density (0.1/take, capped at 0.3), take avg weight (0..0.1), user-holder ratio (0..0.1 over active takes; default holder'garry'). Total clamped to [0..1]. Anglocentric / personal-life-biased seed list intentional; override via configemotional_weight.high_tags(JSON array).userHolderoverridable viaemotional_weight.user_holder.src/core/cycle/anomaly.ts— Pure stats helpers forfind_anomalies.meanStddevreturns sample stddev (n-1 denominator) and (0,0) for empty input.computeAnomaliesFromBuckets(baseline, today, sigma, limit)takes densified daily-count buckets + today's counts per cohort, returnsAnomalyResult[]. Zero-stddev fallback: cohort fires whencount > mean + 1, withsigma_observed = count - meanas a finite sort proxy (no NaN). Brand-new cohorts (no baseline) havemean=0, stddev=0so the fallback fires at count >= 2. Sorted bysigma_observeddesc, toplimit(default 20).page_slugscapped at 50 per cohort.src/core/cycle/recompute-emotional-weight.ts— Cycle phase orchestrator. Two SQL round-trips:engine.batchLoadEmotionalInputs(slugs?)→computeEmotionalWeight(per-row pure function) →engine.setEmotionalWeightBatch(rows). Reads configemotional_weight.high_tags(JSON array, falls back to default seed list on parse error) andemotional_weight.user_holder. EmptyaffectedSlugsshort-circuits with zero-work success. dry-run reports the would-write count without touching the DB. Engine throw bubbles intostatus: 'fail'with codeRECOMPUTE_EMOTIONAL_WEIGHT_FAILso the cycle continues.src/core/transcripts.ts—listRecentTranscripts(engine, opts)library reused by both thegbrain transcripts recentCLI and theget_recent_transcriptsMCP op. Readsdream.synthesize.session_corpus_dir+dream.synthesize.meeting_transcripts_dirconfig (same asdiscoverTranscripts); walks.txtfiles withindays; applies theisDreamOutputguard fromtranscript-discovery.ts(skips dream-generated files); returns{path, date, mtime, length, summary}[]sorted newest-first. Summary mode (default true) = first non-empty line + ~250 trailing chars; full mode caps at 100KB/file. Missing/non-existent corpus dirs return[], not error. Trust gate lives in the op handler, not here: the op throwspermission_deniedforctx.remote === true; this is a trusted library function used by both the gated op and the local CLI.src/core/operations-descriptions.ts— Constants module for tool descriptions. Pinned viatest/operations-descriptions.test.ts. HousesGET_RECENT_SALIENCE_DESCRIPTION,FIND_ANOMALIES_DESCRIPTION,GET_RECENT_TRANSCRIPTS_DESCRIPTIONplusLIST_PAGES_DESCRIPTION,QUERY_DESCRIPTION,SEARCH_DESCRIPTION. Stable surface for the Tier-2 LLM routing eval — extracting them keeps the test from binding to whatever was inoperations.tsat test-run time.src/core/cycle/transcript-discovery.ts— Pure filesystem walk for synthesize.discoverTranscripts(opts)filters.txtfiles by date range, min_chars, and word-boundary regexexcludePatterns(medicalmatches "medical advice" but NOT "comedical"; power users may pass full regex).readSingleTranscript(path)is thegbrain dream --input <file>ad-hoc path. Self-consumption guard:DREAM_OUTPUT_MARKER_RE(anchored at frontmatter open---\n, optional BOM + CRLF tolerance, scans first 2000 chars fordream_generated: truewith case-insensitive value and word boundary ontrue) drivesisDreamOutput(content, bypass=false). Both functions skip matching files and emit a[dream] skipped <basename>: dream_generated markerstderr log (no silent skips).bypassGuard?: booleanonDiscoverOptsandreadSingleTranscript's opts disables the guard for the explicit--unsafe-bypass-dream-guardescape hatch only — never auto-applied for--input.src/commands/dream.ts—gbrain dreamCLI; thin alias overrunCycle. Flags:--dry-run,--json,--phase <name>,--pull,--dir <path>,--input <file>(ad-hoc transcript, implies--phase synthesize),--date YYYY-MM-DD,--from <d> --to <d>(backfill range),--unsafe-bypass-dream-guard(plumbed throughrunCycle.synthBypassDreamGuard→SynthesizePhaseOpts.bypassDreamGuard→discoverTranscripts({bypassGuard})/readSingleTranscript({bypassGuard}); loud stderr warning at synthesize-phase entry; never auto-applied for--input). Conflict detection:--input+--dateexits 2. ISO date validation.--dry-runruns the Haiku significance verdict but skips Sonnet synthesis (NOT zero LLM calls). Exit 1 on status=failed.resolveBrainDirreturnsstring | null(order:--dir→ resolved--source'slocal_path→ globalsync.repo_path→ null); a checkout-less postgres/Supabase brain runs DB-only phases (incl.resolve_symbol_edges) and skips the 6 filesystem phases withdetails.reason: 'no_brain_dir';runDreamowns the only hard error (no checkout AND no engine). When--sourceresolves but has no on-disk checkout, returns null (DB-only) rather than borrowing another source's globalsync.repo_path(would mix scopes). Pinned bytest/dream-postgres.serial.test.ts.--drain [--window <seconds>]for--phase extract_atoms:runDrain()bypasses the pack-gate and runs the single-hold bounded drain fromsrc/core/cycle/extract-atoms-drain.tsunder the samecycleLockIdFor(sourceId)the routine cycle uses (concurrent autopilot tick defers withcycle_already_running), reporting{extracted, skipped, remaining}. ExitsEXIT_DRAIN_INCOMPLETE=3whileremaining > 0; a null backlog count (count query FAILED) is also exit 3, never a drained success;LockUnavailableError→cycle_already_runningskip (also exit 3). Theextract_atoms_backlogdoctor check (computeExtractAtomsBacklogCheck) surfaces the silent pack-gated backlog with the exact--draincommand; pack-gated cycle skips carry a greppablepack_gated:truemarker.src/commands/friction.ts+src/core/friction.ts—gbrain friction {log,render,list,summary}reporter. Append-only JSONL under$GBRAIN_HOME/friction/<run-id>.jsonl. Schema is a flat extension ofStructuredAgentError. Render groups by severity → phase, defaults to--redactfor md output (strips$HOME/$CWDto placeholders so reports paste safely in PRs). Run-id resolves from--run-id>$GBRAIN_FRICTION_RUN_ID>standalone.jsonl. Skills the claw-test exercises gain a_friction-protocol.mdcallout so agents know when to log friction.src/commands/claw-test.ts+src/core/claw-test/—gbrain claw-test [--scenario <name>] [--live --agent openclaw]. End-to-end "fresh user" friction harness. Two modes: scripted (CI gate, agent-free) and live (real openclaw subprocess, $1–2 in tokens). SetsGBRAIN_HOME=<tempdir>for hermeticity and captures gbrain's--progress-jsonevents from each child's stderr to verify expected phases ran (import.files,extract.links_fs,doctor.db_checks). Scripted phases: setup → install_brain (gbrain init --pglite) → import (--no-embed) → query → extract → verify (gbrain doctor --json, assertsstatus: 'ok') → render. Live mode handsBRIEF.mdfromtest/fixtures/claw-test-scenarios/<name>/to the agent runner. Ships with the OpenClaw runner only (src/core/claw-test/runners/openclaw.ts, invokesopenclaw agent --local --agent <name> --message <brief>); hermes runner deferred. Transcript capture (transcript-capture.ts) usesfs.createWriteStreamwith'drain'-event backpressure (256KB-burst child-stall fix). Upgrade scenario seeded viaseed-pglite.tsSQL replay.skills/_friction-protocol.md— shared cross-cutting convention skill (like_brain-filing-rules.md). Tells agents when to callgbrain friction logand how to choose a severity. Routes to friction CLI from any skill the claw-test exercises.scripts/check-progress-to-stdout.sh— CI guard against regressing to\r-on-stdout progress. Wired intobun run testviascripts/check-progress-to-stdout.sh && bun testin package.json.docs/progress-events.md— Canonical JSON event schema reference. Additive only.src/core/markdown.ts— Frontmatter parsing + body splitter.coerceFrontmatterString(v)coerces a non-stringtitle/slug/typeto a deterministic string at parse time so a YAML-typed value never reaches.toLowerCase()and throws (the #1939 wedge:title: 2024-06-01parsed as aDate,title: 1458as a number, and the throw blocked the sync bookmark from advancing); aDatebecomes its UTC ISO date (2024-06-01, machine-independent and matching the on-disk token, unlikeString(date)),null/undefinedbecome'', everything else usesString().splitBodyrequires an explicit timeline sentinel (<!-- timeline -->,--- timeline ---, or---immediately before## Timeline/## History). Plain---in body text is a markdown horizontal rule, not a separator.inferTypeauto-types/wiki/analysis/→ analysis,/wiki/guides/→ guide,/wiki/hardware/→ hardware,/wiki/architecture/→ architecture,/writing/→ writing (plus existing people/companies/deals/etc heuristics).scripts/check-jsonb-pattern.sh— CI grep guard. Fails the build if anyone reintroduces (a) the${JSON.stringify(x)}::jsonbinterpolation pattern (postgres.js v3 double-encodes it), or (b)max_stalled INTEGER NOT NULL DEFAULT 1in any schema source file (must be DEFAULT 5 to preserve SIGKILL-rescue). Wired intobun test.scripts/check-source-id-projection.sh— CI grep guard for the multi-source bug class. Grepssrc/core/postgres-engine.ts+src/core/pglite-engine.tsforSELECT.*FROM pagesprojections matching therowToPagefeeder shape (id + slug + type + title) and fails ifsource_idis missing.Page.source_idis required at the type level; a projection dropping the column producesPagerows withsource_id: undefinedwhile TypeScript's: stringlies about it. Wired intobun run verify+bun run check:all.docker-compose.ci.yml+scripts/ci-local.sh— Local CI gate.bun run ci:localspins uppgvector/pgvector:pg16+oven/bun:1with named volumes (gbrain-ci-pg-data,gbrain-ci-node-modules,gbrain-ci-bun-cache), runs gitleaks on host, smoke-testsscripts/run-e2e.shargv handling, runs unit tests withDATABASE_URLunset, then runs all 29 E2E files sequentially.--diffswaps in the diff-aware selector;--no-pullskips upstream pulls;--cleannukes named volumes. Postgres host port defaults to 5434; override withGBRAIN_CI_PG_PORT=NNNN. Stronger gate than PR CI's 2-file Tier 1 set.scripts/select-e2e.ts+scripts/e2e-test-map.ts— Diff-aware E2E test selector. Reads three git sources (committedorigin/master...HEAD, working-treeHEAD, andgit ls-files --others --exclude-standardfor untracked NOT-gitignored files), classifies as EMPTY / DOC_ONLY / SRC. Fail-closed: EMPTY → all 29 files; DOC_ONLY (every path matches the README/CLAUDE/AGENTS/CHANGELOG/TODOS allowlist) → empty stdout; SRC → escape-hatch paths (schema, package.json, skills/) trigger all, else the hand-tunedE2E_TEST_MAPglob narrows, and an unmapped src/ change still emits ALL files (never silently nothing). Pure-function exportsselectTests,classify,matchGlob.bun run ci:select-e2eprints the current selection on stdout.test/select-e2e.test.tscovers all 4 branches plus 3 regression guards (skills/, untracked files, unmapped src/) — 24 cases.scripts/run-e2e.sh— Sequential E2E runner. Accepts an optional argv-driven file list (used byci:local:diff) and a--dry-run-listflag that prints the resolved file list and exits (used byci-local.sh's startup smoke-test). Falls back totest/e2e/*.test.tswhen invoked with no args.scripts/llms-config.ts+scripts/build-llms.ts— Generator forllms.txt(llmstxt.org-spec web index) +llms-full.txt(inlined single-fetch bundle). Curated config drives both. Runbun run build:llmsafter adding a new doc.LLMS_REPO_BASEenv lets forks regenerate with their own URL base.FULL_SIZE_BUDGET(600KB) caps the inline bundle; generator WARNs if exceeded. Committed output has no runtime consumer; committed for GitHub browsing and fork-safe fetching.AGENTS.md— Local-clone entry point for non-Claude agents (Codex, Cursor, OpenClaw, Aider). MirrorsCLAUDE.mdintent via relative links. Claude Code keeps usingCLAUDE.md.docs/UPGRADING_DOWNSTREAM_AGENTS.md— Patches for downstream agent skill forks to apply when upgrading. Each release appends a new section; includes diffs for brain-ops, meeting-ingestion, signal-detector, enrich.src/core/schema-embedded.ts— AUTO-GENERATED from schema.sql (runbun run build:schema)src/schema.sql— Full Postgres + pgvector DDL (source of truth, generates schema-embedded.ts)src/commands/integrations.ts— Standalone integration recipe management (no DB needed). ExportsgetRecipeDirs()(trust-tagged recipe sources), SSRF helpers (isInternalUrl,parseOctet,hostnameToOctets,isPrivateIpv4). Only package-bundled recipes areembedded=true;$GBRAIN_RECIPES_DIRand cwd./recipes/are untrusted and cannot runcommand/http/string health checks.src/core/search/expansion.ts— Multi-query expansion via Haiku. ExportssanitizeQueryForPrompt+sanitizeExpansionOutput(prompt-injection defense-in-depth). Sanitized query is only used for the LLM channel; the original query still drives search.recipes/— Integration recipe files (YAML frontmatter + markdown setup instructions)docs/guides/— Individual SKILLPACK guides (broken out from monolith)docs/integrations/— "Getting Data In" guides and integration docsdocs/architecture/infra-layer.md— Shared infrastructure documentationdocs/ethos/THIN_HARNESS_FAT_SKILLS.md— Architecture philosophy essaydocs/ethos/MARKDOWN_SKILLS_AS_RECIPES.md— "Homebrew for Personal AI" essaydocs/guides/repo-architecture.md— Two-repo pattern (agent vs brain)docs/guides/sub-agent-routing.md— Model routing table for sub-agentsdocs/guides/skill-development.md— 5-step skill development cycle + MECEdocs/guides/idea-capture.md— Originality distribution, depth test, cross-linkingdocs/guides/quiet-hours.md— Notification hold + timezone-aware deliverydocs/guides/diligence-ingestion.md— Data room to brain pages pipelinedocs/designs/HOMEBREW_FOR_PERSONAL_AI.md— 10-star vision for integration systemdocs/mcp/— Per-client setup guides (Claude Desktop, Code, Cowork, Perplexity)- BrainBench (benchmark suite + corpus): lives in the separate gbrain-evals repo. Not installed alongside gbrain.
skills/_brain-filing-rules.md— Cross-cutting brain filing rules (referenced by all brain-writing skills)skills/RESOLVER.md— Skill routing table (based on the agent-fork AGENTS.md pattern)skills/conventions/— Cross-cutting rules (quality, brain-first, model-routing, test-before-bulk, cross-modal)skills/_output-rules.md— Output quality standards (deterministic links, no slop, exact phrasing)skills/signal-detector/SKILL.md— Always-on idea+entity capture on every messageskills/brain-ops/SKILL.md— Brain-first lookup, read-enrich-write loop, source attributionskills/idea-ingest/SKILL.md— Links/articles/tweets with author people page mandatoryskills/media-ingest/SKILL.md— Video/audio/PDF/book with entity extractionskills/meeting-ingestion/SKILL.md— Transcripts with attendee enrichment chainingskills/citation-fixer/SKILL.md— Citation format auditing and fixingskills/repo-architecture/SKILL.md— Filing rules by primary subjectskills/skill-creator/SKILL.md— Create conforming skills with MECE checkskills/daily-task-manager/SKILL.md— Task lifecycle with priority levelsskills/daily-task-prep/SKILL.md— Morning prep with calendar contextskills/cross-modal-review/SKILL.md— Quality gate via second modelskills/cron-scheduler/SKILL.md— Schedule staggering, quiet hours, idempotencyskills/reports/SKILL.md— Timestamped reports with keyword routingskills/testing/SKILL.md— Skill validation frameworkskills/soul-audit/SKILL.md— 6-phase interview for SOUL.md, USER.md, ACCESS_POLICY.md, HEARTBEAT.mdskills/webhook-transforms/SKILL.md— External events to brain signalsskills/data-research/SKILL.md— Structured data research: email-to-tracker pipeline with parameterized YAML recipesskills/minion-orchestrator/SKILL.md— Unified background-work skill (consolidation of the formerminion-orchestrator+gbrain-jobssplit). Two lanes: shell jobs viagbrain jobs submit shell --params '{"cmd":"..."}'(operator/CLI only; MCP throwspermission_deniedfor protected names) and LLM subagents viagbrain agent run(user-facing entrypoint). Shared Preconditions block, parent-child DAGs with depth/cap/timeouts,child_doneinbox for fan-in, PGLite--followinline path for dev. Triggers narrowed to"gbrain jobs submit"+"submit a gbrain job"sostats/prune/retryquestions fall through togbrain --help.templates/— SOUL.md, USER.md, ACCESS_POLICY.md, HEARTBEAT.md templatesskills/migrations/— Version migration files with feature_pitch YAML frontmattersrc/commands/publish.ts— Deterministic brain page publisher (code+skill pair, zero LLM calls)src/commands/backlinks.ts— Back-link checker and fixer (enforces Iron Law)src/commands/lint.ts— Page quality linter (catches LLM artifacts, placeholder dates)src/commands/report.ts— Structured report saver (audit trail for maintenance/enrichment)src/core/destructive-guard.ts— three-layer protection against accidental data loss.assessDestructiveImpact(engine, sourceId)counts pages/chunks/embeddings/files for a source.checkDestructiveConfirmation(impact, opts)is the fail-closed gate (--confirm-destructiverequired when data is present;--yesalone is rejected).softDeleteSource/restoreSource/listArchivedSources/purgeExpiredSourcesdrive the source-level archive lifecycle viasources.archived BOOLEAN,archived_at TIMESTAMPTZ,archive_expires_at TIMESTAMPTZ. Page-level analog:BrainEngine.softDeletePage/restorePage/purgeDeletedPagespluspages.deleted_at TIMESTAMPTZand a partial purge index. The MCPdelete_pageop rewires tosoftDeletePage; opsrestore_page(scope: write) andpurge_deleted_pages(scope: admin,localOnly: true) round out the surface. Search visibility (buildVisibilityClauseinsrc/core/search/sql-ranking.ts) hides soft-deleted pages and archived sources fromsearchKeyword/searchKeywordChunks/searchVectorin both engines. The autopilot cycle'spurgephase callspurgeExpiredSources+engine.purgeDeletedPages(72)so the 72h TTL is real.src/commands/pages.ts—gbrain pages purge-deleted [--older-than HOURS|Nd] [--dry-run] [--json]operator escape hatch. Mirror ofgbrain sources purgefor the page-level lifecycle. Hard-deletes pages whosedeleted_atis older than the cutoff; cascades to content_chunks/page_links/chunk_relations.src/core/op-checkpoint.ts— DB-backed checkpoint primitive for long-running ops. Migration v67 introducesop_checkpoints (op TEXT, fingerprint TEXT, completed_keys JSONB, updated_at TIMESTAMPTZ, PK(op, fingerprint)). Per-op fingerprint helpers (embedFingerprint,extractFingerprint,reindexFingerprint,integrityFingerprint,purgeFingerprint) computesha8(canonical-JSON(relevant-params))so re-running with the same params resumes fromcompleted_keysand re-running with different params (e.g.--limit 100vs--limit 200) starts fresh. Cross-worker safe on Postgres (DB row, no file-lock race); PGLite degrades gracefully. Replaces per-op file-backed JSON checkpoints scattered acrossimport.ts,embed.ts,reindex.ts. The 7-day TTL GC runs in the cycle'spurgephase. All writes (recordCompleted,clearOpCheckpoint) route throughengine.executeRawDirect+withRetry(BULK_RETRY_OPTS)so they survive Supavisor pool exhaustion, andrecordCompletedreturnsboolean(banked vs failed-after-retries) — the 9 non-sync consumers keep its REPLACE-into-completed_keyssemantics. Resumable sync uses the additiveappendCompleted(key, deltaKeys)/appendCompletedOnce(the latter no-retry for the SIGTERM path) which INSERT a delta into theop_checkpoint_pathschild table (migration v115:(op, fingerprint, path)PK, FK toop_checkpointsON DELETE CASCADE) via a single writable-CTEunnest($3::text[])write — O(delta), killing the old O(N²) full-set rewrite.loadOpCheckpointreturns theUNION ALLof legacycompleted_keys+ child-table paths (deduped in JS), so an in-flight upgrade loses nothing.syncFingerprint({sourceId, lastCommit})keys the sync rows. Pinned bytest/op-checkpoint.test.ts(incl. delta-append, union read, cascade clear, durable-write boolean).import-checkpoint.tswas NOT migrated to this primitive — both checkpoint systems coexist without conflict; migrating requires async-propagating 4 sync call sites insrc/commands/import.tsand rewriting 18 tests, deferred.src/core/brain-score-recommendations.ts— pure data layer consumed by bothgbrain doctor --remediation-plan/--remediateandgbrain features.computeRecommendations(checks, opts)returnsRemediation[]with stableid, content-hashidempotency_key,severity,est_seconds,est_usd_cost,depends_on(references stable ids, not check names — so plan order is reproducible).classifyChecks(report)triages every doctor check three-state intoremediable | human_only | blocked(human_onlycovers RLS warnings and other human-judgment gates;blockedcovers dependency chains where a parent check failed).maxReachableScore(checks)computes the ceiling for empty/under-configured brains (no entity pages → graph_coverage caps at 70; no embedding key → embedding_coverage caps at 60). Cost estimates pull fromanthropic-pricing.ts(synthesize/patterns/consolidate) andembedding-pricing.ts(embed jobs). Pinned bytest/brain-score-recommendations.test.ts(~27 cases incl. determinism, content-hash idempotency, DB-backed checkpoint provenance, three-state triage).src/commands/doctor.tsextension —--remediation-plan [--json] [--target-score N]prints what would run (stableid,idempotency_key,severity,est_seconds,est_usd_cost,depends_on);--remediate [--yes] [--target-score N] [--max-usd N]submits each plan step as a Minion job in dependency order, re-checking score between steps.--target-score Ndefaults to 90; refuses to start when target exceedsmaxReachableScore()and lists what's missing.--max-usd Nis the cron-safety guard — submission refuses when the plan'sest_total_usd_costexceeds the cap. JSON envelope adds aCheck.remediationfield (additive, schema_version unchanged). Pinned by tests intest/doctor.test.ts.src/commands/jobs.tsextension — registers 11 Minion handlers:reindex,repair-jsonb,orphans,integrity,purge,synthesize(PROTECTED),patterns(PROTECTED),consolidate(PROTECTED),extract_facts,resolve_symbol_edges,recompute_emotional_weight. Phase wrappers delegate torunCycle({phases:[name]})sosrc/core/cycle.tsstays the single source of truth for phase semantics. The standalonesynchandler passesnoExtract: trueto matchrunPhaseSync's contract (doctor's remediation plan emitting[sync, extract]would otherwise double-extract).src/core/minions/protected-names.tsextension —PROTECTED_JOB_NAMESincludessynthesize,patterns,consolidate. These phases internally submitsubagentchildren withallowProtectedSubmit=trueand can spend Anthropic credits. Only trusted local callers (CLI, autopilot,doctor --remediate) can submit them; MCP requests are rejected bysubmit_job's protected-name guard.src/commands/autopilot.tsextension — targeted-submit loop instead of blanketautopilot-cycledispatch. Each tick: cheapengine.getHealth()(single SQL count) +computeRecommendations(), then route by shape —score >= 95 AND no plan AND <60min since last full→ sleep;score >= 95 AND >=60min→ submitautopilot-cycle(60-min floor exercises phase-coupling invariants on healthy brains);plan <= 3 steps AND est <5min→ submit individual handlers;plan large OR score < 70→ submit fullautopilot-cycle. Thegbrain-cyclelock ensures targeted submissions and the full cycle can't run concurrently.maxWaiting: 1per submit closes the queue-fan-out vector.src/core/abort-check.ts(#1737) — one canonical place for cooperative-abort checks across gbrain's long loops.isAborted(signal?)→ boolean (for loops thatbreakand return partial progress).throwIfAborted(signal?, label?)throws anAbortError(name === 'AbortError') at phase boundaries, preferring the signal'sreason('wall-clock'/'lock-lost'/'shutdown') so the unwind self-describes.anySignal(internal, external?)composes two signals into one that fires when EITHER does (platformAbortSignal.anywith a manual-relay fallback), returning the internal unchanged when there's no external so non-aborting callers pay nothing. The fix for the #1737 cycle-wedge: the embed phase ignored its abort signal and ran to completion, sogbrain_cycle_locksstayed held and later autopilot cycles skipped withcycle_already_running; threading these checks throughrunPhaseEmbed → runEmbedCore → embedAll(Stale)/embedPagelets the phase bail and release the lock immediately. Pinned bytest/abort-check.test.ts.src/core/cycle.tsextension —purgephase (the cycle's 9th phase, for soft-delete TTLs) also GCs staleop_checkpointsrows older than 7 days. Non-fatal on pre-v67 brains (DROP-target-table check before DELETE). #1737: the cycle threads its abort signal into the embed phase (runPhaseEmbed(engine, dryRun, signal)) so a timed-out cycle's long embed phase honors cancellation and releasesgbrain_cycle_locksright away instead of after a full backlog run.src/commands/embed.tsextension — wires--backgroundas the reference integration for themaybeBackground()helper.gbrain embed --stale --backgroundsubmits as a Minion job, printsjob_id=Nto stdout, exits 0. Composable:JOB=$(gbrain embed --stale --background | grep -oE 'job_id=[0-9]+' | cut -d= -f2); gbrain jobs follow $JOB. The other six commands (extract,lint,backlinks,reindex,integrity,pages) adopt the same pattern in a follow-up wave. #1737:runEmbedCoreaccepts an optionalsignalthreaded down both the--staleand--allpaths (embedAllStale/embedAll/embedPage); each composes it with the internal wall-clock budget viaanySignaland checksisAborted/effectiveSignal.abortedin every per-slug loop, page-claim pool, andembedBatchcall, so a worker abort (wall-clock timeout / lock loss / SIGTERM) stops embedding within a batch. Pinned bytest/embed.serial.test.ts.src/core/cli-options.tsextension —maybeBackground(opName, fingerprintArgs, runDirect)helper. Same semantics in TTY and cron (no--no-tty-detectflag, no surprise behavior change between contexts): when--backgroundis passed, submits the op as a Minion job viaop_checkpointsfor resumability and returns thejob_id.--background --followexecsgbrain jobs follow <id>so the user sees the same stderr stream they'd get from a direct call. PGLite degrades to inline execution with a clear stderr note ("PGLite worker pool not yet supported; running inline"). Returns a tagged union the caller dispatches on.openclaw.plugin.json— ClawHub bundle plugin manifestsrc/commands/capture.ts+src/commands/serve-http.ts+src/core/{operations,import-file,types,utils,facts/absorb-log,brainstorm/{orchestrator,error-classify},scope,postgres-engine,pglite-engine}.tsextensions — ingestion-cathedral productionization after a smoke test against Supabase+PgBouncer. Capture frontmatter merge viamergeCaptureFrontmatter(uses gray-matter directly, NOT the lossyparseMarkdown);/ingestnull-guard + outer try/catch envelope with!res.headersSentguard; dedup via separate normalize-for-hash (normalizeForHashstrips BOM/CRLF/whitespace/NFKC) + body-after-frontmatter-strip on the DB hash (excludescaptured_at+ingested_atso capture-cli timestamp variations don't invalidate the chunk cache); friendlypages_source_id_fkrewrite viamaybeRewriteSourceFkErroron BOTH local + thin-clientcallRemoteToolcatch blocks;facts:absorb'No database connection' suppression via typedinstanceof GBrainError && e.problemcheck + first-occurrence stack-trace info log (module-scoped_hasLoggedDisconnectedFactsAbsorbflag, test seam_resetFactsAbsorbDisconnectedFlagForTests); CLI help discoverability (captureadded toCLI_ONLY_SELF_HELP+ pre-engine-bind--helpshort-circuit inhandleCliOnly+ aBRAINsection inprintHelp); binary-file guard viadetectBinaryNullByte(buf)first-8KB NUL scan on--file(Buffer-read, no encoding) and--stdin(readStdinBufferaccumulator); provenance write-through — put_page accepts 3 optional params (source_kind, source_uri, ingested_via;ingested_atserver-stamped) + trust gate (whenctx.remote !== falseIGNORE client params, server stampsmcp:put_page, fail-closed) + COALESCE-preserve UPDATE semantics (omitting params on a later put_page preserves prior values; first-write-wins);/admin/api/register-clientscopes normalization vianormalizeScopesInput(raw: unknown)insrc/core/scope.ts(accepts string/string[]/missing; rejects['read write']space-in-element shape, non-string elements, empty array, unknown scopes; deduped + sorted); brainstorm timeout surfacing via an orchestrator-level try/catch atrunBrainstormentry (single-point wrap covers every internal SQL site, classifies SQLSTATE 57014 via postgres.js.code/.sqlState/ message fallback intoStructuredAgentErrorcodebrainstorm_timeoutwith a hint covering all 3 PG cancel sub-causes); read-path surfaces all 4 provenance columns viagetPageprojection +rowToPage3-state optional read +Pageinterface; canonical source resolver routes capture throughresolveSourceWithTier(engine, parsed.source, cwd); thin-client--sourcerejection (server-side OAuth client registration owns source scope); thesource_kindtaxonomy is closed (capture-cli | put_page | mcp:put_page | webhook | file-watcher | inbox-folder | cron-scheduler),--sourcemaps to source_id only. Tests:test/capture-build-content.test.ts,test/capture-runcapture.test.ts,test/put-page-provenance.test.ts,test/scope-normalize.test.ts,test/cli-help-discoverability.test.ts,test/brainstorm-timeout.test.ts; extendedtest/facts-absorb-log.test.ts,test/import-file.test.ts,test/e2e/engine-parity.test.ts,test/e2e/serve-http-ingest-webhook.test.ts. Report atdocs/v0.38-smoke-test-report.md. Follow-ups in TODOS.md: SQL-shape rewrite oflistPrefixSampledPagesfor PgBouncer, magic-byte allowlist for binary detection,--source-kindoverride flag, ingest_capture handler migration, provenance-history table, facts:absorb root-cause trace.
BrainBench — the public benchmark for personal-knowledge agent stacks — lives in github.com/garrytan/gbrain-evals. It depends on gbrain as a consumer; gbrain never pulls in the ~5MB eval corpus or the pdf-parse dev dep at install time.
gbrain's public API surface (the exports map in package.json) is what
gbrain-evals consumes: gbrain/engine, gbrain/types, gbrain/operations,
gbrain/pglite-engine, gbrain/link-extraction, gbrain/import-file,
gbrain/transcription, gbrain/embedding, gbrain/config, gbrain/markdown,
gbrain/backoff, gbrain/search/hybrid, gbrain/search/expansion,
gbrain/extract. Removing any of these is a breaking change for the
gbrain-evals consumer.
The wave that taught gbrain to know how the user tends to be wrong + use
that knowledge at every advice surface. Six-migration schema (v67-v72),
three new cycle phases, eight expansions, one admin tab. Plan persisted
at ~/.claude/plans/system-instruction-you-are-working-rippling-knuth.md.
Convention skill at skills/conventions/calibration.md has the agent-
facing rules.
Hotfix (migration v80): takes_resolution_consistency CHECK widened to accept
quality='unresolvable' AND outcome=NULL as the 4th valid resolution state. The
column-level CHECK on resolved_quality (takes_resolved_quality_values) enumerates
all 4 states. Take.resolved_quality, TakeResolution.quality, and
takes-fence.ts:TakeQuality are 4-state. TakesScorecard gains unresolvable_count
unresolvable_rate;resolvedstays 3-state (correct+incorrect+partial) so historical comparisons hold.finalizeScorecard:unresolvable_rate = unresolvable_count / (resolved + unresolvable_count), NULL when both 0. Spec doc atdocs/architecture/calibration-quality-gate-spec.md(falsifiability + per-category calibration ship on top in a follow-up). Pinned by R1-R5 intest/takes-resolution.test.tsandtest/migrate.test.ts's v80 structural + PGLite round-trip suite (CHECK admits unresolvable+NULL, still rejects partial+true and unresolvable+true|false, pre-v80 NULL/NULL rows survive).
src/core/cycle/base-phase.ts— abstractBaseCyclePhaseclass. EnforcessourceScopeOpts(ctx)threading at the type level; closes the source-isolation leak class structurally for every new phase. Inherits source-scope, budget meter, error envelope, progress reporter. propose_takes / grade_takes / calibration_profile all extend it.src/core/cycle/propose-takes.ts— LLM scans markdown prose, proposes gradeable claims to thetake_proposalsqueue. Idempotency cache on(source_id, page_slug, content_hash, prompt_version)composite unique index. Fence-dedup: existing canonical takes passed to the extractor as context. Ships a stub prompt; tuned prompt arrives via the synthetic corpus build.src/core/cycle/grade-takes.ts— walks unresolved takes older than 6 months, retrieves evidence, asks judge model, caches verdict. Auto-resolve DISABLED by default. Conservative thresholds: >=0.95 single OR >=0.85 ensemble 3/3 unanimous.aggregateEnsemblereuses the cross-modal substrate; fires on the borderline 0.6-0.95 band. Writes totake_grade_cache.src/core/cycle/calibration-profile.ts— aggregates resolved takes into 2-4 narrative pattern statements + active bias tags. Voice-gated viagateVoice(). Cold-brain skip when <5 resolved. Writes tocalibration_profileswith audit columns (voice_gate_passed,voice_gate_attempts,grade_completion).src/core/calibration/voice-gate.ts— singlegateVoice()function, mode parameter (pattern_statement|nudge|forecast_blurb|dashboard_caption|morning_pulse). 2 regens then template fallback fromsrc/core/calibration/templates.ts. Haiku judge with mode-specific rubrics; all rubrics structurally forbid clinical/preachy voice.src/core/calibration/cross-brain.ts— 4-rule contract for cross-brain calibration reads. Local-first → mount-fallback (only withcanReadMountsForCtx(ctx)true) → cross-brain attribution viasource_brain_id+from_mount→ subagent prohibition closes the OAuth-token-to-cross-brain-leak surface. All 4 rules pinned intest/cross-brain-calibration.test.ts.src/core/calibration/nudge.ts— real-time pattern surfacing.evaluateAndFireNudge(opts): threshold check (conviction > 0.7, holder match, slug-derived domain hint matches active bias tag) → cooldown probe (14d via take_nudge_log) → fire + log. STDERR-only output; multi-channel deferred.src/core/calibration/take-forecast.ts— Brier-trend at write time. Pure math over existingTakesScorecard; no LLM. Returnspredicted_brier,bucket_n,overall_brier. Insufficient-data branch atMIN_BUCKET_N = 5.batchForecastmemoizes per (holder, domain) tuple.src/core/calibration/gstack-coupling.ts— outcome-driven learnings coupling.writeIncorrectResolution(opts)shells out to thegstack-learnings-logbinary. Config gatecycle.grade_takes.write_gstack_learnings(default false for external users). Namespace prefixgbrain:calibration:v0.36.1.0:so--undo-wavecan scrub.src/core/calibration/svg-renderer.ts— server-rendered SVG for the admin SPA Calibration tab. Pure functions: data → SVG string. Inlines design tokens; XSS-safe viaescapeXml(). Four renderers:renderBrierTrend,renderDomainBars,renderAbandonedThreadsCard,renderPatternStatementsCard. SPA renders via<TrustedSVG>wrapper behindrequireAdmin.src/core/calibration/undo-wave.ts—undoWavereverses the wave's mutations: unsetstakes.resolved_*for wave-applied resolutions (cross-checks resolved_by so manual writes persist), deletes calibration_profiles, purges nudge logs, marks grade-cache rows applied=false.--dry-runshows counts without writing. Idempotent on wave_version match.src/core/calibration/think-ab.ts— A/B harness.runAbTrialcalls thinkRunner twice (baseline + with-calibration), records preference tothink_ab_results.buildAbReportaggregates over a 30-day window; flagscalibration_net_negativewhen n>=20 + win rate < 45% on decisive trials.src/core/calibration/recall-footer.ts— formatter for the morning-pulse calibration block. Cold-brain branch when <5 resolved. Opt-in via the wiring layer.src/core/eval-contradictions/calibration-join.ts— cross-reference.tagFindingWithCalibration(finding, profile)returns bias-tag context for contradictions matching active patterns. Returns null when profile missing (output byte-identical to the pre-calibration baseline).src/core/think/prompt.tsextension — anti-bias rewrite.withCalibrationoption onbuildThinkSystemPromptadds anti-bias rules.buildCalibrationBlock()emits the<calibration>XML.buildThinkUserMessagehas TWO shapes: default (question first), and with-calibration (retrieval → calibration → question) when opt-in. Wired intorunThinkviaopts.withCalibration+opts.calibrationHolder.src/commands/calibration.ts— CLI:gbrain calibration(read + print),--regenerate,--undo-wave <ver>,ab-report. MCP opget_calibration_profile(scope: read) backs the same data path. Source-scoped viasourceScopeOpts(ctx).src/commands/serve-http.tsextension — three admin routes:/admin/api/calibration/profile,/admin/api/calibration/charts/:type(image/svg+xml; type in {brier-trend, domain-bars, pattern-statements, abandoned-threads}),/admin/api/calibration/pattern/:id(drill-down).src/commands/takes.tsextension —gbrain takes revisit <slug>opens $EDITOR on the source page with a<!-- gbrain:revisit -->cursor marker.src/commands/doctor.tsextension — 4 checks:abandoned_threads,calibration_freshness,grade_confidence_drift(mitigation surface; math ships later),voice_gate_health.admin/src/pages/Calibration.tsx— Calibration tab. Single-column layout.<TrustedSVG>wrapper handlesdangerouslySetInnerHTMLfor the server-rendered SVG.admin/src/index.cssextension —--text-muted: #777(WCAG AA contrast bump to ~5.5 on the #0a0a0f bg).test/fixtures/calibration/extract-takes-corpus/— synthetic prompt-tuning corpus. Ships 5 representative pages; full 50-page + 10-page holdout generated bygbrain calibration build-corpus. All anonymized per CLAUDE.md placeholder list.scripts/check-synthetic-corpus-privacy.sh— CI guard inbun run verify. Greps for explicit dollar amounts + verifies non-essay fixtures reference at least one placeholder name.test/regressions/v0.36.1.0-iron-rule.test.ts— R1-R5 regression inventory; pins all 5 IRON-RULE regressions in one place for future bisects.DESIGN.md— repo-root design system. Formalizes the de facto admin tokens. Calibration target for future/plan-design-reviewand/design-review.
The schema-pack mutation surface shipped in v0.40.7.0 as the production rebuild of
closed community PR #1321 (@garrytan-agents). Six new foundation modules + a
mutate skeleton + stats/sync data plane + 14 CLI verbs + 9 MCP ops + a first-class
agent skill. See ~/.claude/plans/system-instruction-you-are-working-recursive-thacker.md
for the full plan + 21 captured design decisions.
Key files (v0.40.7.0 additions):
src/core/schema-pack/pack-lock.ts— AtomicO_CREAT|O_EXCLper-pack lock. DELIBERATELY NOT theexistsSync + writeFileSyncTOCTOU shape fromsrc/core/page-lock.ts. Default 60s TTL, refresh every 10s whilewithPackLock(fn)runs,--forcesemantics = "steal stale lock" NOT "skip locking." Lock path per-pack so two packs never block each other.src/core/schema-pack/mutate-audit.ts— ISO-week JSONL at~/.gbrain/audit/schema-mutations-YYYY-Www.jsonl. Privacy-redacted: type names → sha8, prefixes → first slug segment only, matchescandidate-audit.tsprivacy posture. Logs BOTH success AND failure events so theschema_pack_writabilitydoctor check has signal.summarizeMutations()is the cross-surface parity primitive.src/core/schema-pack/registry.tsextensions —invalidatePackCache(name?)walks the extends-chain reverse-graph (editing a parent pack must not leave children stale).tryCachedPack(name)TTL-gated fast path: insideSTAT_TTL_MS(default 1000ms, envGBRAIN_PACK_STAT_TTL_MS) returns cached without statting; outside the window it stats every file in the chain and cascade-invalidates on mtime change (cross-process detection).src/core/schema-pack/best-effort.ts—loadActivePackBestEffort(ctx)returnsResolvedPack | null. Single source of truth for the T1.5 wiring sites.nullmeans EMPTY FILTER (NOT hardcoded defaults — closes the silent-violation bug class).src/core/schema-pack/lint-rules.ts— 12 pure rule functions.withMutation's pre-write validation gate composes the 10 file-plane rules; the 2 DB-aware rules (extractable_empty_corpus,mutation_count_anomaly) need an engine. Single source of truth consumed by CLI lint + MCPschema_lint+ the pre-write validation gate. New file-plane rulelink_regex_catastrophic_backtrack— advisory ReDoS pre-screen flagging the classic nested-quantifier shapes ((a+)+,(a*)*,(a+)*,(\w+)+) in a link_type'sinference.regexviaNESTED_QUANTIFIER_RE. WARNING not error: a hard reject would disable the whole pack on upgrade (pages fall back to legacy typing). The runtime input-length cap inredos-guard.tsis the actual safety net; this rule tells the pack author to fix the pattern.src/core/schema-pack/redos-guard.ts+src/core/schema-pack/link-inference.ts— ReDoS hardening for pack inference regexes.redos-guard.tsaddsMAX_REGEX_INPUT_CHARS(default 64_000, envGBRAIN_MAX_REGEX_INPUT_CHARS) — a hard input-length cap, the real runtime safety net (catastrophic backtracking needs a long input; a link-extractioncontextis normally a sentence or short paragraph). Over the cap,runRegexBoundedthrows the taggedRegexInputTooLargeErrorand the regex is skipped (degrade-to-mentions) without entering thenode:vm.link-inference.ts:inferLinkTypeFromPackno-budget branch (test contexts) now routes throughrunRegexBoundedso the input-length cap + per-regex vm timeout (PER_REGEX_TIMEOUT_MS = 50) apply on every path (previously this branch rannew RegExp(pattern).test(context)unbounded — the one ReDoS hole with no timeout). Defensive hardening + diagnostics; the deterministic ~3100-file sync-wedge root cause remains open. Pinned bytest/redos-hardening.test.ts+test/schema-pack-lint-rules.test.ts.src/core/schema-pack/query-cache-invalidator.ts—invalidateQueryCache(engine, sourceId?)DELETEs query_cache rows so cached search results bound to old page types don't survive a schema mutation.src/core/schema-pack/mutate.ts— 8-stepwithMutationskeleton (bundled-guard → lock → read → mutator → validate → atomic write → audit → invalidate). 11 mutation primitives:addTypeToPack,removeTypeFromPack(with reference check),updateTypeOnPack,addAliasToType,removeAliasFromType,addPrefixToType,removePrefixFromType,addLinkTypeToPack,removeLinkTypeFromPack,setExtractableOnType,setExpertRoutingOnType. Atomic write via.tmp + fsync + rename— the pack file on disk is NEVER partial. Inline minimal JSON→YAML emitter so YAML packs stay YAML (does NOT preserve comments — pin pack.json if you care about layout).src/core/schema-pack/stats.ts—runStatsCore(engine, opts)returns per-source + aggregate page counts + coverage % +dead_prefixes(declared prefixes with zero matching pages — agent drilldown signal). Multi-source aware (sourceIds[]federated,sourceIdsingle, or whole-brain). PGLite + Postgres parity viaexecuteRaw. Empty brain → coverage:1.0 (vacuous truth).src/core/schema-pack/sync.ts—runSyncCore(engine, opts)chunked UPDATE in 1000-row batches per declared prefix. Concurrent writers never block on a single row >100ms. Write-side scoping viactx.sourceIddirectly (NOTsourceScopeOpts, which inherits OAuth read federation). Idempotent on--applyre-run.src/commands/schema.tsextension — 14 CLI verbs in the dispatch table:add-type,remove-type,update-type,add-alias,remove-alias,add-prefix,remove-prefix,add-link-type,remove-link-type,set-extractable,set-expert-routing,stats,sync,reload.withConnectedEnginedefensive fix retained. Lifecycle-grouped help text (Inspection / Activation / Authoring / Discovery+repair).src/core/operations.tsextension — 9 MCP ops:get_active_schema_pack,list_schema_packs,schema_stats,schema_lint,schema_graph,schema_explain_type,schema_review_orphans(all read-scope, NOT localOnly), plusschema_apply_mutations(admin scope, NOT localOnly so remote agents can author packs over HTTPS MCP — batched, one MCP tool taking amutations[]array atomically inside ONEwithPackLock, audit log capturesactor: mcp:<clientId8>) andreload_schema_pack(admin, NOT localOnly). Trust posture: per-callschema_packopt STAYS rejected for remote callers viaop-trust-gate.ts.src/commands/whoknows.ts+src/core/operations.ts:find_experts— T1.5 wiring sites. Pack-aware viaexpertTypesFromPack(pack.manifest)frombest-effort.ts. Pack-load failure → EMPTY filter (NOT hardcoded['person', 'company']defaults). Aresearchertype declared--expertnow surfaces inwhoknowsresults.skills/schema-author/SKILL.md— Agent dispatcher for "evolve the schema pack." Triggers: 15+ phrasings incl. "add a page type", "my brain has untyped pages", "propose new types from my corpus", "backfill page types". Explicit Non-goals callout tobrain-taxonomist(files one page) andeiirp(schema-check during iteration) so agents pick the right surface. 7-phase workflow: brain → assess → propose → apply → sync → verify → commit. Lists every gbrain schema CLI verb + every MCP op the skill uses.brain_first: exemptfrontmatter. Required conformance sections: Contract, Anti-Patterns, Output Format.skills/conventions/schema-evolution.md— Canonical convention: "when to add a type vs alias vs prefix." Decision tree: <20 pages → don't pack-codify; 20-100 → alias or narrow prefix on existing type; 100+ → first-class type. Don'ts section + "when to remove a type" + "when to commit the pack" all answered in one place.skills/RESOLVER.md+skills/manifest.json— schema-author wired into the dispatcher with the full functional-area trigger list (compressed routing pattern per the dispatcher convention).
T1.5 wiring is partial in v0.40.7.0. Three follow-ups filed in TODOS.md under
"v0.40.7.0 Schema Cathedral v3 follow-ups (v0.40.7+)" — enrichment-service.ts
union widening ('person' | 'company' → string), facts/eligibility.ts
pack-aware ELIGIBLE_TYPES wiring, and 3 doctor checks (schema_pack_coverage,
schema_pack_writability, schema_pack_mutation_audit).