* docs: Knowledge Runtime design doc (draft) — 4-layer architecture + reduced-scope delta
Captures the Knowledge Runtime design thinking from the CEO review session:
Resolver SDK, Enrichment Orchestrator, Scheduler, Deterministic Output Builder.
The original 7-phase plan was drafted before v0.12.0 (knowledge graph layer)
and v0.11.x (Minions agent runtime) shipped. Cross-referenced against what's
already merged on master, roughly 60% of the 4-layer vision is already in
production under different names:
- Minions = scheduler + plugin contract (L1 + L3)
- Knowledge graph auto-link = deterministic output at L4 + orchestrator at L2
- BrainBench v1 benchmarks already validate the graph layer
The doc is kept as a draft design reference; the actual build-out will scope
down to the real delta (typed Resolver interface, BrainWriter API + validators,
BudgetLedger, CompletenessScorer, quiet-hours + stagger). See the CEO review
notes for the reduced plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolvers): Resolver SDK pass 1 — interface + registry (PR 1/5)
Adds the typed plugin interface that unifies external-lookup calls (X API,
Perplexity, HEAD check, brain-local slug resolution) behind a single shape:
registry.resolve('x_handle_to_tweet', { handle, keywords }, ctx)
→ { value, confidence, source, fetchedAt, raw? }
Zero behavior change — the registry is empty by default. Builtins
(url_reachable, x_handle_to_tweet) land in the next pass. ScheduledResolver
wrapping via Minions lands in PR 5.
New files:
- src/core/resolvers/interface.ts — Resolver<I,O>, ResolverResult<O>,
ResolverContext (engine, storage, config, logger, requestId, remote,
deadline, signal), ResolverError (not_found, already_registered,
unavailable, timeout, rate_limited, auth, schema, aborted, upstream)
- src/core/resolvers/registry.ts — ResolverRegistry (register/get/has/
list/resolve/clear/size) + getDefaultRegistry() for process-wide use
- src/core/resolvers/index.ts — barrel export
Design rules enforced by types:
- Every result carries confidence (0.0-1.0) + source attribution
- LLM-backed resolvers return confidence<1.0 by convention
- ctx.remote propagates the trust boundary (mirrors OperationContext.remote)
- AbortSignal threads through for cooperative cancellation
Smoke: imports + runs, list()/get()/resolve() behave as typed.
Dependency-free beyond types and storage/engine type imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(fail-improve): optional AbortSignal — Resolver SDK pass 2 (PR 1/5)
Extends FailImproveLoop.execute with an optional `opts.signal` that threads
through the deterministic-first / LLM-fallback flow. Needed by the Resolver
SDK so long-running lookups can be cooperatively cancelled when a caller
aborts (deadline hit, Minion job timeout, user ctrl-c).
Additive and backwards-compatible:
- execute() signature widens callbacks to (input, signal?) => ...; existing
two-arg callbacks are structurally compatible and ignore the extra arg.
- opts is optional; callers that omit it get pre-extension behavior.
- Aborts throw a DOM-style AbortError (name='AbortError'), matching what
fetch() throws, so downstream `err.name === 'AbortError'` branches work
unchanged.
- Aborted runs are NOT logged to the failure JSONL — not informative and
would pollute pattern analysis.
Abort check fires in three places:
- Before the deterministic call (pre-flight)
- Between deterministic miss and LLM call (mid-flight)
- Inside llmFallbackFn if the implementation respects signal itself
Smoke tests: 5 scenarios (existing sig, llm fallback, pre-abort, mid-flight
abort, signal threaded to fallback) — all pass. Existing test/fail-improve.test.ts
(13 tests, 27 expects) unchanged and passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolvers): url_reachable + x_handle_to_tweet — SDK pass 3 (PR 1/5)
Two reference resolver implementations that validate the interface against
real-world requirements: a deterministic free-cost check and a rate-limited
paid-backend lookup.
src/core/resolvers/builtin/url-reachable.ts
HEAD-check a URL, follow redirects (max 5), detect dead links. Reused
isInternalUrl() from the wave-3 SSRF hardening; re-validates every redirect
hop against the same filter. Falls back from HEAD to GET on 405/501.
Composes caller's AbortSignal with a per-request timeout via
AbortSignal.any (with manual-propagation fallback). Confidence=1 when the
backend answers; confidence=0 only on transport failure (DNS/connect/timeout).
src/core/resolvers/builtin/x-api/handle-to-tweet.ts
Find a tweet by handle + free-text keyword hint. Used by the upcoming
`gbrain integrity --auto` loop to repair the 1,424 bare-tweet citations
in Garry's brain. Confidence buckets align with the three-bucket contract:
- >=0.8 auto-repair (single strong match, or dominant in small candidate set)
- 0.5-0.8 review queue (ambiguous but promising)
- <0.5 skip (many candidates or weak match)
Scoring: normalized keyword-token overlap against tweet text, with margin
boost for dominant matches. Strict handle regex (X's username rules).
Retries on 429 up to 2x with Retry-After honor. Terminal 401/403 surfaces
as auth ResolverError so the caller stops hammering. Bearer token read
from ctx.config.x_api_bearer_token or X_API_BEARER_TOKEN env — never logged.
Smoke: registry accepts both, SSRF blocks localhost + file://, available()
returns false when token missing, schema validator rejects bad handles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolvers): tests + gbrain resolvers CLI — SDK pass 4 (PR 1/5 complete)
Closes out PR 1. 43 new tests in test/resolvers.test.ts covering registry
contract, both reference builtins, all three confidence buckets, and every
ResolverError subcode.
test/resolvers.test.ts
- ResolverRegistry: register, duplicate-id rejection, get/has, list with
cost+backend filters, resolve, unavailable propagation, clear, default
singleton lifecycle.
- url_reachable: available(), SSRF guard on localhost + RFC1918 + 169.254
metadata + file:// scheme, empty-url schema error, 200/404 status
propagation, HEAD→GET fallback on 405, redirect chain, per-hop SSRF
re-validation, network failure → reachable=false, AbortSignal mid-flight.
- x_handle_to_tweet: token gate via env AND via ctx.config, invalid/long
handle schema errors, zero-candidate + single-strong + single-weak +
many-ambiguous confidence buckets (gates >=0.5 url emission), 401/403
auth error, 500 upstream error, 429 retry-then-rate_limited, X operator
stripping (prompt injection defense).
src/commands/resolvers.ts
- `gbrain resolvers list [--cost | --backend | --json]` pretty table
or JSON.
- `gbrain resolvers describe <id>` schema + availability detail.
- registerBuiltinResolvers() is idempotent; ready to be called from
future entry points (gbrain integrity, MCP server).
src/cli.ts wires `resolvers` into CLI_ONLY + dispatches to runResolvers.
Full suite: 1343 pass / 0 fail / 141 skip (E2E without DATABASE_URL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(output): BrainWriter + Scaffolder + SlugRegistry — PR 2 pass 1/4
Lands the transactional writer library that the rest of the Knowledge
Runtime sits on top of. No callers routed through it yet — publish.ts /
backlinks.ts / put_page migrations are pass 4 and PR 2.5.
src/core/output/scaffold.ts
Deterministic URL / citation / link builders. Callers pass typed inputs
(handle + tweetId, account + messageId, slug + display text) and get
canonical markdown bytes out. LLM-generated URLs never touch disk.
- tweetCitation({handle, tweetId, dateISO?})
- emailCitation({account, messageId, subject, dateISO?})
- sourceCitation(resolverResult, {url?, label?})
- entityLink({slug, displayText, relativePrefix?})
- timelineLine({dateISO, summary, citation?})
ScaffoldError with codes for invalid_handle / invalid_tweet_id /
invalid_slug / invalid_message_id / invalid_date / empty.
src/core/output/slug-registry.ts
Solves the "Marc Benioff vs Marc-Benioff both slug to marc-benioff" bug.
create() probes engine.getPage and either returns the desired slug or
disambiguates (alice-smith → alice-smith-2). isFree() + suggestDisambiguators()
for interactive UX. Errors: collision, disambiguator_exhausted, invalid_slug.
src/core/output/writer.ts
BrainWriter.transaction(fn, ctx) wraps engine.transaction. The `fn`
callback receives a WriteTx with createEntity / appendTimeline /
setCompiledTruth / setFrontmatterField / putRawData / addLink (the last
creates both forward + reverse back-link atomically). On commit, per-page
validators run against all touchedSlugs. Strict mode throws on
error-severity findings, rolling back the outer tx. Lint mode (default for
PR 2 rollout) returns the report but commits regardless. Pages with
`validate: false` frontmatter skip validators entirely (grandfather hook
for PR 2 migration).
Integration smoke against PGLite: createEntity → disambiguator (2nd call
with same desired slug), addLink writes both forward + back-link,
strict-mode validator failure rolls back the transaction bit-identically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(output): 4 pre-commit validators + tests — PR 2 pass 2/4
Lands the validator suite that BrainWriter runs before committing a
transaction. Paragraph-level deterministic checks, markdown-aware, skip
legacy pages via validate:false frontmatter.
src/core/output/validators/citation.ts
Every factual paragraph in compiled_truth carries at least one citation
marker: [Source: ...] or a linked URL. Splits paragraphs on blank lines,
strips fenced code / inline code / HTML comments before checking.
Ignores headings, key-value lines ("**Status:** Active"), table rows,
pure wikilink bullets (## See Also), and short labels without a factual
verb. Deterministic — no LLM, no semantic judgment.
src/core/output/validators/link.ts
Every [text](path) wikilink resolves to a page that exists (unless it's
an external http(s) URL, which this validator doesn't check; that's
url_reachable's job in PR 3). Strips relative prefix and .md extension.
Batches engine.getPage lookups per unique target. mailto/anchor/other
schemes flagged as warning. Links inside fenced code blocks are skipped.
src/core/output/validators/back-link.ts
Iron Law: if page X → page Y, then Y → X. Reads engine.getLinks(ctx.slug),
and for each target checks engine.getLinks(target) for a reverse edge.
Missing reverses flagged as warning (runAutoLink is the authoritative
enforcer on put_page; this is defense-in-depth for pages edited outside
the main write path).
src/core/output/validators/triple-hr.ts
Catches hygiene issues on the compiled_truth / timeline split: bare `---`
in compiled_truth would re-split on round-trip through parseMarkdown;
headings in the timeline section signal authoring mistakes. Both warn
(not error) — legacy pages legitimately use thematic breaks.
src/core/output/validators/index.ts
registerBuiltinValidators(writer) wires all four.
test/writer.test.ts
57 tests: Scaffolder (all 5 helpers + error paths), SlugRegistry (create,
disambiguator, collision throw, invalid-slug, isFree, suggestDisambiguators),
BrainWriter (happy path, disambiguate, addLink + reverse, strict rollback,
lint proceeds with report, off skips validators, validate:false grandfather,
setCompiledTruth, setFrontmatterField merge, registered validators list),
citation validator (all 11 shape cases), link validator (normalizeToSlug
including ../../, external URL skip, mailto warning, code-fence skip),
back-link validator (no outbound, missing reverse → warning, bidirectional
clean), triple-hr validator (clean, bare --- warning, fenced --- skipped,
heading in timeline warning, ## Timeline header allowed).
Full suite: 1400 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(migrations): v0.13.0 grandfather validate:false — PR 2 pass 3/4
Adds the TS migration that makes BrainWriter's strict-mode rollout safe:
every existing page gets `validate: false` in frontmatter so the new
citation / link / back-link / triple-HR validators skip legacy content.
gbrain integrity --auto (PR 3) clears the flag per-page once real citations
are repaired.
src/commands/migrations/v0_13_0_add_validate_false.ts
Four-phase orchestrator following the v0_12_0 pattern:
A. connect — loadConfig + createEngine. Does NOT write config (prior
learning: gbrain init --migrate-only semantics; never
flip Postgres users to PGLite via bare init).
B. snapshot — engine.getAllSlugs() upfront (prior learning:
listpages-pagination-mutation; OFFSET iteration is
self-invalidating when each write bumps updated_at).
C. grandfather — per slug, skip if frontmatter.validate already set,
else append-log pre-mutation snapshot to
~/.gbrain/migrations/v0_13_0-rollback.jsonl and
putPage with validate:false merged in. Batched 100
at a time so interruption losses are bounded.
D. verify — SQL count of pages with validate=false ≥ expectedTouched.
Idempotent: second run is a no-op. Reversible: rollback log is
append-only JSONL; future `gbrain apply-migrations --rollback v0.13.0`
replays it. Safe on empty brains (returns complete with 0 touched).
src/commands/migrations/index.ts
Registers v0_13_0 after v0_12_0 in semver order.
test/migrations-v0_13_0.test.ts
Registry integration (v0.13.0 present, semver-after-v0.12.0, pitch
metadata well-formed), orchestrator handles no-config gracefully,
dryRun skips the connect phase.
test/apply-migrations.test.ts
Updated two assertions that hard-coded the v0.12.0 skippedFuture list
to also include v0.13.0 (now skippedFuture when installed < 0.13.0).
Full suite: 1405 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(integrity): gbrain integrity — bare-tweet repair + dead-link scan (PR 3)
Ships the user-visible milestone for the Knowledge Runtime delta: a
command that finds brain-integrity issues and repairs them through the
BrainWriter + Resolver SDK infrastructure from PRs 1 and 2.
Targets the two quantified pain points from brain/CITATIONS.md:
- 1,424 of 3,115 people pages have bare tweet references without URLs
- An unknown fraction of existing URL citations have rotted
Subcommands:
gbrain integrity check Read-only report, optional --json
gbrain integrity auto Three-bucket repair loop
gbrain integrity review Print review-queue path + count
gbrain integrity reset-progress Clear the progress file
Three-bucket contract (matches x_handle_to_tweet resolver's confidence
scoring):
>=0.8 → auto-repair via BrainWriter transaction. Appends a timeline
entry on the page with a Scaffolder-built tweet citation (URL
from the API response, never from LLM text).
0.5-0.8 → append to ~/.gbrain/integrity-review.md with all candidates
sorted by match score, for batch human review.
<0.5 → log reason to ~/.gbrain/integrity.log.jsonl and skip.
Resumable: every processed slug hits ~/.gbrain/integrity-progress.jsonl
so an interrupted run resumes from the last slug. --fresh clears it.
Bare-tweet detection patterns (regex, deterministic, skip code fences
and already-cited lines):
- "tweeted about"
- "in/on a (recent|viral) tweet"
- "wrote a tweet/post"
- "posted on X"
- "via X" (but not "via X/handle" — already cited)
- possessive "his/her/their tweet"
External-link detection extracts all [text](https?://...) pairs (code
fences skipped) for optional dead-link probing via url_reachable.
Dead links are surfaced, not auto-repaired — no "correct" replacement
exists without human judgment.
Wiring: runIntegrity dispatches subcommands, registers builtin resolvers
into the default registry, connects to the brain engine, and uses
BrainWriter in strict-off mode (integrity is the repair path, not the
write-gate path).
Unit tests: 21 cover bare-tweet regex (all 9 phrase shapes + code-fence
skip + URL-already-present skip + per-line dedup), external-link
extraction (http+https, line numbers, fenced skip), frontmatter handle
extraction (x_handle, twitter, twitter_handle, x; preference order;
leading @ strip; null paths). End-to-end auto flow verified manually
via the resolver SDK tests + BrainWriter tests it composes.
src/cli.ts wires `integrity` into CLI_ONLY + dispatches to runIntegrity.
Full suite: 1426 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(enrichment): BudgetLedger + CompletenessScorer — PR 4
Two layer-2 primitives that slot under the resolver SDK and BrainWriter:
cost-aware spend caps and evidence-weighted per-page completeness scoring.
Schema migration v11 adds two tables:
budget_ledger (scope, resolver_id, local_date) PK — midnight rollover by
date column means a new calendar day upserts a new row; no rollover
thread, no race.
budget_reservations (reservation_id) — TTL-bounded held reservations
(default 60s) so process death between reserve() and commit() doesn't
strand money.
Rollback plan: DROP TABLE. Budget data is regenerable from resolver call
logs; no durable product value lives in the ledger.
src/core/enrichment/budget.ts
BudgetLedger.reserve({resolverId, estimateUsd, capUsd?, ttlSeconds?})
serializes concurrent reserves on {scope, resolver_id, local_date} via
SELECT ... FOR UPDATE. Returns {kind:'held', reservationId, ...} or
{kind:'exhausted', reason, spent, pending, cap} — never over-spends.
commit(id, actualUsd) moves money from reserved_usd to committed_usd and
marks the reservation status='committed'. rollback(id) zeros out the
reservation without touching committed. Commit-after-commit throws
already_finalized; rollback-after-commit is a no-op (callers don't need
to guard). commit-unknown-id throws reservation_not_found.
cleanupExpired() sweeps held reservations past expires_at and rolls them
back; reserve() opportunistically reclaims the target row's expired
reservations before acquiring its own lock.
IANA timezone config via opts.tz (default America/Los_Angeles); midnight
rollover is naturally expressed as a date column + Intl.DateTimeFormat
with en-CA locale (YYYY-MM-DD). DST is handled by the formatter.
src/core/enrichment/completeness.ts
Seven per-type rubrics (person, company, project, deal, concept, source,
media) + default. Each rubric's dimension weights sum to 1.0, checked at
module load. scorePage(page) returns {score, dimensionScores, rubric}
where score is 0.000–1.000.
Person rubric dimensions: has_role_and_company, has_source_urls,
has_timeline_entries, has_citations, has_backlinks, recency_score,
non_redundancy. The last two are the explicit fix for the two pathologies
called out in the codex review of the earlier design: stale pages that
never decay (30-day re-enrich forever) and Wilco-style repeated blocks
that pass Wintermute's length heuristic.
Pure functions. No engine calls — BrainWriter invokes scorePage after a
transaction and caches the result in frontmatter.completeness.
test/enrichment.test.ts — 23 tests:
BudgetLedger: under-cap held, over-cap exhausted, commit moves money,
rollback clears, commit-rollback no-op, commit-commit throws, commit-
unknown throws, invalid input, empty state null, scope isolation,
parallel reserves respect cap (10 parallel, cap 1.0, est 0.3 each →
≤ 3 held; state.reservedUsd ≤ 1.0), cleanupExpired reclaims TTL=0.
CompletenessScorer: all 8 rubrics sum to 1.0, empty person scores <0.3,
fully-enriched person >0.8, dimension scores exposed, role detection,
company/concept/source/media/default routing, recency decay with age,
non_redundancy penalizes repeated lines.
Full suite: 1449 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): quiet-hours + stagger + claim-time gate — PR 5
Closes the scheduler gap per CEO plan: Minions v7 shipped a durable
runtime but nothing about when jobs should NOT run. This wires
quiet-hours enforcement at claim time (the codex correction — dispatch-
time is wrong because a queued job can become claimable after its window
opens) plus deterministic stagger slots to prevent cron-boundary storms.
Schema migration v12 adds two columns to minion_jobs:
quiet_hours JSONB — {start, end, tz, policy} window config
stagger_key TEXT — partitioning key for deterministic offset
Plus a partial index on stagger_key for later slot-assignment queries.
src/core/minions/quiet-hours.ts
evaluateQuietHours(cfg, now?) → 'allow' | 'skip' | 'defer'. Pure,
deterministic, no engine. Handles straight-line and wrap-around windows
(e.g. 22→7 spans midnight). IANA timezone via Intl.DateTimeFormat;
unknown tz fails open (allow) — safer than hard-blocking every job.
'skip' policy drops the event; 'defer' (default) re-queues for later.
src/core/minions/stagger.ts
staggerMinuteOffset(key) → 0–59, FNV-1a hash. Same key → same slot.
Pure; no module-level state. Used by scheduled resolvers that want to
avoid cron-boundary collisions ("10 jobs all fire at minute 0").
src/core/minions/worker.ts
MinionWorker.tick now consults evaluateQuietHours on every claimed job.
Verdict 'defer' → UPDATE status='delayed', delay_until = now() + 15m
(prevents immediate re-claim loops when the claim query re-runs).
Verdict 'skip' → UPDATE status='cancelled', error_text='skipped_quiet_hours'.
Both paths clear lock_token and require lock_token match in the WHERE
clause so a concurrent stall recovery can't race us.
test/minions-quiet-hours.test.ts — 25 tests:
evaluateQuietHours: null/undefined/invalid config paths (allow fail-open),
straight-line in/out + exclusive-end, wrap-around in (before midnight +
after), skip vs defer policy, timezone-offset propagation (winter PST
vs summer PDT), localHour parity with Date.getUTCHours.
staggerMinuteOffset: deterministic same key → same offset, different
keys spread across buckets (10 keys → ≥5 unique buckets), empty/non-
string edge cases.
Schema v12: quiet_hours and stagger_key columns exist on minion_jobs,
idx_minion_jobs_stagger_key index present.
Full suite: 1474 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(output): post-write validator lint hook — PR 2.5
Minimal integration of BrainWriter validators into the main write path,
feature-flag-gated and non-blocking. The CEO plan explicitly scoped PR 2.5
as a pre-soak landing step: the hook plugs in now, observability lands,
but strict-mode rejection is deferred to a follow-on release gated on the
7-day soak + BrainBench regression ≤1pt.
src/core/output/post-write.ts
runPostWriteLint(engine, slug, opts?) invokes the four BrainWriter
validators (citation, link, back-link, triple-hr) against a freshly
written page and returns a PostWriteLintResult. Skips cleanly when:
- config `writer.lint_on_put_page` is not truthy (default OFF; opts.force overrides)
- the page is not found (shouldn't happen in normal put_page flow)
- the page has frontmatter.validate === false (grandfathered)
Findings are logged to:
- ~/.gbrain/validator-lint.jsonl (capped at 20 findings per line)
- engine.logIngest (ingest_log table) for durable agent-inspectable history
Validator-level exceptions are swallowed so a buggy validator never
breaks put_page.
src/core/operations.ts put_page handler
After importFromContent + runAutoLink, imports runPostWriteLint and
invokes it. Result returns writer_lint: {error_count, warning_count} or
{skipped: reason}. Try/catch wraps the whole hook so an import or
runtime error never blocks the main write.
Enable locally:
gbrain config set writer.lint_on_put_page true
Then every put_page emits a writer_lint summary + appends structured
findings to the ingest log for analysis before the strict-mode flip.
test/post-write-lint.test.ts — 11 tests:
Flag reader (default off, true/1/on, other values false, explicit false)
Hook behavior (flag-off skip, page-not-found skip, validate:false
grandfather skip, force=true overrides flag, dirty page yields citation
error, clean page yields zero findings).
Full suite: 1485 pass / 0 fail / 141 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(migrations-v0_13_0): drop flaky no-config assertion
The 'does not succeed when no brain is configured' test assumed loadConfig
would return null when HOME is empty, but it also reads DATABASE_URL from
the environment. When .env.testing sources DATABASE_URL into the shell
(normal E2E lifecycle), the orchestrator connects successfully and runs
to completion — the test's assertion was unreachable.
The dry-run path is still covered by the remaining test in the same
describe block; registry integration and semver ordering are covered by
the sibling describe.
Full suite with DATABASE_URL live: 1574 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(minions): wire quiet_hours + stagger_key into MinionJobInput + queue.add
Codex adversarial review caught that PR 5 (claim-time quiet-hours gate) was
cosmetic: the schema v12 column existed, the worker read it via
`readQuietHoursConfig(job)`, but `MinionJobInput` never accepted it,
`queue.add()` never inserted it, and `rowToMinionJob()` never mapped it out.
Result: every scheduled job saw `quiet_hours: null`, so the gate was a
no-op. Stagger_key had the same broken wiring.
- MinionJob (types.ts): add `quiet_hours` and `stagger_key` fields.
- MinionJobInput: add matching optional fields so callers can submit them.
- rowToMinionJob: parse both columns (JSONB handled the same way as `data`).
- MinionQueue.add: include both columns in the INSERT (idempotent + normal
paths), bound as $19/$20. The `$19::jsonb` cast matches the JSONB column
shape; the wire format is the same native-JS object path that fixed the
JSONB double-encode bug in v0.12.1.
After this, `await queue.add('x', {}, { quiet_hours: {start:22,end:7,
tz:"America/Los_Angeles",policy:"defer"} })` actually stores the window
and the worker's claim-time gate defers the job inside it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(minions): route quiet-hours 'skip' through cancelJob to rollup parents
Codex flagged that handleQuietHoursDefer with verdict='skip' directly set
status='cancelled' via raw UPDATE — bypassing MinionQueue.cancelJob, which
means:
- Parent jobs in 'waiting-children' never get rolled up.
- Descendant jobs don't cascade-cancel.
- Child-done inbox notification is skipped.
Result: a parent waiting on a child that fell inside quiet hours with
policy='skip' stays stuck forever.
Fix: release the lock, then delegate to queue.cancelJob(job.id) which
handles the recursive CTE + parent rollup + inbox posting correctly.
Falls back to a direct UPDATE only if cancelJob errors — even then, the
status transition is status-guarded to avoid stomping terminal states.
Defer path unchanged (no parent rollup needed since the job hasn't reached
a terminal state).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(budget): commit() re-checks cap + rejects negative actuals
Codex caught two cap-bypass bugs in BudgetLedger.commit():
1. reserve({estimateUsd: 0.01, capUsd: 1.0}) + commit(id, 100) silently
charged $100 to a $1-cap bucket. Cap is an advertised invariant that
the code was not enforcing.
2. Negative actuals (commit(id, -5)) were accepted, letting callers
artificially reduce committed_usd below the real spend. Refunds need
a dedicated API, not a side-channel on commit.
Fix:
- Reject non-finite AND negative actualUsd at entrypoint.
- Lock the ledger row FOR UPDATE during commit (same serialization as
reserve).
- Compute effective cap headroom = cap - other_committed - other_reserved
(excluding this reservation from the reserved pool since we're about to
finalize it).
- When actualUsd would exceed available, clamp committed_usd to max
available and throw BudgetError with the overage reported. The
reservation is still marked 'committed' (API call already happened;
don't retry-loop), but the cap is honored.
After this, a $1/day cap actually means $1/day.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(integrity): --dry-run no longer writes progress, poisoning resume
Codex caught that 'gbrain integrity auto --dry-run' appended progress
entries (status='repaired', 'reviewed', 'skipped', 'error') despite doing
no actual writes. The follow-on real run with default --resume would then
skip those slugs — the dry-run silently consumed the work queue.
Fix: gate every appendProgress() call in cmdAuto on !dryRun. Dry-run
still logs to the skip log / review queue (so the user sees what WOULD
happen), but the progress file stays untouched.
Behavior:
--dry-run → buckets counted + summary printed + review-queue
+ log populated, but progress file unchanged.
(default) → progress file tracks every processed slug, so
Ctrl-C + re-run resumes from the right place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump version and changelog (v0.13.0.0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(resolvers): DNS-rebinding defense + X rate-limit header parity
Two non-blocking codex findings on PR #210 rolled into one bisectable
commit because their tests share an import line.
url_reachable: hostname-string SSRF guard is vulnerable to DNS rebinding
(attacker-controlled DNS returns a public IP at validate time and
169.254.169.254 at fetch time). Add checkDnsRebinding() that resolves
the hostname via dns.lookup({all:true}) and rejects any result whose
A/AAAA record lands in a private range (v4 via isPrivateIpv4, v6
loopback/link-local/unique-local/IPv4-mapped). Applied on the initial
URL and on every redirect target. Null on DNS failure so genuine
network problems surface via fetch.
x_handle_to_tweet: rate-limit backoff only honored Retry-After and
ignored X's proprietary x-rate-limit-reset header. computeBackoffMs()
parses both (Retry-After = seconds or HTTP-date; x-rate-limit-reset =
epoch seconds), takes MAX, and clamps to [2s, 60s]. Exported for
testability; callers use it uniformly on every 429.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(writer): advisory lock on desiredSlug prevents cross-process TOCTOU
BrainWriter's createEntity checks engine.getPage(slug) and falls back
to putPage(), which upserts. Two putPage('people/alice') calls from
separate processes (a Claude Code session + a Minions worker, say) can
both read "free" from SlugRegistry and both call putPage, silently
overwriting each other with no disambiguation.
Take a transaction-scoped advisory lock keyed on hashtext(desiredSlug)
before the registry check. Concurrent writers for the same slug now
serialize at the DB level: the second observes the first's commit and
disambiguates to alice-2. PGLite is single-process so this is a
harmless no-op there. Wrapped in try/catch so engines/test doubles
that don't support advisory locks fall through to the existing
within-process check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(validators): empty [Source:] no longer satisfies citation check
Regex /\[Source:[^\]]*\]/ matched decorative markers like [Source:]
and [Source: ] that carry zero provenance. Tighten to require at
least one non-whitespace character before the closing bracket. The
inline URL form ](https://...) already requires a scheme+host so it
stays as-is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(auto-link): advisory lock serializes concurrent reconciliation
runAutoLink wraps getLinks + addLink/removeLink in a transaction, but
row-level locks alone don't prevent the union-of-writes race: two
concurrent put_page calls on the same slug can both read the same
existingKeys BEFORE either mutates a row, then proceed to add links
the other side's rewrite no longer mentions.
Take a transaction-scoped advisory lock on hashtext("auto_link:" ||
slug) at the start of the reconciliation. Concurrent writers on the
same slug now fully serialize; writers on different slugs still run
in parallel. No-op on engines without advisory locks (PGLite).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: expand coverage on abort-signal threading + integrity CLI dispatch
fail-improve: four new AbortSignal cases — pre-start abort, between
deterministic and LLM, signal forwarded into both callbacks, and
LLM-thrown AbortError propagates without logging a failure entry.
integrity: three new CLI dispatch cases — --help, no-subcommand (help),
and unknown subcommand (stderr + exit 1). Non-engine paths so they
exercise routing without spinning up a DB.
Coverage-only; no source changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(doctor): fold integrity sample scan into default health check
Expose scanIntegrity(engine, opts) as a pure library function — same
logic cmdCheck uses — and call it from doctor in non-fast mode with
a 500-page sampling limit. Surfaces bare-tweet phrase count and
external-link count as an 'integrity' check, warn-status when bare
tweets are present with a one-liner pointing at 'gbrain integrity
check' for the full report and 'integrity auto' for repair.
Read-only: no network, no writes, no resolver calls. Pages with
validate:false frontmatter are skipped (grandfathered). --fast mode
skips it entirely so the existing health-snapshot contract holds.
Users no longer need to remember three separate commands (doctor,
lint, integrity check) to audit brain health — doctor surfaces the
integrity signal by default, full scan stays available for deep dives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(put_page): auto-extract timeline entries alongside auto-link
put_page already chunks, embeds, reconciles tags, and extracts
auto-links on every write. Timeline extraction has lived in a
separate command (gbrain extract timeline) that users had to remember
to run. Fold it into the write path: after the page commits, parse
timeline entries from compiled_truth + timeline body and insert via
addTimelineEntriesBatch. ON CONFLICT DO NOTHING keeps it idempotent
across re-writes.
Mirrors auto-link shape: best-effort post-hook, skipped for remote
(MCP) callers, gated by auto_timeline config (default TRUE). Response
includes auto_timeline: { created } alongside auto_links.
Side effect: a one-shot `gbrain put` now produces a complete page —
chunks, embeddings, links, AND timeline — instead of three commands
the user has to chain manually.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(migrate): verify target health after engine migration
After a PGLite↔Postgres migration, the user was left to run 'gbrain
doctor' themselves to confirm the target is good. Not great, because
the failure modes (partial copy, missing embeddings, schema drift)
all surface at next CLI use when the migration itself looks like it
succeeded.
Add verifyTarget() — inline doctor-lite that checks page count
matches the source, embedding coverage is above 90%, and schema
version is at latest. Prints a 3-line status table at the end of
migrate and points at 'gbrain doctor' for the full check. Non-fatal:
warns on discrepancies instead of failing the command so the user
sees the full picture.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(bench): add v0.13 knowledge runtime benchmark deltas
Two new benchmark scripts + one consolidated markdown comparing this
branch against master (c0b6219, v0.12.1):
benchmark-put-page-latency.ts — 200 put_page ops, measures the
per-write cost of Step B's auto-timeline extraction. Branch adds
~0.5ms mean latency and produces 300 timeline entries for free;
master produces zero and requires a separate 'gbrain extract timeline'
pass.
benchmark-knowledge-runtime.ts — three measurements in one script:
time-to-queryable (branch 40/40 vs master 0/40 on post-ingest
timeline queries), integrity repair rate (70/20/10 three-bucket
split via mocked resolver), doctor completeness (surfaces 100% of
real issues after Step A, respects grandfathered pages).
docs/benchmarks/2026-04-19-knowledge-runtime-v0.13.md — consolidated
report. Covers the four moved benchmarks plus side-by-side runs of
graph-quality and search-quality showing they're identical across
master and branch. Proof of no regression on the retrieval hot path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GBrain
Your AI agent is smart but forgetful. GBrain gives it a brain.
Built by the President and CEO of Y Combinator to run his actual AI agents. The production brain powering his OpenClaw and Hermes deployments: 17,888 pages, 4,383 people, 723 companies, 21 cron jobs running autonomously, built in 12 days. The agent ingests meetings, emails, tweets, voice calls, and original ideas while you sleep. It enriches every person and company it encounters. It fixes its own citations and consolidates memory overnight. You wake up and the brain is smarter than when you went to bed.
The brain wires itself. Every page write extracts entity references and creates typed links (attended, works_at, invested_in, founded, advises) with zero LLM calls. Hybrid search. Self-wiring knowledge graph. Structured timeline. Backlink-boosted ranking. Ask "who works at Acme AI?" or "what did Bob invest in this quarter?" and get answers vector search alone can't reach. Benchmarked end-to-end: Recall@5 jumps from 83% to 95%, Precision@5 from 39% to 45%, +30 more correct answers in the agent's top-5 reads on a 240-page Opus-generated rich-prose corpus. Graph-only F1: 86.6% vs grep's 57.8% (+28.8 pts). Full report.
GBrain is those patterns, generalized. 26 skills. Install in 30 minutes. Your agent does the work. As Garry's personal agent gets smarter, so does yours.
~30 minutes to a fully working brain. Database ready in 2 seconds (PGLite, no server). You just answer questions about API keys.
Install
On an agent platform (recommended)
GBrain is designed to be installed and operated by an AI agent. If you don't have one running yet:
- OpenClaw ... Deploy AlphaClaw on Render (one click, 8GB+ RAM)
- Hermes Agent ... Deploy on Railway (one click)
Paste this into your agent:
Retrieve and follow the instructions at:
https://raw.githubusercontent.com/garrytan/gbrain/master/INSTALL_FOR_AGENTS.md
That's it. The agent clones the repo, installs GBrain, sets up the brain, loads 26 skills, and configures recurring jobs. You answer a few questions about API keys. ~30 minutes.
Standalone CLI (no agent)
git clone https://github.com/garrytan/gbrain.git && cd gbrain && bun install && bun link
gbrain init # local brain, ready in 2 seconds
gbrain import ~/notes/ # index your markdown
gbrain query "what themes show up across my notes?"
3 results (hybrid search, 0.12s):
1. concepts/do-things-that-dont-scale (score: 0.94)
PG's argument that unscalable effort teaches you what users want.
[Source: paulgraham.com, 2013-07-01]
2. originals/founder-mode-observation (score: 0.87)
Deep involvement isn't micromanagement if it expands the team's thinking.
3. concepts/build-something-people-want (score: 0.81)
The YC motto. Connected to 12 other brain pages.
MCP server (Claude Code, Cursor, Windsurf)
GBrain exposes 30+ MCP tools via stdio:
{
"mcpServers": {
"gbrain": { "command": "gbrain", "args": ["serve"] }
}
}
Add to ~/.claude/server.json (Claude Code), Settings > MCP Servers (Cursor), or your client's MCP config.
Remote MCP (Claude Desktop, Cowork, Perplexity)
ngrok http 8787 --url your-brain.ngrok.app
bun run src/commands/auth.ts create "claude-desktop"
claude mcp add gbrain -t http https://your-brain.ngrok.app/mcp -H "Authorization: Bearer TOKEN"
Per-client guides: docs/mcp/. ChatGPT requires OAuth 2.1 (not yet implemented).
The 26 Skills
GBrain ships 26 skills organized by skills/RESOLVER.md. The resolver tells your agent which skill to read for any task.
Skill files are code. They're the most powerful way to get knowledge work done. A skill file is a fat markdown document that encodes an entire workflow: when to fire, what to check, how to chain with other skills, what quality bar to enforce. The agent reads the skill and executes it. Skills can also call deterministic TypeScript code bundled in GBrain (search, import, embed, sync) for the parts that shouldn't be left to LLM judgment. Thin harness, fat skills: the intelligence lives in the skills, not the runtime.
Always-on
| Skill | What it does |
|---|---|
| signal-detector | Fires on every message. Spawns a cheap model in parallel to capture original thinking and entity mentions. The brain compounds on autopilot. |
| brain-ops | Brain-first lookup before any external API. The read-enrich-write loop that makes every response smarter. |
Content ingestion
| Skill | What it does |
|---|---|
| ingest | Thin router. Detects input type and delegates to the right ingestion skill. |
| idea-ingest | Links, articles, tweets become brain pages with analysis, author people pages, and cross-linking. |
| media-ingest | Video, audio, PDF, books, screenshots, GitHub repos. Transcripts, entity extraction, backlink propagation. |
| meeting-ingestion | Transcripts become brain pages. Every attendee gets enriched. Every company gets a timeline entry. |
Brain operations
| Skill | What it does |
|---|---|
| enrich | Tiered enrichment (Tier 1/2/3). Creates and updates person/company pages with compiled truth and timelines. |
| query | 3-layer search with synthesis and citations. Says "the brain doesn't have info on X" instead of hallucinating. |
| maintain | Periodic health: stale pages, orphans, dead links, citation audit, back-link enforcement, tag consistency. |
| citation-fixer | Scans pages for missing or malformed citations. Fixes format to match the standard. |
| repo-architecture | Where new brain files go. Decision protocol: primary subject determines directory, not format. |
| publish | Share brain pages as password-protected HTML. Zero LLM calls. |
| data-research | Structured data research with parameterized YAML recipes. Extract investor updates, expenses, company metrics from email. |
Operational
| Skill | What it does |
|---|---|
| daily-task-manager | Task lifecycle with priority levels (P0-P3). Stored as searchable brain pages. |
| daily-task-prep | Morning prep: calendar lookahead with brain context per attendee, open threads, task review. |
| cron-scheduler | Schedule staggering (5-min offsets), quiet hours (timezone-aware with wake-up override), idempotency. |
| reports | Timestamped reports with keyword routing. "What's the latest briefing?" finds it instantly. |
| cross-modal-review | Quality gate via second model. Refusal routing: if one model refuses, silently switch. |
| webhook-transforms | External events (SMS, meetings, social mentions) converted into brain pages with entity extraction. |
| testing | Validates every skill has SKILL.md with frontmatter, manifest coverage, resolver coverage. |
| skill-creator | Create new skills following the conformance standard. MECE check against existing skills. |
| minion-orchestrator | Long-running agent work as background jobs. Submit, fan out children with depth/cap/timeouts, collect results via child_done inbox. |
Identity and setup
| Skill | What it does |
|---|---|
| soul-audit | 6-phase interview generating SOUL.md (agent identity), USER.md (user profile), ACCESS_POLICY.md (4-tier privacy), HEARTBEAT.md (operational cadence). |
| setup | Auto-provision PGLite or Supabase. First import. GStack detection. |
| migrate | Universal migration from Obsidian, Notion, Logseq, markdown, CSV, JSON, Roam. |
| briefing | Daily briefing with meeting context, active deals, and citation tracking. |
Conventions
Cross-cutting rules in skills/conventions/:
- quality.md ... citations, back-links, notability gate, source attribution
- brain-first.md ... 5-step lookup before any external API call
- model-routing.md ... which model for which task
- test-before-bulk.md ... test 3-5 items before any batch operation
- cross-modal.yaml ... review pairs and refusal routing chain
How It Works
Signal arrives (meeting, email, tweet, link)
-> Signal detector captures ideas + entities (parallel, never blocks)
-> Brain-ops: check the brain first (gbrain search, gbrain get)
-> Respond with full context
-> Write: update brain pages with new information + citations
-> Auto-link: typed relationships extracted on every write (zero LLM calls)
-> Sync: gbrain indexes changes for next query
Every cycle adds knowledge. The agent enriches a person page after a meeting. Next time that person comes up, the agent already has context. The difference compounds daily.
The system gets smarter on its own. Entity enrichment auto-escalates: a person mentioned once gets a stub page (Tier 3). After 3 mentions across different sources, they get web + social enrichment (Tier 2). After a meeting or 8+ mentions, full pipeline (Tier 1). The brain learns who matters without being told. Deterministic classifiers improve over time via a fail-improve loop that logs every LLM fallback and generates better regex patterns from the failures. gbrain doctor shows the trajectory: "intent classifier: 87% deterministic, up from 40% in week 1."
"Prep me for my meeting with Jordan in 30 minutes" ... pulls dossier, shared history, recent activity, open threads
"What have I said about the relationship between shame and founder performance?" ... searches YOUR thinking, not the internet
Minions: your sub-agents won't drop work anymore
A durable, Postgres-native job queue built into the brain. Every long-running agent task is now a job that survives gateway restarts, streams progress, gets paused / resumed / steered mid-flight, and shows up in gbrain jobs list. Zero infra beyond your existing brain.
The production numbers that matter
Here's my personal OpenClaw deployment: one Render container. Supabase Postgres holding a 45,000-page brain. 19 cron jobs firing on schedule. Real gateway load from real daily work. The task: pull a month of my social posts from an external API and ingest them end-to-end into the brain as a structured page.
| Minions | sessions_spawn |
|
|---|---|---|
| Wall time | 753ms | >10,000ms (gateway timeout) |
| Token cost | $0.00 | ~$0.03 per run |
| Success rate | 100% | 0% (couldn't even spawn) |
| Memory/job | ~2 MB | ~80 MB |
Under that 19-cron load, sub-agent spawn couldn't clear the 10-second gateway wall. Minions landed it in under a second for zero tokens. Scaling: 19,240 posts across 36 months, single bash loop, ~15 min total, $0.00. Sub-agents: ~9 min best case, ~$1.08 in tokens, ~40% spawn failure. Lab: durability ∞ (SIGKILL mid-flight, 10/10 rescued), throughput ~10× faster, fan-out ~21× with no failure wall, memory ~400× less.
Full benchmarks: production and lab.
The routing rule
Deterministic (same input → same steps → same output) → Minions Judgment (input requires assessment or decision) → Sub-agents
Pull posts, parse JSON, write a brain page, run a sync — deterministic. $0 tokens, survives restart, millisecond runtime. Triage the inbox, assess meeting priority, decide if a cold email deserves a reply — judgment. What sub-agents are actually good at. minion_mode: pain_triggered (the default) automates the routing.
What's fixed
The six daily pains — spawn storms, agents that stop responding, forgotten dispatches, gateway crashes mid-run, runaway grandchildren, debugging soup — all belonged to the "deterministic work through a reasoning model" mistake. Minions fixes them by not making that mistake: max_children cap, timeout_ms + AbortSignal, child_done inbox, full parent_job_id/depth/transcript per job, Postgres durability with stall detection, cascade cancel via recursive CTE. Plus idempotency keys, attachment validation, removeOnComplete, and gbrain jobs smoke that proves the install in half a second.
gbrain jobs smoke # verify install
gbrain jobs submit sync --params '{}' # fire a background job
gbrain jobs stats # health dashboard
gbrain jobs work --concurrency 4 # start a worker (Postgres only)
Read skills/minion-orchestrator/SKILL.md for parent-child DAGs, fan-in collection, steering via inbox.
Minions is not incrementally better than sub-agents for background work. It's categorically different. 753ms vs gateway timeout. $0 vs tokens. 100% vs couldn't-spawn. If your agent does deterministic work on a schedule, it runs on Minions now.
Health check and self-heal
Minions is canonical as of v0.11.1 — every gbrain upgrade runs the migration automatically (schema → smoke → prefs → host rewrites → env-aware autopilot install). If you ever want to verify manually or wire a cron into your morning briefing:
gbrain doctor # half-migrated state? prints loud banner + exits non-zero
gbrain skillpack-check --quiet # exit 0/1/2 for pipeline gating
gbrain skillpack-check | jq # full JSON: {healthy, summary, actions[], doctor, migrations}
If anything's off, actions[] tells you the exact command to run. For deeper troubleshooting: docs/guides/minions-fix.md.
Skillify: your skills tree stops being a black box
Hermes and similar agent frameworks auto-create skills as a background behavior. Fine until you don't know what the agent shipped. Checklists decay. Tests drift. Resolver entries get stale. Six months later you've got an opaque pile of "skills" that nobody has read, nobody has tested, and nobody is sure still work.
GBrain ships the same capability. Except the human stays in the loop.
/skillifyturns raw code into a properly-skilled feature: SKILL.md + deterministic script + unit tests + integration tests + LLM evals + resolver trigger + resolver trigger eval + E2E smoke + brain filing. Ten items. Every one required.gbrain check-resolvablewalks the whole skills tree: reachability, MECE overlap, DRY violations, gap detection, orphaned skills. Exits non-zero if anything is off.scripts/skillify-check.ts— machine-readable audit.--jsonfor CI,--recentfor last-7-days files.
You decide when and what. The tooling keeps the checklist honest.
Why this is the right answer for OpenClaw
Auto-generated skills are a liability the first time a behavior breaks. Was it the skill? The test? The resolver trigger? The eval? You don't know, because you never read it. Debugging a black box is pure guesswork.
Skillify makes the black box legible. Every skill in your tree has: a contract (SKILL.md), tests that exercise that contract, an eval that grades LLM output against a rubric, a resolver trigger the user actually types, and a test that confirms the trigger routes right. If something breaks, you know which layer to look at. If anything goes stale, check-resolvable says so.
In practice this combo produces zero orphaned skills, every feature with tests + evals + resolver triggers + evals of the triggers. Compounding quality instead of compounding entropy.
# Audit a feature's skill completeness (10-item checklist)
bun run scripts/skillify-check.ts src/commands/publish.ts
# In CI: fail the build when a new feature isn't properly skilled
bun run scripts/skillify-check.ts --json --recent
# Validate the whole skills tree before shipping
gbrain check-resolvable
Skillify is not a nice-to-have. It's the piece that makes the skills tree survive six months of compounding work. Read skills/skillify/SKILL.md for the full 10-item checklist and the anti-patterns it catches.
Getting Data In
GBrain ships integration recipes that your agent sets up for you. Each recipe tells the agent what credentials to ask for, how to validate, and what cron to register.
| Recipe | Requires | What It Does |
|---|---|---|
| Public Tunnel | — | Fixed URL for MCP + voice (ngrok Hobby $8/mo) |
| Credential Gateway | — | Gmail + Calendar access |
| Voice-to-Brain | ngrok-tunnel | Phone calls to brain pages (Twilio + OpenAI Realtime) |
| Email-to-Brain | credential-gateway | Gmail to entity pages |
| X-to-Brain | — | Twitter timeline + mentions + deletions |
| Calendar-to-Brain | credential-gateway | Google Calendar to searchable daily pages |
| Meeting Sync | — | Circleback transcripts to brain pages with attendees |
Data research recipes extract structured data from email into tracked brain pages. Built-in recipes for investor updates (MRR, ARR, runway, headcount), expense tracking, and company metrics. Create your own with gbrain research init.
Run gbrain integrations to see status.
GBrain + GStack
GStack is the engine. GBrain is the mod.
- GStack = coding skills (ship, review, QA, investigate, office-hours, retro). 70,000+ stars, 30,000 developers per day. When your agent codes on itself, it uses GStack.
- GBrain = everything-else skills (brain ops, signal detection, ingestion, enrichment, cron, reports, identity). When your agent remembers, thinks, and operates, it uses GBrain.
hosts/gbrain.ts= the bridge. Tells GStack's coding skills to check the brain before coding.
gbrain init detects if GStack is installed and reports mod status. If GStack isn't there, it tells you how to get it.
Architecture
┌──────────────────┐ ┌───────────────┐ ┌──────────────────┐
│ Brain Repo │ │ GBrain │ │ AI Agent │
│ (git) │ │ (retrieval) │ │ (read/write) │
│ │ │ │ │ │
│ markdown files │───>│ Postgres + │<──>│ 26 skills │
│ = source of │ │ pgvector │ │ define HOW to │
│ truth │ │ │ │ use the brain │
│ │<───│ hybrid │ │ │
│ human can │ │ search │ │ RESOLVER.md │
│ always read │ │ (vector + │ │ routes intent │
│ & edit │ │ keyword + │ │ to skill │
│ │ │ RRF) │ │ │
└──────────────────┘ └───────────────┘ └──────────────────┘
The repo is the system of record. GBrain is the retrieval layer. The agent reads and writes through both. Human always wins... edit any markdown file and gbrain sync picks up the changes.
The Knowledge Model
Every page follows the compiled truth + timeline pattern:
---
type: concept
title: Do Things That Don't Scale
tags: [startups, growth, pg-essay]
---
Paul Graham's argument that startups should do unscalable things early on.
The key insight: the unscalable effort teaches you what users actually
want, which you can't learn any other way.
---
- 2013-07-01: Published on paulgraham.com
- 2024-11-15: Referenced in batch W25 kickoff talk
Above the ---: compiled truth. Your current best understanding. Gets rewritten when new evidence changes the picture. Below: timeline. Append-only evidence trail. Never edited, only added to.
Knowledge Graph
Pages aren't just text. Every mention of a person, company, or concept becomes a typed link in a structured graph. The brain wires itself.
Write a meeting page mentioning Alice and Acme AI
-> Auto-link extracts entity refs from content (zero LLM calls)
-> Infers types: meeting page + person ref => `attended`
"CEO of X" pattern => `works_at`
"invested in" => `invested_in`
"advises", "advisor" => `advises`
"founded", "co-founded" => `founded`
-> Reconciles stale links: edits remove links no longer in content
-> Backlinks rank well-connected entities higher in search
gbrain graph-query people/alice --type attended --depth 2
# returns who Alice met with, transitively
The graph powers questions vector search can't: "who works at Acme AI?", "what has Bob invested in?", "find the connection between Alice and Carol". Backfill an existing brain in one command:
gbrain extract links --source db # wire up the existing 29K pages
gbrain extract timeline --source db # extract dated events from markdown timelines
Then ask graph questions or watch the search ranking improve. Benchmarked: Recall@5 jumps from 83% to 95%, Precision@5 from 39% to 45%, +30 more correct answers in the agent's top-5 reads on a 240-page Opus-generated rich-prose corpus. Graph-only F1 hits 86.6% vs grep's 57.8% (+28.8 pts). See docs/benchmarks/2026-04-18-brainbench-v1.md.
Search
Hybrid search: vector + keyword + RRF fusion + multi-query expansion + 4-layer dedup.
Query
-> Intent classifier (entity? temporal? event? general?)
-> Multi-query expansion (Claude Haiku)
-> Vector search (HNSW cosine) + Keyword search (tsvector)
-> RRF fusion: score = sum(1/(60 + rank))
-> Cosine re-scoring + compiled truth boost
-> 4-layer dedup + compiled truth guarantee
-> Results
Keyword alone misses conceptual matches. Vector alone misses exact phrases. RRF gets both. Search quality is benchmarked and reproducible: gbrain eval --qrels queries.json measures P@k, Recall@k, MRR, and nDCG@k. A/B test config changes before deploying them.
Why it works: many strategies in concert
The brain isn't one trick. Every retrieval question goes through ~20 deterministic techniques layered together. No single one is magic; the win comes from stacking them so each layer covers what the others miss.
Question
│
├─ INGESTION (every put_page)
│ ├─ Recursive markdown chunking (or semantic / LLM-guided)
│ ├─ Embedding cache invalidation on edit
│ └─ Idempotent imports (content-hash dedup)
│
├─ GRAPH EXTRACTION (auto-link post-hook, zero LLM)
│ ├─ Entity-ref regex (markdown links + bare slugs)
│ ├─ Code-fence stripping (no false-positive slugs in code blocks)
│ ├─ Typed inference cascade (FOUNDED → INVESTED → ADVISES → WORKS_AT)
│ ├─ Page-role priors (partner-bio language → invested_in)
│ ├─ Within-page dedup (same target collapses to one link)
│ ├─ Stale-link reconciliation (edits remove dropped refs)
│ └─ Multi-type link constraint (same person can works_at AND advises)
│
├─ SEARCH PIPELINE (every query)
│ ├─ Intent classifier (entity / temporal / event / general — auto-routes)
│ ├─ Multi-query expansion (Haiku rephrases the question 3 ways)
│ ├─ Vector search (HNSW cosine over OpenAI embeddings)
│ ├─ Keyword search (Postgres tsvector + websearch_to_tsquery)
│ ├─ Reciprocal Rank Fusion (score = sum 1/(60+rank) across both)
│ ├─ Cosine re-scoring (re-rank chunks against actual query embedding)
│ ├─ Compiled-truth boost (assessments outrank timeline noise)
│ ├─ Backlink boost (well-connected entities rank higher)
│ └─ Source-aware dedup (one CT chunk per page guaranteed)
│
├─ GRAPH TRAVERSAL (relational queries)
│ ├─ Recursive CTE with cycle prevention (visited-array check)
│ ├─ Type-filtered edges (--type works_at, attended, etc.)
│ ├─ Direction control (in / out / both)
│ └─ Depth-capped (≤10 for remote MCP; DoS prevention)
│
└─ AGENT WORKFLOW (graph-confident hybrid)
├─ Graph-query first (high-precision typed answers)
├─ Grep fallback when graph returns nothing
└─ Graph hits ranked first in top-K (better P@K and R@K)
End-to-end on the BrainBench v1 corpus (240 rich-prose pages, before/after PR #188):
| Metric | BEFORE PR #188 | AFTER PR #188 | Δ |
|---|---|---|---|
| Precision@5 | 39.2% | 44.7% | +5.4 pts |
| Recall@5 | 83.1% | 94.6% | +11.5 pts |
| Correct in top-5 | 217 | 247 | +30 |
| Graph-only F1 (ablation) | 57.8% (grep) | 86.6% | +28.8 pts |
Plus 5 orthogonal capability checks (identity resolution, temporal queries, performance at 10K-page scale, robustness to malformed input, MCP operation contract). All pass. Full report.
The point: each technique handles a class of inputs the others miss. Vector search misses exact slug refs; keyword catches them. Keyword misses conceptual matches; vector catches them. RRF picks the best of both. Compiled-truth boost keeps assessments above timeline noise. Auto-link extraction wires the graph that lets backlink boost rank well-connected entities higher. Graph traversal answers questions search alone can't reach. The agent picks graph-first for precision and falls back to keyword for recall. All deterministic, all in concert, all measured.
Voice
Call a phone number. Your AI answers. It knows who's calling, pulls their full context from the brain, and responds like someone who actually knows your world. When the call ends, a brain page appears with the transcript, entity detection, and cross-references.
The voice recipe ships with GBrain: Voice-to-Brain. WebRTC works in a browser tab with zero setup. A real phone number is optional.
Engine Architecture
CLI / MCP Server
(thin wrappers, identical operations)
|
BrainEngine interface (pluggable)
|
+--------+--------+
| |
PGLiteEngine PostgresEngine
(default) (Supabase)
| |
~/.gbrain/ Supabase Pro ($25/mo)
brain.pglite Postgres + pgvector
embedded PG 17.5
gbrain migrate --to supabase|pglite
(bidirectional migration)
PGLite: embedded Postgres, no server, zero config. When your brain outgrows local (1000+ files, multi-device), gbrain migrate --to supabase moves everything.
File Storage
Brain repos accumulate binaries. GBrain has a three-stage migration:
gbrain files mirror <dir> # copy to cloud, local untouched
gbrain files redirect <dir> # replace local with .redirect pointers
gbrain files clean <dir> # remove pointers, cloud only
gbrain files restore <dir> # download everything back (undo)
Storage backends: S3-compatible (AWS, R2, MinIO), Supabase Storage, or local.
Commands
SETUP
gbrain init [--supabase|--url] Create brain (PGLite default)
gbrain migrate --to supabase|pglite Bidirectional engine migration
gbrain upgrade Self-update with feature discovery
PAGES
gbrain get <slug> Read a page (fuzzy slug matching)
gbrain put <slug> [< file.md] Write/update (auto-versions)
gbrain delete <slug> Delete a page
gbrain list [--type T] [--tag T] List with filters
SEARCH
gbrain search <query> Keyword search (tsvector)
gbrain query <question> Hybrid search (vector + keyword + RRF)
IMPORT
gbrain import <dir> [--no-embed] Import markdown (idempotent)
gbrain sync [--repo <path>] Git-to-brain incremental sync
gbrain export [--dir ./out/] Export to markdown
FILES
gbrain files list|upload|sync|verify File storage operations
EMBEDDINGS
gbrain embed [<slug>|--all|--stale] Generate/refresh embeddings
LINKS + GRAPH
gbrain link|unlink|backlinks Cross-reference management
gbrain extract links|timeline|all Batch backfill from existing pages
(--source db|fs, --type, --since, --dry-run)
gbrain graph-query <slug> Typed traversal (--type T --depth N
--direction in|out|both)
JOBS (Minions)
gbrain jobs submit <name> [--params JSON] [--follow] Submit a background job
gbrain jobs list [--status S] [--queue Q] List jobs with filters
gbrain jobs get|cancel|retry|delete <id> Manage job lifecycle
gbrain jobs prune [--older-than 30d] Clean completed/dead jobs
gbrain jobs stats Job health dashboard
gbrain jobs smoke One-command health check
gbrain jobs work [--queue Q] [--concurrency N] Start worker daemon
ADMIN
gbrain doctor [--json] [--fast] Health checks (resolver, skills, DB, embeddings)
gbrain doctor --fix Auto-fix resolver issues
gbrain stats Brain statistics
gbrain serve MCP server (stdio)
gbrain integrations Integration recipe dashboard
gbrain check-backlinks check|fix Back-link enforcement
gbrain lint [--fix] LLM artifact detection
gbrain repair-jsonb [--dry-run] Repair v0.12.0 double-encoded JSONB (Postgres)
gbrain orphans [--json] [--count] Find pages with zero inbound wikilinks
gbrain transcribe <audio> Transcribe audio (Groq Whisper)
gbrain research init <name> Scaffold a data-research recipe
gbrain research list Show available recipes
Run gbrain --help for the full reference.
Origin Story
I was setting up my OpenClaw agent and started a markdown brain repo. One page per person, one page per company, compiled truth on top, timeline on the bottom. Within a week: 10,000+ files, 3,000+ people, 13 years of calendar data, 280+ meeting transcripts, 300+ captured ideas.
The agent runs while I sleep. The dream cycle scans every conversation, enriches missing entities, fixes broken citations, consolidates memory. I wake up and the brain is smarter than when I went to sleep.
The skills in this repo are those patterns, generalized. What took 11 days to build by hand ships as a mod you install in 30 minutes.
Docs
For agents:
- skills/RESOLVER.md ... Start here. The skill dispatcher.
- Individual skill files ... 25 standalone instruction sets
- GBRAIN_SKILLPACK.md ... Legacy reference architecture
- Getting Data In ... Integration recipes and data flow
- GBRAIN_VERIFY.md ... Installation verification
For humans:
- GBRAIN_RECOMMENDED_SCHEMA.md ... Brain repo directory structure
- Thin Harness, Fat Skills ... Architecture philosophy
- ENGINES.md ... Pluggable engine interface
Reference:
- GBRAIN_V0.md ... Full product spec
- CHANGELOG.md ... Version history
Benchmarks:
- BrainBench v1 (PR #188) ... single comprehensive before/after report on a 240-page Opus-generated corpus. 7 categories: relational queries, identity resolution, temporal queries, performance, robustness, MCP contract.
Contributing
See CONTRIBUTING.md. Run bun test for unit tests. E2E tests: spin up Postgres with pgvector, run bun run test:e2e, tear down.
PRs welcome for: new enrichment APIs, performance optimizations, additional engine backends, new skills following the conformance standard in skills/skill-creator/SKILL.md.
License
MIT
