Files
gbrain/docs/guides/push-context.md
Garry TanandClaude Fable 5 c2cc8b0207 v0.45.1.0 feat(context): hook-lane feedback loop + cross-turn dedupe for the harness hooks (#4025)
* refactor(context): extract pure gateVolunteeredPointers (no behavior change)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(context): volunteer-shaped resolve IPC — channel/cwd/volunteer fields, handler-result widening, server-side gate + validated source + delivery-point logging; listener on both engines

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(cli): gbrain volunteer-hook — one-shot harness hook entry (IPC-first; Postgres-only fallback; startup-hook-exempt)

T0 evidence: a UserPromptSubmit hook's additionalContext is recorded in the
session transcript as a structured hook_additional_context attachment and
transcript_path is stable across turns — cross-turn dedupe reads ONLY those
attachments (never raw tail substrings). Live smoke: seeded brain + running
serve → pointer injected via IPC at 0.43s wall, turn 2 suppressed, one event
banked channel=claude-code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(recipes): claude-code-reflex + codex-reflex (experimental) hook adapters

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(doctor): volunteer_channels per-adapter visibility

Engine-aware sibling of retrieval_reflex_health: groups
context_volunteer_events by channel (7d) so operators can see which push
adapters fire; message separates installed-but-unregistered from
registered-but-quiet; pre-v117 tolerant; info-only status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(context): hook-lane feedback loop + cross-turn dedupe — converge cathedral-3 onto the #3975 harness hooks

The #3975 hook lane volunteered pages invisibly (nothing logged to
context_volunteer_events) and re-volunteered the same page every turn an
entity was mentioned (no priorContextText). This convergence ports the
cathedral-3 branch's two gap-fills into the blessed lane and retires its
parallel surface:

- transcripts: parseTranscript extracts injectedContextBlocks (structured
  hook_additional_context attachments — the T0-verified recorded shape of
  our own injections; never raw-turn substring matching)
- hook user-prompt: feeds those blocks as priorContextText (volunteer once
  per session, not once per mention) + sends a feedback-loop channel
  (--harness <claude-code|codex>, default claude-code); hook joins
  STARTUP_HOOK_SKIP_COMMANDS (per-prompt check-update spawn class, grep-pinned)
- turn-context: result exposes post-trim volunteered survivors
- resolve-ipc: additive TurnContextRequest.channel + onTurnContextDelivered
  seam (fires post-write only — abandoned blocks are never counted)
- serve: delivery callback logs volunteered pages + pointers per channel
- doctor: volunteer_channels check (per-channel 7d visibility; separates
  installed-but-unregistered from registered-but-quiet; pre-v117 tolerant)

Retired (never released): gbrain volunteer-hook, hook-adapters.ts,
claude-code-reflex + codex-reflex recipes, the volunteer-shaped resolve wire
extension. Kept: gateVolunteeredPointers extraction, VolunteerChannel
claude-code/codex values, the real hook-transcript fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: regenerate llms bundles for the hook-lane doc updates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage for the hook-lane delivery wiring

Extract logTurnContextDeliveryFireAndForget into volunteer-events.ts so the
shipped serve callback is unit-testable (channel fallback, sessionId clamp,
pointer+page rows in ONE synchronous registration — the double dynamic import
could register the pending write too late to survive process exit). Direct
units for isVolunteerChannel, explicit-channel logDeliveredReflexPointers,
and the pure gate (idempotency pin; stale parity claim in its doc corrected).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: pre-landing review fixes — 7-reviewer batch

Multi-confirmed: volunteer_channels now runs on the LOCAL doctor path too
(buildChecks; docs pointed there); transient DB errors no longer misreported
as pre-v117; priorContextText deduped + 32KB-capped hook-side. Red team:
delivery counts reconciled against the hook heartbeat (CAUTION when mostly
degraded — write-accept isn't injection); IPC clamp drops advisory
priorContextText BEFORE any window turn; foreign hook_additional_context
blocks (no gbrain marker) excluded from dedupe input; engine-aware quiet
guidance (Postgres brains aren't told to chase hook registration); 2MB dedupe
horizon documented. Security: wire channel claims restricted to harness
channels. DRY: VOLUNTEER_CHANNELS single source, shared reflexPointerRationale,
SESSION_ID_MAX_LEN shared with the op clamp; dead channel param dropped from
logDeliveredReflexPointers. Tests: budget-trim volunteered invariant under
real trim pressure, poll/ordering-based delivery-seam tests (no fixed sleeps),
doctor branch + engine-parity-row tests, wiring source pins, cap pins;
fixture pruned to load-bearing lines (environment inventory removed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: adversarial-review fixes — cross-model P1 + Claude adversarial batch

P1 (codex structured review + Claude adversarial, cross-model): the remote
run_doctor path now threads the caller's source scope into
volunteer_channels — a source-bound token can no longer see other sources'
activity counts/timestamps (must-never-violate source-isolation rule);
local trusted doctor stays brain-wide. Claude adversarial: IPC connection
handler gains a one-request re-entrancy guard (trailing bytes mid-await
double-processed the same line → duplicate handler work + duplicate
delivery logging); partial client-side trims now heartbeat as reason
'trimmed' (delivery-count drift visibility); heartbeat reconciliation uses
the same 7-day window as the counts + a 5-sample floor; one oversized
dedupe block no longer evicts all smaller ones (continue, not break);
quiet-channel guidance no longer claims old serves attribute hook traffic
to reflex (they log nothing); 'transient' claim softened to evidence;
marker-filter + unknown-channel-default comments state their honest limits.
Pins: scoped-query SQL/params, re-entrancy single-fire, updated wiring grep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump version and changelog (v0.45.1.0)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: restamp bootstrap runbook + regenerate vendored template repo for v0.45.1.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update project documentation for v0.45.1.0

Post-ship /document-release pass: fold the post-doc review fixes into the
reference docs (KEY_FILES: source-scoped remote volunteer_channels + heartbeat
reconciliation, 32KB-capped dedupe input, marker filter, IPC one-request
guard + clamp order), fix BRAINBENCH's now-stale memoryless-hook rationale,
and mark the doctor-check half of the push-context TODO shipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: cross-model doc-review fixes for v0.45.1.0

Codex doc review against the shipped diff: push-context channel table gains
the harness-hook row (three-channels claim was stale), the guide states the
PGLite-socket topology limit and the bounded dedupe horizon, delivery-point
claims are hedged to match the code (write-accept isn't proof of injection;
the doctor reconciles against the hook heartbeat, which can't inspect
registration), BRAINBENCH's future-tense hooks-PR sentence updated now the
hook has landed, and the adapter-flip + resolver-channel TODOS reflect the
current lane count. llms-full.txt regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(hook): document --harness in the user-prompt usage text

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 22:45:44 -07:00

6.2 KiB

Push-based context (#2095, v0.42.43.0)

Retrieval used to be pull-only: the agent had to know to ask before the brain contributed anything. Push-based context inverts that — the brain volunteers relevant pages from the recent conversation, confidence-gated so push noise never becomes worse than pull silence.

The push channels share one zero-LLM core (src/core/context/volunteer.ts):

Channel Surface When to use
reflex automatic, inside the context engine default-on for plugin hosts; nothing to call
op gbrain volunteer-context / MCP volunteer_context agents without the plugin; one call per turn
watch gbrain watch stream a transcript in, volunteered pages stream out
claude-code / codex gbrain hook user-prompt (registered by gbrain bootstrap) per-prompt injection inside a harness; see "Harness hooks" below

How it decides

  1. Extract entities across the last N turns (capitalized runs, @handles), merged with recency / frequency / user-role salience. Assistant-introduced entities and "what did she invest in?" follow-ups whose antecedent was named in the window now resolve.
  2. Resolve through the alias table, exact titles, and slug suffixes — each arm carries an honest confidence: alias 0.9, exact title 0.8, slug-suffix 0.6, +0.05 when mentioned in ≥2 turns or the newest turn.
  3. Gate at min_confidence (default 0.7 — slug-suffix matches need an explicit lower gate), suppress pages already surfaced (slug-presence only), cap at 3 pages (hard cap 5).

CLI

# one-shot: pipe recent turns (oldest → newest)
printf 'user: ask alice-example about the deal\nassistant: noted\nuser: what did she say?\n' \
  | gbrain volunteer-context

# streaming: volunteered pages print as the transcript flows
some-transcript-feed | gbrain watch --json

# the feedback loop: how often were volunteered pages actually opened?
gbrain volunteer-context --stats

Stats are approximate by design: "used" means pages.last_retrieved_at > volunteered_at — the 5-minute last-retrieved throttle causes false negatives and unrelated reads of the same page cause false positives. Use the per-arm precision to tune min_confidence, not as an exact metric.

PGLite + gbrain watch: PGLite is single-connection, and watch holds its connection for the whole session — a concurrent gbrain serve or any write path blocks until watch exits. On a PGLite brain, run watch in bursts (piped input exits at EOF) or use the ambient reflex channel instead, which routes through a running serve's resolve socket rather than taking the lock. Routing watch through that same socket is a filed follow-up (TODOS.md). Postgres brains are unaffected.

Harness hooks (the prompt-time channel)

gbrain bootstrap registers gbrain hook user-prompt as a Claude Code UserPromptSubmit hook: every prompt is assembled into a per-turn context block (reflex pointers + volunteered pages + hot facts) through a running serve's IPC socket and injected as additionalContext. Two properties make this channel production-grade rather than spammy-and-invisible:

  • Cross-turn dedupe. The hook reads its OWN previous injections back out of the session transcript (Claude Code records them as structured hook_additional_context attachments; only gbrain-marked blocks count) and passes them as prior context — so a page is volunteered once per session, not once per mention. The dedupe horizon is bounded (the recent transcript window, byte-capped), so a marathon session can eventually re-volunteer its oldest injections. The extraction is structural, never substring matching over raw turn text, so a short slug appearing in a tool payload can't over-suppress.
  • The feedback loop. The serve logs each DELIVERED block's volunteered pages and pointers to context_volunteer_events under the hook's channel (claude-code by default; a codex hook registration passes --harness codex). gbrain volunteer-context --stats then shows per-harness precision, and gbrain doctor's volunteer_channels check shows which channels actually fire, with guidance for the two quiet cases: "hook installed but never registered (restart the session)" and "registered but quiet". Logging happens at the delivery point only — a block abandoned before the serve responded is never counted — and because a delivered response still isn't proof of injection (the hook can trim or drop it client-side), the doctor check reconciles the counts against the hook's own heartbeat and cautions when they diverge.

The hook lane rides the PGLite serve's IPC socket: on a Postgres brain or a thin-client install the hook stays quiet by design (pull-mode retrieval covers those; extending the lane is a filed follow-up in TODOS.md).

Kill switch: GBRAIN_HOOKS=0. Install/uninstall: docs/guides/bootstrap.md.

Config

Key Default What it does
retrieval_reflex_window_turns 4 turns the ambient reflex extracts from; 1 = legacy current-turn-only (file/env plane: GBRAIN_RETRIEVAL_REFLEX_WINDOW_TURNS)
retrieval_reflex true the ambient channel's master switch
retrieval_reflex_max_pointers 3 pointer cap per turn

Per-call knobs: max_pages + min_confidence on both the op and gbrain watch (--max-pages / --min-confidence, plus --window-turns / --source on watch); on the op only: prior_context (text whose already-surfaced slugs are suppressed), session_id / turn attribution params (watch stamps its own per-session id and turn numbers in the feedback log), and days to size the --stats window.

Storage + privacy

Volunteered pages log to context_volunteer_events (migration v117): slug, arm, confidence, channel, optional session/turn — the rationale is a deterministic template string, never raw conversation text. Event writes are best-effort (fire-and-forget, drained at CLI exit) — the log is a tuning signal, not an audit trail. Rows are pruned after 90 days by the dream cycle's purge phase. Synopses always strip the takes/facts fences — the same strip get_page applies to untrusted callers, applied unconditionally here so private fence rows never reach a prompt regardless of caller trust.