feat: trusted recall and memory write surface (v1.35.0)#144
Conversation
Phase-0 artifacts for the trusted recall and memory write surface wave: design doc (consultant Variant 3, two shared kernels), implementation plan (10 tasks plus one test de-flake rider), and the variant audit trail with the raw consultant output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll clock The "temporalReplace logs a temporal-replace event" test read the log via readLogDay(vault, "2026-07-18"), a hardcoded day. temporalReplace logs under the UTC day of its wall-clock write instant (carried on the result as loggedAt), not the belief instant `at`, so the test only passed when run on 2026-07-18 and failed on every other date. Derive the expected log day from res.loggedAt.slice(0, 10) so the test is green on any wall-clock date. The sibling lifecycle tests (chain-decay, tombstone) inject an explicit `now` and are already deterministic, so they need no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`o2b doctor --readiness` runs three probes past the static invariant checks, each reporting exactly one of pass, fail-with-reason, or skipped-not-configured (never a silent pass) under a per-check timeout, and any fail yields a non-zero exit code: - llm_key: the model-inference credential resolves. The deterministic core has no in-repo chat-LLM client (write-time model steps are handed to the host as needs-llm-step envelopes), so the only model credential the system resolves itself is the embedding provider's API key; this probe resolves that. Skips for providers that need no key (local). - embedding_provider: the provider loads and its ping reports a model and a positive dimension. Skips when semantic search is disabled. - runtime_adapter_wiring: the adapter registry is populated and the canonical MCP payload builds and plans for every adapter. Without the flag, doctor output and exit behavior are byte-identical to before (readiness stays null; the new JSON key and text section are only added when the flag is present). A regression test proves the plain run is an exact prefix of the --readiness run and that --json omits the readiness key without the flag. Deviations from plan.md: the plan named src/core/doctor.ts as the host module, but that file's `doctor()` is a synchronous CheckResult[] aggregator with several callers (openclaw, mcp, main); adding async timeout-bounded probes there would ripple through all of them and risk the byte-identical contract. The probes live in a new sibling module src/core/doctor-readiness.ts and the `o2b doctor` CLI verb (src/cli/main.ts) mounts them, matching the codebase layout. A new src/core/install/adapters/all.ts barrel is the single source of the adapter-registration list (DRY); install.ts now imports it instead of repeating the nine side-effect imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
agent_id now joins session_id as a first-class correlation field on the token-impact (pack economics) and continuity identity, and both are guaranteed to survive an output-budget clip. - continuity/types.ts: named constants for the identity payload keys (session_id, agent_id, turn_id) and CLIP_PROTECTED_PAYLOAD_KEYS - the keys a clip must never drop. - continuity/store.ts: clipPayloadToBudget(payload, budgetChars) - a pure primitive that trims a payload to a character budget, dropping non-protected keys in original order only as far as needed to fit, and ALWAYS retaining the protected identity keys (even when they alone exceed the budget). No budget pressure (undefined/non-finite budget, or a payload already within budget) returns the SAME reference unchanged, so it is byte-identical. This is the shared clip contract C2's include_raw will reuse. - continuity/read-model.ts: lifts agent_id to a first-class agentId field beside sessionId/turnId, using the shared key constants. - token-impact.ts: emitTokenImpact and recordTokenImpactOutcome accept an agentId and write agent_id beside session_id (only when provided, so callers that omit it produce byte-identical records and dedup ids). listTokenImpact gains an opt-in payloadBudgetChars filter that clips each retained sample's payload through the protected-fields contract; a record that fits, or an omitted budget, is returned by the same reference. summarizeTokenImpact never clips (aggregation needs every field), so the roll-up is unaffected. A regression test drives a tiny budget and asserts session_id and agent_id survive while a non-protected field is dropped, and that an omitted budget leaves the payload byte-identical. Deviation from plan.md: the plan listed continuity/types.ts and store.ts plus token-impact.ts, which this matches; the "output-budget clip" it references had no existing routine, so the clip is introduced as the reusable clipPayloadToBudget primitive and consumed via listTokenImpact's new payloadBudgetChars filter (the token-impact clip path). The read-model lift is generic, so any continuity emitter that records agent_id gets the first-class field for free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The morning brief now renders its kept items as a single chronological, typed, age-labeled timeline instead of three per-kind sections, so an operator reads recent activity in the order it happened. - New src/core/brain/render/activity-line.ts: a small pure rendering helper with a FIXED structural marker vocabulary (preference -> pref, openQuestion -> open, note -> note) and a relative-age label derived from each item's stored timestamp. renderActivityLine renders one bullet; renderActivityTimeline sorts items most-recent-first (undated last, deterministic tie-break) and joins them. No natural-language classification anywhere - the marker comes from the item kind, never its words. - morning-brief.ts uses the helper: each kept preference, open question, and note becomes one timeline entry tagged by kind with its source timestamp; the rendered text is one "## Recent activity" feed. The public data (preferences, openQuestions, recentNotes arrays and their ageLabel fields) is unchanged, so brain_brief view=morning and the profile-doc/idea-discovery consumers that read those arrays are unaffected; only the rendered text becomes the timeline. The helper is deliberately free of vault/I-O concerns so the later knowledge-gap agenda (theme A) can reuse it without a shared fail-open/fail-closed guarantee leaking between surfaces. No deviations from plan.md; the new helper lives at the design's named path src/core/brain/render/activity-line.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add kernel 1, the deterministic retrieval rank-adjustment sink (src/core/search/rank-adjust.ts). Registered adjusters return a per-candidate verdict (keep, multiply, exclude-with-reason); the sink applies them, records exclusions with reasons, and re-sorts survivors. With no adjuster registered, and when every adjuster only keeps or multiplies by 1, it returns the exact input array, so the default retrieval path is byte-identical. It is mounted before the final slice in search.ts, so it covers BOTH the semantic and the pure-lexical paths (both flow through the same pre-slice pool) and a gate exclusion lets a deeper survivor backfill the window. Add the first consumer, the retrieval trust gate (src/core/brain/trust/retrieval-gate.ts). It contributes an exclude verdict for quarantined material classified DETERMINISTICALLY from three structural signals already in the codebase, reusing each module's exported identity: the self-approval guardrail state (BRAIN_PREFERENCE_STATUS.quarantine), the untrusted-source provenance marker (UNTRUSTED_SOURCE_TAG), and the entity-contamination marker (ENTITY_CONTAMINATION_FRONTMATTER_KEY, added to contamination.ts so that module stays the single boundary). It reads only controlled-vocabulary frontmatter keys, never note prose, so there is no natural-language word list anywhere. Excluded items are never silently dropped: every pack carries two compact-reference receipts consistent with the context-receipt model (retrieval_decision_trace counts and lists exclusions with reasons; memory_trust_assessment summarizes the pack trust posture with a reason histogram). Neither duplicates result payloads. The gate is opt-in via recall.retrievalTrustGateEnabled (env OPEN_SECOND_BRAIN_SEARCH_TRUST_GATE / config search_trust_gate_enabled), default off. Flag off keeps ranking and the outcome shape byte-identical; tests include the ablation (gate on vs off exact deltas) and a byte-identical regression. Deviations from plan.md paths (recorded per the codebase-wins rule): - Receipt builders live at src/core/brain/trust/retrieval-receipts.ts (co-located with the gate); plan.md named a src/core/brain/receipts directory that does not exist. - Receipts are surfaced as typed, opt-in compact-reference fields on SearchOutcome rather than emitted into the continuity context-receipt store, keeping the retrieval path self-contained and byte-identical when the gate is off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the second consumer of kernel 1: a relation-only supersede fade (t_c4a9cef8). The fade adjuster (src/core/search/result-filters.ts, co-located with attachTrustMetadata) fades any candidate whose surfaced typed relations mark it superseded, reusing hasSupersededRelation - the exact source of truth deriveTrust / attachTrustMetadata use for the inline trust.superseded display flag, refactored out of deriveTrust so display and ranking agree on what "superseded" means. The fade factor is the named constant SUPERSEDE_FADE_MULTIPLIER, placed beside the freshness multipliers in ranker.ts as the same family of bounded score-scaling constant. Because the adjuster runs through kernel 1 at the shared pre-slice pool, the fade covers BOTH the semantic and the pure-lexical paths. search.ts fetches the pool's typed relations once and registers the adjuster only when the fade is enabled. The fade is opt-in via recall.supersedeFadeEnabled (env OPEN_SECOND_BRAIN_SEARCH_SUPERSEDE_FADE / config search_supersede_fade_enabled), default off. A pool with no supersede relation ranks byte-identically whether the flag is on or off (both covered by regression tests). The existing superseded-non-tip tombstone drop is untouched: a tombstoned row is removed by the status filter before the fade runs, so the fade never resurrects it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…core Introduce kernel 2 (src/core/brain/write-batch.ts), the atomic multi-operation write core that generalises the validate-project-commit shape of runPinnedContextBatch: an ordered list of typed operations is validated and projected in memory first, then committed to disk as a unit; the first invalid operation aborts with a typed WriteBatchError naming the operation index and no disk write happens. Add the brain_update_note and brain_append_note MCP tools as single-operation batches over kernel 2. update_note merges frontmatter keys and/or replaces the body of an existing note; append_note appends to the body. Both reuse the exact create-note safety envelope (path traversal, the Brain machinery root, and vault-scope-excluded paths are refused) and the atomic-write pipeline, so a mid-write failure leaves the target byte-identical. A missing target is a typed error mapped to INVALID_PARAMS. The core takes typed operations only; the MCP layer maps request params onto them (no MCP shapes inside the core). The create-note safety envelope is extracted into resolveNoteTarget so the note write operations reuse it exactly rather than re-deriving it. Deviations from plan.md: kernel 2 lives at src/core/brain/write-batch.ts (the plan's suggested home, confirmed against the layout). The registry tool-count baseline moves 103 -> 105 and the frozen tool-name set gains the two new names (the one legitimate baseline change for added tools). A note target may appear at most once per batch: a second op on the same file is refused with a typed duplicate_target error rather than silently clobbering the first op's projection at commit time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the brain_write_batch MCP tool, the second consumer of the atomic write-batch core (kernel 2). One ordered list of typed operations is committed all-or-nothing: the whole batch is validated and projected in memory first, and the first invalid operation aborts with a typed error naming the operation index with no disk write - a later invalid op never lets an earlier one land. Extend kernel 2 with the full operation vocabulary: create note, update note (body and/or frontmatter), append note, apply evidence, and append log line. Each op maps onto an existing core writer (createNote, appendApplyEvidence, appendBrainNote) rather than reimplementing it; the apply_evidence and append_log_line ops are pre-validated (required fields, result enum, preference existence, non-empty text) so a malformed op aborts the batch before any commit. Single-operation batches produce the same result as the dedicated brain_create_note / brain_update_note / brain_append_note tools (parity is covered by tests). The MCP layer maps request params onto the core's typed operations and resolves the caller identity for the log-writing ops; no MCP shapes reach the core. The pre-existing brain_pinned_context batching behaviour is untouched. Deviations from plan.md: the tool lives in a new module src/mcp/brain/write-batch-tools.ts (registered through the brain-tools aggregator) rather than being folded into notes-tools.ts. The registry tool-count baseline moves 105 -> 106 and the frozen tool-name set gains brain_write_batch (the one legitimate baseline change for an added tool). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task C2 (t_ac1d36ea). An optional include_raw flag on session recall search inlines the original raw captures beside each derived record and stamps an extracted boolean discriminator on every returned hit (true for a distilled summary node, false for a raw session turn). The raw captures reuse the existing collectRawRecords/expandedRawTurn machinery already behind brain_session_expand rather than duplicating it: a summary node carries the raw turns it was distilled from, a raw turn carries itself. Raw payloads respect the batch-1 clip contract: an optional raw_budget_chars clips each inlined raw payload through clipPayloadToBudget, so the protected identity keys (session_id, agent_id) always survive while bulky fields such as the turn text drop first. With the flag omitted the response is byte-identical to before (neither extracted nor raw appears), covered by a regression test on both the core and MCP surfaces. The MCP surface is the existing brain_session_grep tool; no new tool is added, so registry tool counts are unchanged. The two new schema properties stay within the 300/160 description limits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ubmit Task A2 (t_2ce46130). Adds an opt-in UserPromptSubmit hook that, on each user prompt, relevance-recalls a small bounded brief of vault notes and injects it as additionalContext. Design: - Opt-in via one flag (OPEN_SECOND_BRAIN_RECALL_INJECT_ENABLED / recall_inject_enabled), default OFF. Flag off is a byte-identical no-op (checked before any payload read or output), covered by a regression test. - Pure decision core (src/core/brain/recall-inject.ts) with hard caps as named constants: max notes (4), max chars (900), a fixed time budget (2500ms), and a normalized confidence floor (0.35). It abstains on an empty prompt, no matches, or a top match below the floor. Any retriever error or a time-budget overrun is caught and surfaced as an explicit error decision, never a silent fallback. - No new retriever: defaultRecallRetriever adapts the existing cross-vault search (which already consults every RecallSource); the brief's orientation line reuses deriveRecallHint over RecallHintInput. - Every decision (inject, abstain, error) writes exactly one structured, payload-safe audit line (counts, scores, reason - never the prompt text or recalled content). - The hook process is fail-open for the session: it arms the shared process ceiling and exits 0 on every path. The hook is registered in hooks.json following the existing version-current command shape and is distributed as a plain hooks/*.ts file resolved by the o2b-hook wrapper (no manifest change needed). HookPayloadBase gains the optional UserPromptSubmit `prompt` field. Deviation: the decision core takes an injected retriever rather than calling search directly, so the caps/floor/time-budget/brief logic is unit-testable without a vault; the hook wires the default retriever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-close
Task A3 (t_67d38036). Closes the loop on recurring recall gaps, opt-in and
default OFF.
Core (src/core/brain/gaps/gap-loop.ts), each part independently testable:
- detectRecurringGaps aggregates the EXISTING recall-telemetry gap_counts
aggregate, keeping gaps at or above a tunable threshold (named-constant
default, env-overridable). Gap topics are telemetry keys verbatim, never
words classified from prose.
- promoteGapsToTasks writes ONE durable gap-task note per recurring gap
under Brain/gap-tasks/, deduped on a stable hash gap key via an exclusive
create so re-promotion never collides or forks. Plain note files - they
never touch the Hermes kanban board.
- renderGapAgenda renders open gap tasks as a compact session-start agenda
through the shared batch-1 activity helper (each task is an openQuestion
item carrying the fixed structural marker and a relative-age label).
- autoCloseRecalledGaps flips a task's frontmatter status to closed once its
topic recalls at or above the confidence floor (mirroring the dream
freshness auto-resolve precedent: a recorded status flip, never silent).
It excludes the gap-task notes themselves from the recall, so a task can
only close on genuine vault coverage elsewhere - never self-close on the
placeholder that carries its own topic verbatim.
Wiring: two opt-in hooks gated by one flag (OPEN_SECOND_BRAIN_GAP_LOOP_
ENABLED / gap_loop_enabled), default OFF - flag off is a byte-identical
no-op with no writes (regression tests). gap-agenda (SessionStart) injects
the agenda; gap-promote (SessionEnd) promotes recurring gaps and auto-closes
recalled ones, writing a best-effort audit line. Both follow the existing
version-current hooks.json command shape and are distributed as plain
hooks/*.ts files.
Deviations:
- Auto-close runs in the gated session-end hook rather than inside the
central dream planner. It faithfully mirrors the freshness auto-resolve
precedent (recorded frontmatter status flip) while keeping all gap-loop
writes on one opt-in surface, avoiding threading a retriever and a new
gated branch through the deterministic dream pass. The dream module is
untouched.
- doctor conceptGaps is left as a documented future secondary input: it
requires a full (heavy) doctor report, and the telemetry gap_counts is the
sufficient primary source; the plan marked it optional ("if it fits").
- grok-asset.ts is updated to mirror the new session hooks AND the A2
recall-inject hook (which the A2 commit registered only in hooks.json)
into the grok/Codex distribution asset, keeping the cross-runtime hook
wiring consistent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace an em-dash with a hyphen in a session-recall test describe string to match the branch prose convention (no em-dashes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tent The UserPromptSubmit recall hook injected a brief built from vault note titles verbatim (only length-trimmed) as additionalContext. Titles are untrusted vault content, so a title carrying newlines, zero-width or bidi characters, control characters, or a forged untrusted_source delimiter could break the brief's line structure or smuggle encoded directives past review. Reuse the existing untrusted-source machinery rather than a parallel neutralizer: - Add fenceUntrustedContent, a path-less sibling of wrapUntrustedSource for aggregate content assembled from many notes. Same delimiter tag, same body neutralization; it carries an origin label instead of a per-file path/sha256. - Neutralize every title before it reaches the recall-hint line or a note bullet: strip control/bidi/zero-width characters and escape delimiters via neutralizeUntrustedText, then collapse surviving newline/tab runs so a title stays on one line. - Fence the finished brief as untrusted content and charge the fence delimiter overhead against the existing max-chars cap, so the whole fenced brief stays within the bound the audit relies on. The audit line still records only counts, scores, and reason; behavior with the flag off is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… to 1.35.0 CHANGELOG entry and link reference for 1.35.0, README What-is-new rewrite, cli-reference section for the new doctor readiness flag, timeline render, hook flags and search trust switches, and mcp.md bullets for the three note-write tools and the include_raw params. Version propagated with scripts/sync-version.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughChangesOpen Second Brain 1.35.0 adds opt-in bounded recall injection, retrieval trust gating with receipts, supersede fading, durable recall-gap tasks, raw session recall, protected continuity payload clipping, atomic MCP write tools, and Trusted recall and memory write surface
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
tests/cli/cli.test.ts (1)
215-253: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNo CLI-level test for the readiness-failure exit code path.
These tests cover pass/skip outcomes only; none forces a probe
fail(e.g., semantic enabled with a missing key) to verifydoctor --readinessactually exits1end-to-end. Since the readiness flag's stated purpose is CI gating, that wiring is worth a directed test, unless already covered at the unit level intests/core/doctor-readiness.test.ts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/cli/cli.test.ts` around lines 215 - 253, Add a CLI-level test alongside the existing readiness tests that configures the readiness environment to produce a probe failure, such as enabling semantic checks without providing the required key, then runs doctor with --readiness and asserts the command returns exit code 1. Keep the existing pass/skip assertions and verify the output identifies the failed readiness probe.src/core/doctor-readiness.ts (1)
100-127: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoffTimeout wrapper doesn't cancel the underlying call.
withReadinessTimeoutreports a timeout/failure to the caller, butfn()(e.g.,provider.ping()inprobeEmbeddingProvider) keeps running in the background with noAbortSignal. A genuinely hung network call stays outstanding after the probe is markedfail, which is a soft rather than hard timeout. Given the CLI is short-lived, impact is limited, but this diverges slightly from the "never blocking the operator" framing in the module doc comment (the CLI output is unblocked, but the resource isn't released).Wiring real cancellation would need
provider.ping()to accept anAbortSignal, which is a larger, cross-file change; flagging for awareness rather than requiring it now.Also applies to: 190-196
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/doctor-readiness.ts` around lines 100 - 127, No code change is required for withReadinessTimeout; retain the current soft-timeout behavior and do not introduce cross-file AbortSignal plumbing. Record the cancellation limitation for future work around withReadinessTimeout and its probeEmbeddingProvider callers.tests/mcp/brain-update-append-note.test.ts (1)
70-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the specific error code, not just
instanceof MCPError.The file header documents that missing/invalid targets are "refused ... with a typed error mapped to INVALID_PARAMS," but none of these tests (Lines 70-75, 77-80, 82-87, 107-111, 113-117) actually check the error code/data — only that some
MCPErrorwas thrown.tests/mcp/brain-write-batch.test.tsalready shows the pattern to mirror (asserting.dataagainst{ index, code }). Without this, a regression that maps these failures to the wrong error code would pass silently.♻️ Example tightening for one case
- test("a missing target is rejected with INVALID_PARAMS and writes nothing", async () => { - await expect(updateTool.handler(ctx, { path: "Notes/Ghost.md", content: "x" })).rejects.toThrow( - MCPError, - ); - expect(existsSync(join(vault, "Notes/Ghost.md"))).toBe(false); - }); + test("a missing target is rejected with INVALID_PARAMS and writes nothing", async () => { + let thrown: unknown; + try { + await updateTool.handler(ctx, { path: "Notes/Ghost.md", content: "x" }); + } catch (err) { + thrown = err; + } + expect(thrown).toBeInstanceOf(MCPError); + expect((thrown as MCPError).code).toBe("INVALID_PARAMS"); + expect(existsSync(join(vault, "Notes/Ghost.md"))).toBe(false); + });Also applies to: 107-117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/mcp/brain-update-append-note.test.ts` around lines 70 - 87, Strengthen the rejection assertions in the affected tests for missing targets, missing frontmatter/content, and path traversal, including the cases around the later lines, to verify the thrown MCPError carries the expected INVALID_PARAMS code/data rather than only checking its type. Mirror the assertion pattern used in tests/mcp/brain-write-batch.test.ts while preserving the existing filesystem side-effect checks.src/core/brain/trust/retrieval-gate.ts (1)
80-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse
hasEntityContaminationMarkerinstead of reimplementing the truthy check.
contamination.tsexportshasEntityContaminationMarkerspecifically so consumers like this gate share one definition of "truthy" for the marker. This file only imports the key constant and reimplements the equality check locally viatruthy(), so the two definitions can silently drift.♻️ Proposed fix
-import { ENTITY_CONTAMINATION_FRONTMATTER_KEY } from "../truth/contamination.ts"; +import { hasEntityContaminationMarker } from "../truth/contamination.ts";- if (truthy(meta[ENTITY_CONTAMINATION_FRONTMATTER_KEY])) { + if (hasEntityContaminationMarker(meta)) {Based on the coding intent stated in
contamination.ts: "so contamination stays a single boundary: the detector here and any read-time trust consumer (the retrieval trust gate) agree on the one structural key rather than each hardcoding it."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/brain/trust/retrieval-gate.ts` around lines 80 - 82, Update the retrieval trust gate around the contamination check to call the exported hasEntityContaminationMarker helper from contamination.ts instead of directly reading ENTITY_CONTAMINATION_FRONTMATTER_KEY with truthy(). Remove the now-unneeded local key import or truthy-based check, while preserving the existing reason push behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude-plugin/plugin.json:
- Line 3: The mirrored version files were edited directly instead of being
synchronized from the canonical package.json version. Revert the manual edits in
.claude-plugin/plugin.json (line 3), .codex-plugin/plugin.json (line 3),
plugins/codex/.codex-plugin/plugin.json (line 3), plugins/hermes/plugin.yaml
(line 2), and pyproject.toml (line 13); update the version only in package.json,
then run scripts/sync-version.ts via bun run to regenerate all mirrored
manifests.
In `@src/core/brain/morning-brief.ts`:
- Around line 206-215: The open-question timeline entry in the OQ_PREFIX branch
should reuse the budget-trimmed kept.text instead of reconstructing the full
topic/domain string. Update the timeline push near keptQuestions.push while
preserving the existing timestamp and metadata behavior, so rendered text
matches totalChars.
In `@src/core/brain/recall-inject.ts`:
- Around line 164-179: Update the brief construction around the initial `lines`
header and `innerBudget` calculation so the returned fenced brief never exceeds
the supplied maxChars, including when the header alone is too large. Preserve
the existing hint and note inclusion behavior for budgets that can accommodate
the header, and ensure the result remains valid when the budget is smaller than
the header.
- Around line 193-203: Update renderNoteLine to pass note.path through the
existing single-line neutralization helper, neutralizeTitle, before
interpolating it into the pointer; preserve the current path and line-number
formatting while ensuring embedded newlines or control characters cannot break
the one-note-per-line output.
In `@src/core/brain/write-batch.ts`:
- Around line 165-189: Add a shared maximum batch-size limit and enforce it in
applyWriteBatch alongside the existing non-empty validation, rejecting
operations that exceed the limit before projection or commit. Update the
brain_write_batch inputSchema operations property with the same maxItems value
so oversized requests are rejected during schema validation.
In `@src/mcp/brain/notes-tools.ts`:
- Around line 59-74: Update writeBatchErrorToMcp so non-WriteBatchError values
are converted into a structured MCPError using INTERNAL_ERROR, matching the
fallback behavior of toolBrainCreateNote. Preserve the existing INVALID_PARAMS
mapping and details for WriteBatchError instances, while ensuring unexpected
commit-phase failures return an MCPError with a descriptive message and
underlying error information.
---
Nitpick comments:
In `@src/core/brain/trust/retrieval-gate.ts`:
- Around line 80-82: Update the retrieval trust gate around the contamination
check to call the exported hasEntityContaminationMarker helper from
contamination.ts instead of directly reading
ENTITY_CONTAMINATION_FRONTMATTER_KEY with truthy(). Remove the now-unneeded
local key import or truthy-based check, while preserving the existing reason
push behavior.
In `@src/core/doctor-readiness.ts`:
- Around line 100-127: No code change is required for withReadinessTimeout;
retain the current soft-timeout behavior and do not introduce cross-file
AbortSignal plumbing. Record the cancellation limitation for future work around
withReadinessTimeout and its probeEmbeddingProvider callers.
In `@tests/cli/cli.test.ts`:
- Around line 215-253: Add a CLI-level test alongside the existing readiness
tests that configures the readiness environment to produce a probe failure, such
as enabling semantic checks without providing the required key, then runs doctor
with --readiness and asserts the command returns exit code 1. Keep the existing
pass/skip assertions and verify the output identifies the failed readiness
probe.
In `@tests/mcp/brain-update-append-note.test.ts`:
- Around line 70-87: Strengthen the rejection assertions in the affected tests
for missing targets, missing frontmatter/content, and path traversal, including
the cases around the later lines, to verify the thrown MCPError carries the
expected INVALID_PARAMS code/data rather than only checking its type. Mirror the
assertion pattern used in tests/mcp/brain-write-batch.test.ts while preserving
the existing filesystem side-effect checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4c9c1b4c-8acf-4b55-95ae-dd0cd2c57cd0
📒 Files selected for processing (84)
.claude-plugin/plugin.json.codex-plugin/plugin.jsonCHANGELOG.mdREADME.mddocs/brainstorm/recall-trust-and-write-surface/cli-output/claude.mddocs/brainstorm/recall-trust-and-write-surface/cli-output/prompt.mddocs/brainstorm/recall-trust-and-write-surface/design.mddocs/brainstorm/recall-trust-and-write-surface/plan.mddocs/brainstorm/recall-trust-and-write-surface/variants.mddocs/cli-reference.mddocs/mcp.mdhooks/gap-agenda.tshooks/gap-promote.tshooks/hooks.jsonhooks/lib/stdin.tshooks/recall-inject.tsopenclaw.plugin.jsonpackage.jsonplugin.yamlplugins/codex/.codex-plugin/plugin.jsonplugins/hermes/plugin.yamlpyproject.tomlsrc/cli/install/install.tssrc/cli/main.tssrc/core/brain/continuity/read-model.tssrc/core/brain/continuity/store.tssrc/core/brain/continuity/types.tssrc/core/brain/gaps/gap-loop.tssrc/core/brain/morning-brief.tssrc/core/brain/notes/create-note.tssrc/core/brain/paths.tssrc/core/brain/recall-inject.tssrc/core/brain/render/activity-line.tssrc/core/brain/session-recall.tssrc/core/brain/token-impact.tssrc/core/brain/trust/retrieval-gate.tssrc/core/brain/trust/retrieval-receipts.tssrc/core/brain/truth/contamination.tssrc/core/brain/untrusted-source.tssrc/core/brain/write-batch.tssrc/core/config.tssrc/core/doctor-readiness.tssrc/core/install/adapters/all.tssrc/core/install/grok-asset.tssrc/core/search/enrich.tssrc/core/search/index.tssrc/core/search/rank-adjust.tssrc/core/search/ranker.tssrc/core/search/result-filters.tssrc/core/search/search.tssrc/core/search/types.tssrc/mcp/brain-tools.tssrc/mcp/brain/notes-tools.tssrc/mcp/brain/recall-tools.tssrc/mcp/brain/write-batch-tools.tssrc/mcp/registry-guard.tstests/cli/cli.test.tstests/core/brain/continuity-read-model.test.tstests/core/brain/gap-loop.test.tstests/core/brain/lifecycle/temporal-replace.test.tstests/core/brain/recall-inject.test.tstests/core/brain/render/activity-line.test.tstests/core/brain/session-recall.test.tstests/core/brain/token-impact-clip.test.tstests/core/brain/trust/retrieval-gate.test.tstests/core/brain/trust/retrieval-receipts.test.tstests/core/brain/untrusted-source.test.tstests/core/brain/write-batch-vocab.test.tstests/core/brain/write-batch.test.tstests/core/doctor-readiness.test.tstests/core/search/rank-adjust.test.tstests/core/search/retrieval-trust-gate.test.tstests/core/search/store.test.tstests/core/search/store.vec.test.tstests/core/search/supersede-fade.test.tstests/helpers/search-fixtures.tstests/hooks/gap-loop.test.tstests/hooks/recall-inject.test.tstests/mcp/brain-tools-parity.test.tstests/mcp/brain-update-append-note.test.tstests/mcp/brain-write-batch.test.tstests/mcp/mcp.test.tstests/mcp/removed-tools.test.tstests/mcp/session-recall-tool.test.ts
|
CodeRabbit triage: 8 findings fixed in the follow-up commit (morning-brief open-question budget, recall-inject header cap and path neutralization, write-batch size bound with matching maxItems, MCPError wrapping for unexpected write errors, CLI-level readiness fail exit test, tightened update/append error-code assertions, shared contamination marker helper). Two deliberate skips: the mirrored-version Critical is answered inline (bump was done via sync-version.ts, CI sync check green), and the readiness soft-timeout note is acknowledged as a recorded limitation, matching the reviewer's own no-change-required guidance. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: trusted recall and memory write surface (v1.35.0)
Summary
Recall now reaches the agent at the moment it matters and can be trusted when it arrives: an opt-in bounded recall brief lands on every prompt, retrieval zero-ranks quarantined material and hands back receipts explaining every exclusion, a supersede relation on a successor fades the superseded note without editing it, and MCP agents get the full atomic note lifecycle - update, append, and all-or-nothing mixed batches - alongside a typed session-start timeline, a self-directing knowledge-gap loop, clip-proof pack identity, and fail-fast doctor readiness probes.
Why this matters
flowchart LR subgraph Recall["Recall at the right time"] P["User prompt"] -->|"opt-in, capped, audited"| B["Bounded recall brief<br/>(fenced untrusted)"] S["Session start"] --> T["Typed age-labeled timeline"] S --> G["Gap agenda"] E["Session end"] -->|"recurring gaps"| GT["Durable gap tasks<br/>auto-close on resolution"] end subgraph Trust["Trust in what returns"] Q["Search pool"] --> K1["Kernel 1: rank adjust"] K1 -->|"zero-rank + receipts"| TG["Trust gate"] K1 -->|"fade multiplier"| SF["Supersede fade"] end subgraph Write["Atomic write lifecycle"] U["brain_update_note"] --> K2["Kernel 2: write batch core"] A["brain_append_note"] --> K2 W["brain_write_batch"] --> K2 K2 -->|"all-or-nothing"| V["Vault"] end Trust --> RecallConcrete benefits
retrieval_decision_traceinstead of vanishing silently.supersedesrelation fades the old note in both semantic and lexical search.o2b doctor --readinessanswers the Day-0 question "is my wiring actually usable" with three functional probes, per-check timeouts, and a real exit code.What ships
hooks/recall-inject.ts,src/core/brain/recall-inject.tssrc/core/search/rank-adjust.ts,src/core/brain/trust/retrieval-gate.ts,retrieval-receipts.tssrc/core/search/result-filters.ts,ranker.tssrc/core/brain/write-batch.ts,src/mcp/brain/notes-tools.tssrc/mcp/brain/write-batch-tools.tssrc/core/brain/session-recall.ts,src/mcp/brain/recall-tools.tssrc/core/brain/render/activity-line.ts,morning-brief.tssrc/core/brain/gaps/gap-loop.ts,hooks/gap-promote.ts,hooks/gap-agenda.tssrc/core/brain/continuity/,token-impact.tssrc/core/doctor-readiness.tstests/core/brain/lifecycle/temporal-replace.test.tsMCP surface: 103 -> 106 tools. Every new surface is opt-in and byte-identical when its flag or param is omitted, each with a regression test.
Test plan
bun run fmtcleanbun run lintexactly 134 warnings / 0 errors (baseline)bun run typecheckcleanbun testfull foreground suite green (6496+ pass / 0 fail)bun run scripts/sync-version.ts --checkpassesfix(hooks): neutralize and fence recall-inject brief as untrusted contentSummary by CodeRabbit
doctor --readinessreadiness probes with detailed pass/fail reporting.