Reranker default moves to Voyage rerank-2.5 ahead of the ZeroEntropy hosted-API end date, with a fail-open no_key preflight (one audit row per process, no stderr), a reranker_skipped ranking-only degraded stage, and readiness surfaces in gbrain init, gbrain doctor reranker_health and gbrain search modes (remote callers get the verdict without the host key inventory). Merges master v0.48.1.0 (both degraded-stage vocabularies coexist). Docs: LongMemEval-S re-run at this release (strict recall_all@5 93.19% reranker off, 95.32% with the default reranker; receipts in gbrain-evals) and a present-tense rewrite of README and docs. Pins transitive fast-uri/qs via overrides. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
14 KiB
Ambient memory writeback
Ambient recall (ambient-recall.md) is the READ side of
memory: context arrives at session boundaries without a question being asked.
This guide is the WRITE side: when the brain's operator opts in, agents save
salient facts the user states directly — during ordinary conversation, without
being told to — through the existing MEMORY_VERBS surface. No new protocol
verb; remember and extract_facts do all the writing.
Say to your agent: "Turn on ambient memory writeback for my brain" —
"remember things I tell you without being asked" — your agent runs
gbrain config set memory.auto_writeback salient and
gbrain bootstrap harness --yes.
Off by default. Nothing in this feature ever enables itself.
Who gets asked (and who is never asked)
gbrain distinguishes PERSONAL brains from company/team brains and only ever offers ambient writeback on personal ones — capturing what people say to agents on a shared brain is a privacy decision the whole team owns, not a default.
- Declaration wins.
brain.audience(personal|shared) is the declared axis: set it yourself, letcompany-brainifystampsharedat its Phase-5 handoff, or answer the bootstrap interview. A declaration always beats the heuristic. - The heuristic is conservative. Without a declaration, only ≥3 distinct
non-automation MCP clients active in the last 30 days reads as shared
evidence (client count measures surface breadth, not people — Claude Code +
Codex + a phone client is one human).
gbrain doctor'smemory_writebackcheck shows the resolved audience and its reasons; correct a misclassification withgbrain config set brain.audience personal|shared. - The ask fires once. On personal brains,
gbrain initand the post-upgrade banner print a one-time[AGENT]-relayed disclosure + ask (sentinelmemory.auto_writeback_notice_shown); declining is permanent.gbrain advisorkeeps a quiet reminder afterward — informational only, never--apply-able: consent is never automated. - Silence everywhere else: shared/unknown brains, mounted team brains, thin clients, remote MCP callers, and any classifier failure.
- Explicitly enabling on a shared-classified brain works (operator sovereignty) but prints a caution: members' words get persisted into a store other authorized agents can read.
Modes
memory.auto_writeback |
What agents are told to save |
|---|---|
off (default) |
Nothing — no instruction section, no backstop, no banking. |
salient (recommended) |
Durable, notable claims only: preferences, corrections, decisions, commitments, relationships, project-state changes. The backstop keeps medium+ notability facts. |
all |
Every direct factual user statement — still excluding operational chatter, assistant-generated content, secrets/credentials, and quoted third-party material. Precision rides the extractor's semantic skip rules (the second of the two filters); expect more low-value facts and more extraction spend. |
Both planes carry the setting: gbrain config set memory.auto_writeback …
dual-writes the DB plane (authoritative — the serve re-checks it before any
extraction) and the ~/.gbrain/config.json mirror (what the engine-free
Stop-hook child reads). The mirror never enables anything on its own: it is
machine-global while DB rows are per-brain, so engine-backed resolution is
DB-only — a mounted or selected brain whose operator never opted in cannot
inherit another brain's setting. When the planes disagree (a failed
dual-write, a reinitialized DB, or a config set run on another machine of a
shared Postgres brain), that is plane drift: extraction gates hold banked
turns without the terminal skip (nothing is destroyed), gbrain doctor warns
with the one-line re-sync (gbrain config set memory.auto_writeback <mode>),
and a DB-write failure during config set itself exits non-zero and says the
runtime value is unchanged. Selecting a MOUNTED brain (--brain,
GBRAIN_BRAIN_ID, .gbrain-mount) writes the mount's DB row only — the
machine-local mirror gates the host's Stop hook, so enabling a team mount
never opts the host's own conversations into banking. A wrong-brain hook
bank remains harmless — the target serve's own DB gate decides.
The three activation surfaces
- MCP
instructions(all transports). When enabled, the initialize handshake appends a ~15-line ambient-writeback contract to the base operating contract — one claim perremembercall,entitywhenever a person/company/project is the subject, concise provenance (harness + session id + date), durable facts withoutttl, transient facts with the configured TTL, the skip-list, and the visibility rule. stdio resolves it at boot (restart to flip — same posture asmcp.strict_params); the HTTP transports resolve per request (restart-free, with a last-known-good bundle riding out config blips). The section only renders when the caller can actually invokeremember— OAuth tokens without write scope, slug-bound clients whose fence denies it, and clamped surfaces that drop it all get the base instructions instead (never orders to make calls dispatch will deny);extract_factsis only named when the transport's actual tool set can call it. - Managed harness instruction blocks.
gbrain bootstrap harness --yesinstalls the same contract (same builder — the surfaces cannot drift) as a managed block between<!-- gbrain:ambient-writeback:begin/end -->sentinels in user-scopeCLAUDE.md(Claude Code) and$CODEX_HOME/AGENTS.md(Codex). Idempotent re-runs; converge-on-off (re-running with writeback off removes the block);--removestrips it. Codex caveat: when$CODEX_HOME/AGENTS.override.mdexists, Codex ignoresAGENTS.mdentirely — bootstrap fails that target loudly and doctor warns, instead of reporting a dead integration as healthy. The block header names the serve endpoint; after changing mode/TTL/visibility config, re-rungbrain config set memory.auto_writeback <mode>thenbootstrap harness(the config set refreshes the engine-free posture stamp the renderer reads; doctor's drift warning names the same combo). Blocks install only after the same run's MCP registration confirms, and a failed final smoke test strips the blocks that run installed — no block outlives a rolled-back registration. Registrar mode (--urlto a non-loopback serve) never installs instruction blocks: the local setting speaks for the local brain, and the remote brain's own MCP instructions carry the contract when its operator enables writeback. - The Claude Code Stop-hook backstop. After each assistant turn, the
hook gates the user's message through a deterministic, zero-LLM filter
(min length — CJK-aware, ack/greeting lexicon, slash commands,
question-only turns, quoted/tool output, bulk pastes >8KB), secret-scans
it, banks it as a content-addressed
.wb-corpus file (same turn = same name = free dedup, even on keyless brains), and asks the serve to extract it asynchronously. The hook never blocks: its own 2s deadline inside Stop's 10s cap, exit 0 on every path, typed heartbeat reasons for every outcome. Serve down? The file waits for the maintenance sweep. The lane is engine-uniform: the IPC listener keys its socket off the brain's connection URL, so Postgres brains harvest the same way whenever agbrain servefor that brain is running (heartbeatno_servebetween serves — the banked file is the durable artifact either way).
Per-harness reality (honest limitations)
| Harness | Real-time contract | Backstop |
|---|---|---|
| Claude Code | MCP instructions + managed user CLAUDE.md block | Stop-hook lane (above) |
| Codex | MCP instructions + managed $CODEX_HOME/AGENTS.md block |
No per-turn hook exists (SessionEnd only, 3s hard-kill). The existing SessionEnd capture → corpus → maintenance-sweep extraction lane is the delayed backstop — whole-session, next-sweep latency, governed by facts.extraction_enabled (it predates this feature). |
| opencode / OpenClaw / others | MCP instructions when connected | None wired — follow-ups filed. |
The workspace-bootstrap "same-turn write-back" contract
(gbrain bootstrap contract, AGENTS.md) and the two surfaces above are three
renderings of one posture; the MCP section and the harness blocks share one
builder, and the workspace contract is the always-on convention documented in
bootstrap.md. Keep them coherent when editing any of them.
TTL: transient facts expire at read time
Agents pass ttl: "3d" (configurable: memory.auto_writeback_transient_ttl,
duration shorthand only, max 365d) on transient facts — current health,
location, travel, mood, near-term schedule. Durable facts carry no TTL.
Expiry is exact-time and read-side: active reads (recall, entity cards,
hot-memory injection, dedup candidates) exclude facts whose valid_until has
passed — no sweeper needed, nothing mutated. The rows stay in the database as
history (--asof and supersession views still see them), and a re-stated
fact after expiry inserts fresh. Backstop-extracted facts currently get no
TTL (the extractor doesn't classify transience yet — a filed follow-up), so a
transient fact caught only by the backstop is durable until forgotten.
Note on lapsed rows: valid_until is temporal validity and active reads
honor it, so a brain that already carries lapsed rows sees them leave the
active set on the first read after the migration (facts-health counts step
accordingly). Nothing is deleted or mutated; the doctor's
validity_lapsed_facts count sizes the shift.
Privacy and visibility
- What is never saved: greetings, acknowledgements, fact-free questions, the assistant's own inferences/diagnoses/speculation, tool output, quoted third-party material, pasted/imported text (unless the user explicitly asks), raw transcripts, and — in every mode — secrets/credentials (banked turns are secret-scanned before they touch disk; scanner unavailable = fail-closed skip).
worldis not the internet. Fact visibilityworldmeans readable by agents authorized on this brain — the default that makes the remember→recall round-trip work across sessions. Whenfacts.default_visibilityis unset, the instruction template tells agents to writeworld; only an explicitly-private brain gets the private posture, stated with its trade-off: private facts are readable by the local CLI only, so remote agents cannot recall them later. An explicit private setting is never widened — not by the template, not by the backstop (which resolvesfacts.default_visibilityexactly likeextract_factsalways has).- Backstop facts carry
source: 'hook:writeback'and the session'sGBRAIN_SOURCEon BOTH extraction paths: the prompt-time IPC ask carries it directly, and the banked filename embeds it (.src-<sourceId>segment) so the sweep fallback files the turn into the same source even when serve was down at Stop time — never the sweep's own source. A conversation spanning multiple brains still attributes to the configured one (same limitation as the existing capture lanes).
Cost posture
The instruction path costs nothing extra — the conversing model does the
salience filtering in-line. The backstop runs one extraction call per banked
turn, capped at 30 prompt-harvests per session (overflow degrades to the
sweep's batch pass — freshness lost, nothing dropped within the corpus
retention window; un-ingested files older than the corpus GC's 30-day
retention are deleted, so a keyless brain — or one whose serve AND sweep
stayed away for a month — does eventually shed unbanked turns; serve restarts
reset the counter). Keyless brains skip extraction entirely (typed keyless skip)
and still get agent-authored remember writes; note that keyless dedup is
degraded (degraded_dedup) — near-duplicate phrasings may insert. See
spend-controls for the brain-wide
extraction switches.
Diagnostics
gbrain doctor → memory_writeback: resolved mode (+mode_valid), TTL
(+ttl_valid), both visibility postures (instruction template vs backstop),
brain audience + reasons, installed harness blocks (receipt vs live sentinel
probe, override-file detection, config-drift warning — and a receipt target
marked FAILED, e.g. a smoke-rollback strip that itself failed, stays a
standing warn until a bootstrap harness re-run or --remove converges it),
and validity-lapsed fact count. With writeback OFF the check still probes for
lingering instruction blocks and warns — the off switch is incomplete until a
bootstrap harness re-run converges them. It also reports 7-day counters —
remember outcomes over MCP (all callers — the
wire cannot distinguish ambient from explicit saves) and persisted backstop
results from the serve-side harvest receipts. Counters are local, append-only,
loss-tolerant observability — never a source of truth.
Enable / verify / disable
gbrain config set memory.auto_writeback salient
gbrain config set memory.auto_writeback_transient_ttl 3d # optional; default
gbrain bootstrap harness --harness codex --yes
grep -n "gbrain:ambient-writeback" "${CODEX_HOME:-$HOME/.codex}/AGENTS.md"
gbrain doctor | grep -A6 memory_writeback
# In a NEW agent session, say: "I prefer dark mode in every editor." Then:
gbrain recall --grep "dark mode"
gbrain sweep --once # drives the sweep backstop extraction immediately
# Off switch (anytime; converge harness blocks with another bootstrap run):
gbrain config set memory.auto_writeback off
gbrain bootstrap harness --yes