Repository navigation
Releases: andreineacsu/gbrain
Release list
v0.60.64.0
An agent connected to a remote brain can now save a stack of long pages in under a minute, and gbrain tells it how.
Agents writing to a brain over MCP (for example through gbrain serve --http on another machine) used to save pages one at a time, and each page cost several seconds of back-and-forth with the database. A large page also outlasted the reply's built-in wait, so the agent got "still pending" and had to keep asking. Writing 27 long research pages took about 15 minutes, followed by around 30 more calls to wire up links the pages already contained.
Now the agent sends its pages in batches with put_pages, waits for the commit in the same reply with wait_ms, and the server links [[wikilinks]] between the pages by itself. The server also does far less database work per page and publishes a whole batch in one transaction. The instructions every MCP agent receives at connect time say all of this up front.
Measured with 25 pages of 60 KB each, written over MCP to a Postgres brain 30 ms away (round trip), with embeddings and Git on:
| Before | Now | |
|---|---|---|
25 pages, one put_page at a time, with polling |
nearly 5 minutes (about 11 s a page) | not needed |
25 pages, 4 put_pages calls sent one after another |
not available | 49 s, no polling |
One 60 KB put_page |
7.2 s reply saying "pending", then polls (about 11 s total) | committed in about 6 s, in the reply |
| SQL statements on one page's publish path | 135 | 115 |
| Backlinks between the pages you just wrote | about 30 manual add_link calls |
built automatically after the commit |
To take advantage of v0.60.64.0
gbrain upgrade should do this automatically. Remote agents pick up the new instructions the next time they connect.
- Tell your agent (or let the connect-time instructions do it): "When you save more than three pages to gbrain, use put_pages with one request_id per batch and wait_ms 25000."
- Turn mention links off if you don't want them:
gbrain config set mcp.remote_auto_links false. - Verify:
gbrain call put_pages '{"request_id":"<uuid>","pages":[{"slug":"notes/test-a","content":"# A"},{"slug":"notes/test-b","content":"# B\n\nSee [[notes/test-a]]."}],"wait_ms":25000}'returns"state": "committed", andgbrain call get_links '{"slug":"notes/test-b"}'shows thementionslink tonotes/test-a.
Itemized changes
Batch writes
- New MCP tool
put_pages(full surface): 1-50 complete pages, 8 MB of content at most, per call. Every page is an ordinaryput_pagewrite with the same fences, revision checks and receipts. The pages are accepted together, so queue capacity (request count and bytes) is reserved for the whole batch, or the call refuses withqueue_capacityand accepts none. - One receipt per batch: the state of each page with its revision or its own error envelope, totals,
links, andnext(done,pollorfix_pages). A page refused for its own reason does not stop the others. A grant that cannot write refuses the whole call with onepermission_denied. - Replaying the identical call with the same
request_idnever writes twice.put_pageswith onlyrequest_idreports progress without resending content. A replay with different pages refuses withidempotency_conflictand names the changed pages. - Every page needs
put_pagepermission; bound clients get each page's slug fenced likeput_page.
Waiting for the commit
put_pageandput_pagesacceptwait_ms(0-30000, counted from arrival; default 5000 forput_page, 25000 forput_pages). It is not part of the write's identity, so a replay with a different wait is still the same write. Out-of-range values refuse withinvalid_write_waitbefore anything is accepted.- A pending receipt's
retry_after_mscomes from the server's recent publishing pace instead of a fixed 1 s. get_write_requeststates its poll cadence and final states; a pendingput_pagenameswait_msandput_pages.
Faster publishing
- A
put_pagesbatch publishes up to 8 pages per database transaction, with file-safe recovery: if one page fails, the group rolls back, every file is restored, and the pages publish one at a time so each failure stays with its own page. - Fewer database round trips per write: claiming, admission, recovery records and completion each take fewer statements, a page's own writes skip repeated reads, and a batch's shared admission reads run once.
- Connector writes wait for a pending withdrawal rewrite of the same page before reading its file, so a sync right after a
forgetno longer fails withsource_changed.
Links for remote writes
- Remote
put_page,put_pages,captureandedit_pagewrites queue alinkseffect after the commit. It turns[[wikilinks]]and markdown links in the page body into plainmentionslinks to pages that already exist in the same source, are visible to the writer and sit inside its slug grant. Typed, frontmatter and timeline edges stay off for untrusted writes. A batch links its pages to each other once its last page commits. - On by default;
mcp.remote_auto_links falseturns it off. Receipts reportauto_links.mention_linksand the effect'sadded/removedcounts.
Agent guidance
- The connect-time instructions put the prompt-critical lines first, so harnesses that read only the first 2,048 characters still see them:
context_packat session start,put_pagereplaces the whole page, and the writing guidance (put_pages,wait_ms, followingnext). put_page,add_linkandadd_timeline_entrydescriptions say when links and timeline entries come from the page itself.- The simple HTTP transport's 413 reply tells the agent to split the request and names
GBRAIN_HTTP_MAX_BODY_BYTES.
For contributors
- New e2e coverage: Postgres statement budgets per write phase, two-connection page-guard tests, grouped
put_pagepublication (happy path and per-member failure), and the remote links effect on both engines. - The initialize-instructions and served-schema budgets grew for the write guidance; a new test pins the prompt-critical lines inside the first 2,048 characters.
v0.60.32.0
Fix wave 7: automatic capture stops double-storing what you remember, the maintenance sweep reads whole transcripts, Gmail commitments and timelines work again on managed brains, gbrain upgrade refuses a Bun it can't run, the contradiction judge stops excusing undated conflicts, "who is waiting on me" ages and ranks requests honestly, and 62 community pull requests land.
When two notes give different numbers for the same thing and neither note has a date, nothing tells you which came first, so that is a conflict you need to resolve. The judge kept calling those pairs a change over time instead, guessing an order from the numbers themselves (the bigger headcount must be newer) or from the kind of note (a board deck must be newer than a memo). Prompt v4 tells it plainly not to do that, and that one note saying nothing about headcount does not contradict another note that gives one. On a fresh test world it now calls 149 of 150 planted same-time conflicts contradictions (was 131), all 50 undated ones included (was 37). The price is a few more false alarms: 14 of 820 unrelated pairs (was 11) and 5 of 50 compatible pairs (was 2).
gbrain waiting had four rough edges, all found by the gbrain-evals open-loops wave. A reply that only said "Thanks!" to someone's question closed the loop although the question was still open. An FYI whose only question mark sat inside a link opened a loop as if you had asked something. A first sync of an old inbox ranked a request that had waited 30 days as no older than one from yesterday, because a loop's age started when gbrain noticed it. And the ranking moved with the wall clock, so a published order could not be reproduced. All four are fixed; --as-of pins the clock.
| After upgrading | Before | After |
|---|---|---|
| Undated same-fact conflicts called a contradiction (fresh N2 seed) | 37/50 | 50/50 |
| All same-time conflicts called a contradiction (fresh N2 seed) | 131/150 | 149/150 |
| False alarms on unrelated / compatible pairs (fresh N2 seed) | 11/820, 2/50 | 14/820, 5/50 |
| "Thanks!" in reply to a question | closes the loop | loop stays open |
FYI whose only ? is inside a link |
opens a loop after 72 h | opens nothing |
| A 30-day-old request found on first sync | ranked as new | ranked 30 days old |
gbrain waiting order |
moves with the wall clock | pinnable with --as-of |
| "Who works at Acme?" when "Acme Labs" also exists | no seed, arm silent | seeds from the page titled "Acme" |
A meeting note's Participants: list |
mentions | attended |
segments.join("/") in a file that defines join |
resolved as a caller of join |
unresolved |
| Keyword-only answer to a question no page can answer | graded moderate | graded weak |
check-backlinks fix on a managed brain |
one refusal per page; dry run claims success | one clear refusal, dry run too |
A fact remembered, then seen again by automatic capture |
stored twice | stored once (corrections always kept) |
| Sweep extraction of a 120 KB session transcript | first 8,000 characters | whole transcript, in windows |
| Gmail commitments on a managed brain | never extracted | extracted every sweep |
gbrain upgrade to a release needing a newer Bun |
installs, then fails to start | refuses (exit 78); autopilot holds |
| Remote title search, rare term, 40k pages (Postgres) | ~490 ms | ~34 ms |
Conversation-facts backlog with source-evidence pages marked conversation_parseable: false |
never reaches zero | drains |
chronicle-backfill --limit N, run twice |
the same N pages again | the next N pages |
These came from the gbrain-evals category waves A4 (abstention), N2 (contradictions), N7 (open loops), N9 (multi-hop), N12 (meeting formats) and N13 (code intelligence), plus open issues garrytan#5341, garrytan#5330 and garrytan#5329, and the GBRA-35 lane's fixes for garrytan#5888, garrytan#5887, garrytan#5889, garrytan#5890, garrytan#5892, garrytan#5855, garrytan#5902, garrytan#5875, garrytan#5867, garrytan#5856, garrytan#5877, garrytan#5854, garrytan#5868 and garrytan#5869.
Memory capture got two fixes that change what lands in your brain. A fact your agent saved with remember was often stored a second time by automatic capture a moment later; capture now skips an exact same-claim copy on the same entity, and never skips a correction. And the maintenance sweep only ever read the first 8,000 characters of each session transcript, so everything said after that never became a fact. It now reads whole transcripts in turn-aligned windows and remembers where it stopped. That costs more model calls on long sessions (about 15 for a typical 120 KB transcript, capped per sweep).
Managed brains with Google connected had quietly stopped doing several jobs: Gmail commitments were never extracted, Gmail and Calendar pages got no links or timeline entries, quiet threads never opened their loops once the waiting window passed, and closing a commitment loop claimed it retired the fact when it hadn't. All four work again. gbrain upgrade now checks the target release's Bun requirement before swapping, so a host can no longer upgrade itself into a binary it can't start. An exact-title page ranks first in title search again, and remote title search is index-backed (a rare term on a 40k-page Postgres brain: about 490 ms to about 34 ms).
The release also absorbs 62 open community pull requests, each re-checked against current master: sync stops deleting pages it does not own and keeps skillpack files out of managed checkouts, backups and storage restores cover every source and every live page, Claude Sonnet 5.5 works and Sonnet 5 is priced at its standard rate, list_pages pages losslessly, a multi-line fact no longer blocks its page from being written, and two search paths get faster. Thank you to everyone listed below.
To take advantage of v0.60.32.0
gbrain upgrade installs the binary. There is no schema migration. Restart every gbrain serve, autopilot and worker afterwards so they run the new code.
- Meeting pages re-derive their attendees on their own. The link extractor version moved, so the next
gbrain extract --stale(or the autopilot cycle) re-readsParticipants:lists. - Expect one paid re-judge of contradictions. The judge prompt version moved to 4, so the next
gbrain eval suspected-contradictionsre-judges every pair. It prints its cost estimate first, as on any prompt change. - Existing loops get request-time ages when their thread is next checked. An open thread loop keeps the earlier of its stored age and the oldest unanswered message's time, so the next sync that touches a thread corrects an age set at detection time.
- Code call graphs pick up the member-call fix when a file re-imports. To refresh every code page now:
gbrain reindex-code. - Preview the two new explicit repairs, and apply only after you agree.
gbrain doctorreportscaptured_facts_active(facts captured before v0.60.30.0 from gbrain's own claude-cli sessions or pasted text) andloop_facts_drift(facts of loops closed before this release):Guide: docs/guides/repair.md.gbrain repair captured-facts # preview; apply with --apply --expect <hash> gbrain repair loop-facts # preview
- Decide on connector atom extraction. Atom extraction of connector
emailandmeetingpages is now opt-in on every brain. To keep it (the text of each thread or event goes to yourextract_atomsmodel):gbrain config set cycle.extract_atoms.connector_pages true. - Cached search results are invalidated once (
KNOBS_HASH_VERSION29 -> 30). If you changed your FTS language, rungbrain reindex-search-vectorso remote title search matches it. - Verify:
gbrain waiting --as-of 2026-10-01T12:00:00Z --json | grep as_of gbrain sweep --once --json # corpus_files[] shows windows_done / windows_remaining gbrain doctor
- If any step fails, file an issue at https://github.com/garrytan/gbrain/issues with the output of
gbrain doctorand~/.gbrain/upgrade-errors.jsonlif it exists.
Behavior changes
- Automatic capture skips an exact copy of a claim already stored in the same source on the same entity (or another entity the claim names), visible where the new fact would be, and written within 15 minutes of the turn or by capture in the same conversation. Reworded copies are never skipped, so corrections ("is not moving", a changed amount or place) are always kept. Explicit
rememberis never skipped (garrytan#5888). - The sweep extracts whole transcripts in turn-aligned windows of about 8,000 characters, with a
<file>.progresssidecar, capped at 8 windows per file and 32 per sweep (GBRAIN_CORPUS_WINDOWS_PER_SWEEP). Transcripts marked done before this release are not re-read; only turns added after the upgrade are extracted (garrytan#5887). gbrain upgradeandgbrain self-upgraderefuse a release whose Bun floor the host does not meet (exit 78, nothing changed);--no-bun-floor-checkupgrades anyway when the floor cannot be read. Autopilot holds the upgrade andself_upgrade_healthshows the hold and fix (garrytan#5855).- Remote title matching follows the language
search_vectorwas built with (garrytan#5889). - A synthesis publish still pending after the 30 s writer wait is a phase warning (
publish_pending), not a failure, and the next cycle finishes it (garrytan#5854). - Connector
emailandmeetingatom extraction is opt-in on every brain (cycle.extract_atoms.connector_pages). The auto-drain's daily cap counts attempts at the per-attempt estimate, so 6 attempts a day by default (garrytan#5856). loops_closereturns{ closed, id, status, fact_expired, retryable, reason? }and expires the fact and strikes its fence row in one coordinated write (garrytan#5869).gbrain extract timeline --source dbexits non-zero when a write is refused, naming the code and recovery (garrytan#5904 probe).- Short random passwords are redacted reliably. The
high_entropy_assignmentrule (retriev...
v0.60.27.0
GBrain now needs Bun 1.4 or newer, because a bug in older Bun releases could make GBrain wait forever on a helper process that had already finished.
GBrain starts small helper processes all the time: git for your brain repo, background workers, syncs. Older Bun releases had a bug where one of those helpers could exit and Bun would never pass the news along, so whatever was waiting for it waited forever. On a laptop that looks like a command that never returns. On an always-on brain it looks like a worker that quietly stops making progress. Bun 1.4 includes the fix, so 1.4.0 is now the minimum.
Compiled release binaries already carry Bun 1.4.2, so if you run the gbrain binary there is nothing to do. If you installed from source with Bun, run bun upgrade first.
On an older Bun, every command (including serve, jobs work, hooks and autopilot) stops at startup with one message that names your Bun, the minimum and the fix, instead of starting and hanging later:
GBrain requires Bun 1.4.0 or newer (found Bun 1.3.14).
Fix: run `bun upgrade`, then restart GBrain. If a `gbrain upgrade` stopped here, finish it with `gbrain post-upgrade`.
| You run GBrain on | What happens after this release |
|---|---|
The compiled gbrain binary |
Nothing changes |
| Bun 1.4.0 or newer | Nothing changes; gbrain doctor shows a new bun_runtime row |
| Bun 1.3.x | Commands refuse with the message above until you run bun upgrade |
Things to watch
- If you run
gbrain upgradewhile still on Bun 1.3, the new version installs but its migrations stop at the runtime check, and the install prints the same message. Runbun upgrade, thengbrain post-upgrade. - Services under launchd, systemd or cron keep retrying and recover by themselves once Bun is upgraded. The refusal is in
~/.gbrain/autopilot.log. gbrain --versionstill answers on an older Bun, so tools that check the version keep working.
To take advantage of v0.60.27.0
-
Upgrade Bun (source installs only; compiled binaries skip this):
bun upgrade bun --version # 1.4.0 or newer -
Upgrade GBrain, or finish an upgrade that stopped at the runtime check:
gbrain upgrade # or, if you already upgraded on the old Bun: gbrain post-upgradeThen restart anything long-running:
gbrain serve(restart your agent harness for stdio MCP),gbrain jobs supervisor, autopilot. -
Your agent reads
skills/migrations/v0.60.27.0.mdthe next time you interact with it. It checks the runtime, asks before runningbun upgradefor you, and finishes the upgrade. -
Verify the outcome:
gbrain doctor # bun_runtime: "Bun 1.4.x (minimum 1.4.0)" -
If any step fails, please file an issue:
https://github.com/garrytan/gbrain/issues with:- output of
gbrain doctor - output of
bun --versionandwhich -a bun - contents of
~/.gbrain/upgrade-errors.jsonlif it exists
This feedback loop is how the gbrain maintainers find fragile upgrade paths. Thank you.
- output of
Say to your agent: "Is my Bun new enough for GBrain?" or "Finish upgrading GBrain after the Bun upgrade."
Itemized changes
Runtime floor
MINIMUM_BUN_VERSIONinsrc/core/runtime-version.tsandengines.buninpackage.jsonare now 1.4.0, the lowest 1.4 release. It carries the Linux child-exit fix (oven-sh/bun#30301, first shipped in 1.3.14), andtest/bounded-child-exec.test.tspasses on it.unsupportedBunMessage()builds the one refusal text: found version, minimum,bun upgrade, restart, andgbrain post-upgradefor an interrupted upgrade.assertSupportedBun()throws it with codeUNSUPPORTED_RUNTIME.- The CLI entrypoint (
src/cli.ts) checks before any command runs, so every subcommand fails fast with exit 1.gbrain --versionprints the version and exits 0, with the refusal on stderr, so an upgrade started by an older gbrain can still confirm what it installed.gbrain autopilotalso writes the refusal to stdout, because its services log stdout toautopilot.logand stderr to anautopilot.errnothing points at. scripts/postinstall.tsprints the refusal when an install orbun updatelands on an older Bun, and still exits 0.- New doctor check
bun_runtime(ops):Bun <version> (minimum 1.4.0), or a failure with the fix command. gbrain bootstrap cloud-setup-scriptinstalls Bun through npm when the sandbox has no Bun or one older than 1.4.0, and its launcher runs that Bun.- Install docs (README,
INSTALL_FOR_AGENTS.md,BOOTSTRAP_FOR_AGENTS.md,CONTRIBUTING.md,SECURITY.md,docs/guides/authorization-upgrade.md) state Bun 1.4.0 or newer and namebun upgrade.
What Bun 1.4 does not fix
- Bun still drops a child's pipe events when a callback re-enters the event loop (for example bun:test
expect().resolves); a raw repro withoutexecFileBoundedstill hangs on 1.4.2. The in-code bounds ongitchildren stay, and their comments now say which part Bun fixed.
For contributors
- The minimum-version CI lanes move to 1.4.0:
test.ymlsecurity regressions and all fourpersistence-validation.ymlmatrices run 1.4.0 and 1.4.2 (pull requests still skip the minimum).native-locks.ymldrops 1.3.11 and 1.3.13 and runs 1.4.0 and 1.4.2 on full scope (16 native pairs, 4 musl, 4 per Windows probe). test/scripts/ci-pr-scope.test.tsrequires every minimum-version matrix to be exactly[MINIMUM_BUN_VERSION, primary]. Newtest/runtime-version.test.tspins the refusal text, the doctor row, and the floor inpackage.jsonand the cloud setup script.- Doctor goldens scrub the running Bun version (
Bun <bun-version>). - If your local Bun is 1.3.x,
bun upgradebefore running the CLI or CLI-spawning tests.
v0.60.25.0
CI now runs on Bun 1.4.2, so contributors stop seeing random test hangs.
Bun 1.3 had a bug where a finished child process could go unnoticed. The process exited, but the "it exited" signal got lost, so whatever was waiting for it waited forever. In GBrain's test suites that showed up as hangs that ended in "killed 1 dangling process" and a red run nobody could reproduce. Bun 1.4 fixes the bug at the source. Every CI job that pinned Bun 1.3.13 now pins 1.4.2, the same version that already compiles the release binaries.
Two things behaved differently on 1.4 and are fixed. The out-of-band watchdog that kills a stuck gbrain sync or a wedged PGLite close kept killing on time, but its log lines (parent alive ..., SIGTERM, SIGKILL) stopped appearing while the process was stuck, because Bun 1.4 routes a worker thread's stderr through the main thread. They now go straight to the terminal, so cron logs show why a process died again. One test's fake database server also closed a socket twice, which Bun 1.4 reports as an error.
If you run GBrain from source on Bun 1.3.x, nothing changes. Bun 1.3.11 is still the minimum and CI still tests it on pushes to master and nightly. The worst case of the old bug, a background git call that never returns, has been bounded in code since v0.60.16.0.
| CI lane | Before | After |
|---|---|---|
| Unit, serial, slow, E2E, verify, release publishing | Bun 1.3.13 | Bun 1.4.2 |
| Security and persistence matrices | 1.3.11 and 1.3.13 | 1.3.11 and 1.4.2 |
| Native lock matrix on pull requests | 1.3.13 | 1.4.2 (pushes still run 1.3.11, 1.3.13 and 1.4.2) |
Local gates (ci:local, ci:ubicloud) |
Bun 1.3.13 | Bun 1.4.2 |
To take advantage of v0.60.25.0
Nothing to do. gbrain upgrade as usual; there is no migration and no Bun upgrade is required.
Itemized changes
- The sync hard-deadline watchdog and the stall watchdog (
src/core/process-watchdog.ts) write log lines with a direct fd 2 write from their worker thread, so heartbeat, SIGTERM and SIGKILL lines stay visible while the main thread is starved on Bun 1.4. - New in-agent installs (
scripts/setup-in-agent.sh) download the checksummed Bun 1.4.2 runtime. A repair keeps the runtime version recorded in its receipt.
For contributors
- Pins moved from 1.3.13 to 1.4.2: every
bun-version:intest.yml,e2e.yml,heavy-tests.yml,macos-validation.yml,persistence-validation.ymlandrelease.yml; theGBRAIN_CI_BUN_TAGdefault indocker-compose.ci.yml; theBUN_VERSIONdefault inscripts/ubicloud/setup-ci-vm.shand the fallback inscripts/ci-ubicloud.ts; theoven/bunimage intests/docker/. test.yml's security matrix and everypersistence-validation.ymlmatrix run 1.3.11 and 1.4.2; pull requests still skip 1.3.11.native-locks.ymlkeeps all three versions on full scope and narrows pull requests to 1.4.2.test/scripts/ci-pr-scope.test.tsfails when any single-version pin drifts from the primary, or when a matrix stops running the minimum supported Bun.test/postgres-engine-singleton-lifecycle.test.ts's fake endpoint ends each refused socket once.- Bun 1.4.2 accepts the committed
bun.lockunchanged.package.jsonengines stay at>=1.3.11.
v0.60.11.0
Managed brains stop re-writing the same pages every hour, every maintenance job finishes again, and after an upgrade doctor shows you each leftover problem with the exact fix.
If you connect Gmail, Calendar, Contacts or GitHub to a managed brain, each connector used to forget where it stopped after every maintenance cycle. So every hour it walked its whole window again and spent a new permanent write ID and receipt on every unchanged page. Now it resumes where it stopped, skips pages that did not change, and keeps going when the owner is slow instead of giving up after one write.
On the same brains, a dozen maintenance steps still used a writer that managed brains refuse. Concept synthesis paid for the model call and then died, taking the rest of the nightly job with it. Every maintenance writer now goes through the coordinator, and one failing step no longer stops the steps after it.
After an upgrade, gbrain post-upgrade prints a preview banner, and gbrain doctor --remediation-plan lists every repair with its command. gbrain doctor --remediate --yes --include-repairs --max-usd 2 applies the ones you agree to, under a spending cap.
| Managed brain, test fixtures on both engines | Before | After |
|---|---|---|
| Page admissions on a quiet connector's second run | every window item | 0 |
| Connector runs that resume their cursor after a maintenance cycle | none | all |
Global maintenance after synthesize_concepts is refused |
job dies | later phases run, job reports the failure |
Repairs listed by doctor --remediation-plan |
none | every kind with work, each with its command |
Commands from gbrain post-upgrade to a clean repair plan |
not possible | 3 |
| Grandfathering 250 pages on a pushed repository, seconds per page (PGLite / Postgres) | 0.456 / 0.821 | 0.060 / 0.070 |
Things to watch: the first connector run after the upgrade may re-admit its window once (see below). A page you saved to an unbound Postgres source still refuses by default; the error now names both fixes.
To take advantage of v0.60.11.0
gbrain upgrade should do this automatically. If it didn't, or if gbrain doctor warns about a partial migration:
-
Upgrade hosts in this order, with autopilot paused on connector hosts. Mixed versions refuse with typed errors instead of losing data, and this order keeps them away:
gbrain autopilot pause --reason "upgrading to v0.60.11.0" # on each host that runs connector jobs gbrain upgrade # first: every consumer and worktree-owner host gbrain upgrade # then: the connector hosts gbrain doctor --remediation-plan # preview; ask the user before applying gbrain autopilot resume # on each host you paused gbrain sources status # verify
If schema work did not complete, run
gbrain apply-migrations --yes --no-autopilot-installon the brain host. -
Expect one connector re-walk, once. Migration 176 moves each connector's cursor to its new key. A source whose newest cursor receipt was compacted re-walks its window once on its next run, and connector writes still queued from before the upgrade fail once with
connector_intent_outdated(detailpre_upgrade, nothing to do) and are fetched again. The post-upgrade banner andgbrain sources statuscount those sources. This first spike is expected and is not the hourly churn this release fixes. -
Your agent reads
skills/migrations/v0.60.11.0.mdthe next time you interact with it. It previews the recovery plan and asks you before applying anything. -
Verify the outcome:
gbrain sources status --json # after the second run a quiet connector shows page_admissions: 0 gbrain doctor --remediation-plan # no repair steps left once you applied the agreed ones gbrain sources writer status --json # writer versions per host
-
If any step fails or the numbers look wrong, please file an issue:
https://github.com/garrytan/gbrain/issues with:- output of
gbrain doctor - contents of
~/.gbrain/upgrade-errors.jsonlif it exists - which step broke
This feedback loop is how the gbrain maintainers find fragile upgrade paths. Thank you.
- output of
Itemized changes
Connectors (garrytan#5686, garrytan#5470, garrytan#5600, garrytan#5601)
- Stable connector identity. The checkpoint key, the admission and publication change checks and the attachment-repair preview use the parsed connector settings minus credential-delivery fields. Cycle stamps and other bookkeeping in
sources.configno longer restart a connector or fail its pending writes withsource_changed. - Account pinning. Each connector source pins the account its credential belongs to. Google checks it through every enabled service before importing; GitHub records the App installation, or the login under
scope: auto. A different account refuses withconnector_account_changedand two exact ways out. - Unchanged items take no admission. Connector sync, managed
gbrain import,gbrain sync --working-treeand company-profile sync run each item through its own publication preparation first and skip it when publishing would change nothing. Pages below the safe-chunk fence, pages whose search projection lags, deleted pages and changed pages are still published. An unchanged cursor is not saved again; freshness forgbrain waitingis stamped directly. - Accepted writes count as progress. A connector keeps going when the owner is slow, waits at most 30 seconds in total per run, and records writes still pending; the next run resolves them first and retries failed ones by itself.
extract_atomsreports a batch the owner accepted but has not published as pending, not failed, and dream no longer halts on it. gbrain sync --source <id> --reset-checkpointre-walks one connector's window. Unchanged pages are not admitted again and the account pin is kept.gbrain sources statusshows each connector's upgrade recovery state and its last run's admissions, skipped pages, pending writes and checkpoint admissions.- Mixed-version safety. Connector writes use a new intent format. An older consumer refuses it with
unsupported_mutation_protocoland the connector names the hosts to upgrade; an older connector's writes failconnector_intent_outdatedand are fetched again after the upgrade. - Your notes on connector pages survive a re-walk. A timeline entry you add to a connector page (
add_timeline_entry) is kept when the connector renders the page again from the provider, and adding one to a database-only connector source stays database-only instead of claiming the source or refusing.
Maintenance writers on managed brains (garrytan#5484, garrytan#5280, garrytan#5523, garrytan#5405)
synthesize_conceptspublishes through the maintenance coordinator in the same order as before: private first, then the provenance edges, then promotion to world. The authority check runs before any model call.- A failing phase no longer kills maintenance. A phase that throws is reported as a failed phase and later phases still run; the job still reports the failure and
gbrain dreamexits non-zero. Cancellation, a lost cycle lease and budget exhaustion still stop the job. A phase that fails after paid model calls is counted in doctor'sdream_paid_loopcheck. - More writers go through the coordinator: Life Chronicle events with their timeline row, the purge of deleted pages,
gbrain enrichandenrich_thin(keeping facts and takes fences), the drift report andgrade_takesauto-resolutions, the managedextractwalk,add_link/remove_linkas coordinated database-only writes (a manual link survives re-derivation),gbrain bootstrap verifycleanup, and the facts, phantom-redirect, open-loop and conversation-facts writers. - Every phase and mutating operation is classified for managed brains, and a matrix test runs every phase once on a managed PGLite and Postgres brain.
- Unbound Postgres sources (garrytan#5254). Saving a page to a source with a checkout folder but no owner still refuses by default, but the error (
owner_unavailable, detailunbound_source) names both fixes: bind with the printedgbrain sources writer claimcommand, or rungbrain config set persistence.unbound_write database_only. Migration 177 records those pages as database-only, so they stay that way after binding, and a publication racing a bind fails instead of committing. Doctor'sunbound_sourcecheck is ok while the source is unbound and warns once it is bound; when a canonical file appears at such a page's path,gbrain sources reconcile <source> <slug> --previewshows both sides and--applyresolves it.
Embeddings and migrations (garrytan#5680, garrytan#4616, garrytan#5621, garrytan#5530, garrytan#5289)
- Embedding migration budget (garrytan#5680). Each provider request is charged at its maximum and settled to reported usage, once per attempt, summed across retries and splits. When
--max-cost-usdis below the printed worst case, the migration stops before touching any vector and prints the command that covers it (embedding_budget_below_worst_case). - Degenerate vectors are refused per chunk (garrytan#4616). A zero-norm, NaN or infinite vector fails only its own chunk with
embedding_zero_norm; the page's other vectors are kept, on managed brains too, and the failure namesgbrain embed <slug>. Empty inputs never reach the provider.gbrain reindex --vectorsrebuilds vector indexes after a PGLite crash repair, and the repair notice names it. - Contextual retrieval mode (garrytan#5621).
--no-embedimports record their mode. Contributed by @woprrr (garrytan#5630).gbrain repair contextual-modestamps existing pages exactly as a fresh import would and re-embeds only pages whose input changes. - Grandfathering groups its Git work (garrytan#5530). The effect runner...
v0.59.0.0
The LongMemEval reader now checks the evidence before giving its short answer.
When an answer depends on several old conversations, the benchmark reader now
briefly extracts the relevant facts and reasons over them before answering. A
matched study over 361 questions moved from 308 to 324 judged correct answers
with the same full sessions, dates and question text. A separate replication
using the published supporting sessions also found a gain from notes in both
natural language and JSON; changing the format to JSON alone did not help.
These are reader comparisons, not evidence that everyday gbrain think
improved. The full research record includes losses, ambiguous grades, and nine
notes responses that reached the original 512-token output limit.
How to use it
gbrain eval longmemeval DATASET --reader-mode notes --reader-max-tokens 1024 --judge --output notes.ndjson
gbrain eval longmemeval DATASET --reader-mode direct --judge --output direct.ndjsonThe first command's reader settings are now the defaults. Direct keeps the
original prompt and 512-token cap, so existing baseline runs remain
reproducible. --reader-mode notes --reader-max-tokens 512 reproduces the
original treatment's output budget, but risks the same cutoffs. The larger
default cap is a mitigation, not a separately measured answer-quality gain. A
bounded nine-case replay of the prior cutoffs finished all nine naturally at
1024 tokens (445–603 output tokens), but did not remeasure answer accuracy.
What to watch
The reader still sees the same sanitized full conversations in the same order;
notes do not recover facts missing from retrieval. Output-limit, empty and
unknown completions now record an error and preserve any partial text only as
a diagnostic. They cannot be judged as complete answers or silently inflate
accuracy. Receipts pin the reader mode, prompt, model, output budget and finish
reason; resume rejects a different reader configuration even with the
retrieval-mixing override. Paid reader and judge calls remain opt-in.
To take advantage of v0.59.0.0
gbrain upgrade should apply the update. If it reports a partial migration or
gbrain doctor flags one, run gbrain apply-migrations --yes --no-autopilot-install
and then gbrain doctor. Read skills/migrations/v0.59.0.0.md for the
reader-default comparison boundary and verify the flags with
gbrain eval longmemeval --help. No database migration or new model key is
required; running a judged benchmark still needs its configured providers.
Itemized changes
Added
- Add a shared LongMemEval reader config and request builder with explicit
direct|notesselection and output budget flags, plus per-row and summary
configuration pins and strict resume checks. The stable package subpath
gbrain/eval/longmemeval/readerlets companion evaluation tools use the
same request builder instead of copying prompts. Existing invocation-guard
and canonical-pricing modules are also exported through stable package
subpaths so a capped companion evaluator can reuse the real cost controls. - Preserve original transfer and oracle comparison receipts, negative excerpt
experiments, source audits and the primary-study compendium underdocs/eval/
anddocs/research/, without promoting the experimental selector.
Fixed
- Retain all provider text blocks and any incomplete partial text separately
from a completed hypothesis. Output-limit, empty and unknown completions are
recorded as failed reader rows, kept in the judged denominator, and fail the
benchmark instead of passing a partial answer to the judge. - In the inactive experimental excerpt runner, future incomplete reader
responses also stop before judging rather than entering a completed pair;
historical records and results remain untouched.
v0.57.0.0
Know when an accepted write needs attention.
An accepted write is not always a finished write. When a page or remembered fact is waiting, its receipt can now distinguish ordinary pending work from a known blocker or an unusually old request. Your agent gets a sensible polling interval and a clear instruction to inspect the existing owner when waiting alone is no longer enough. It keeps the original request reference instead of creating a duplicate.
Locked expired work no longer holds up the entire scheduler while unrelated work could proceed. Slow preparation and supported database waits have deadlines, and repeated receipt waits no longer accumulate unfinished reads. A deadline does not turn a pending write into a successful save or authorize another publisher. Work that cannot actually be cancelled stays tracked until it settles, and shutdown keeps the existing safety protections in place. Once contention clears, the original accepted request can finish without losing its identity or applying its content twice.
How to check a pending write
Keep the original request_id and arguments. Read its receipt, then inspect the selected brain's existing owner when advised:
gbrain write-request <request-id> --json
gbrain sources writer status --brain <brain> --probe --json| What the receipt knows | What to do |
|---|---|
| Recent request, no known blocker | Poll after 1 second, then 5 seconds as it ages. |
| Ordinary contention or an earlier write | Retain the original request and poll after 5 seconds. |
| At least two minutes old, or an operator-required blocker | Inspect the existing owner; poll no faster than every 30 seconds. |
Age is advisory, not proof of a dead owner. Older clients may omit the optional diagnosis. Never remove locks, transfer ownership, or discard recovery records just because a request is old.
To take advantage of v0.57.0.0
Use gbrain upgrade during your approved owner rollout. If automatic migrations did not complete, run:
gbrain apply-migrations --yes --no-autopilot-install
gbrain sources writer status --brain <brain> --probe --json
gbrain statsSchema migration 165 adds an index for database-only pending writes. It does not rewrite accepted requests or change the writer protocol. Quiesce the existing owner before replacing or rolling it back, preserve original receipts and recovery state, and verify canonical page or fact readback before calling a deployed incident recovered. If migration or verification fails, report sanitized doctor output and the failing step at https://github.com/garrytan/gbrain/issues; do not include credentials or private content.
Itemized changes
- Pending receipts include validated, privacy-filtered age, assessment, reason and next action. Initial responses, replay, receipt helpers and frozen memory verbs preserve their existing required fields and error codes.
- Receipt health enrichment is authorization-first, batched for at most 100 receipts, bounded to a 500ms caller wait, and limited to one unsettled query per engine. The new pending index keeps retained terminal history out of this lookup.
- Expired-claim sweeps skip locked rows without bypassing same-root order. Supported scheduler and renewal waits use cancellation budgets; ordinary
put_pageandrememberpreparation gets a cooperative deadline with late-result fencing. - PostgreSQL timeout cancellation isolates the affected query from neighboring work, including transaction siblings and connections reassigned after a disconnect. Cancellation failures do not authorize blind statement retries.
- PostgreSQL pool shutdown rejects work still waiting for a connection instead of silently reconnecting after shutdown. Recognized shutdown cancellations no longer appear as resident storage failures.
- Trusted writer status exposes process-local phases, deadlines and attempts, with inspection advice consistent with receipt health. Operator guidance distinguishes observation from recovery authority and local tests from live recovery.