Skip to content

AI second brain Phase 2: retriever seam in AiSurfaceService + brain helpers (0211) - #230

Merged
crs48 merged 2 commits into
mainfrom
claude/ai-second-brain-phase2
Jun 22, 2026
Merged

AI second brain Phase 2: retriever seam in AiSurfaceService + brain helpers (0211)#230
crs48 merged 2 commits into
mainfrom
claude/ai-second-brain-phase2

Conversation

@crs48

@crs48 crs48 commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Phase 2 of exploration 0211, building on the engine merged in #228. This connects the brain engine to the agent's context surface and fills the remaining package-level gaps so adoption is a few lines.

What ships

AiSurfaceService retriever seam (packages/plugins/src/ai-surface/service.ts):

  • New optional retrieveContext on AiSurfaceServiceConfig. When present, the query path of createContextPack uses the injected graph-aware retriever (hybrid vector+keyword entry search + bounded graph expansion, budgeted) instead of the built-in linear keyword scan. Absent it, behavior is unchanged.
  • The app injects @xnetjs/brain's retrieve (mapping items → { nodeId, pathLabel }). Decoupled — no plugins → brain import.
  • 2 new tests prove the seam: the retriever's results win the query path, and the keyword fallback still works when no retriever is configured.

@xnetjs/brain helpers (make the engine batteries-included; +17 tests, 81 total):

  • relationFieldsResolver / schemaRelationFields — derive the relationFieldsOf resolver straight off compiled schemas, so graph-walk works with the built-in registry without a hand-written map.
  • applyMemoryOp / rememberFact — execute consolidateMemory's ADD/UPDATE/DELETE/NOOP decisions as governed MemoryItem node mutations (closes the memory write-loop).
  • saveVectorTier / loadVectorTier — persist the semantic index through a blob store (@xnetjs/storage); a cold/corrupt tier reports false so the caller rebuilds lazily via reindexAll (the 0204 OPFS-eviction concern).
  • All structural/injected — the brain still carries no hard @xnetjs/data dependency.

Checks off (in the 0211 doc)

  • Phase 2: Wire retrieve() into AiSurfaceService ✓ (the seam)
  • Phase 1: Persist the vector tier via @xnetjs/storage; rebuild lazily

Still deferred (next phase)

Live injection in apps/web (constructing a SemanticSearch + brain, embedding backfill + tier persistence on boot) — deserves its own perf-aware PR per [0204]; an xnet_graph_expand MCP tool; the data-bridge query-path placement of the locality planner; WorkingSetPrewarm consumption; and a managed /ai/embed hub route.

Verification

  • @xnetjs/brain: 81 tests green; ai-surface.test.ts: 26 green (incl. the 2 new).
  • tsc --noEmit clean for @xnetjs/brain + @xnetjs/plugins (via turbo).
  • eslint + prettier clean; fallow audit (with coverage): "✓ No issues in 9 changed files" (the flagged functions are pre-existing/inherited).

No user-visible change yet (internal plumbing toward the capability already announced in #228's changelog), so this carries the skip-changelog label.

🤖 Generated with Claude Code

xNet Test and others added 2 commits June 21, 2026 19:21
…tier persistence

- relationFieldsResolver/schemaRelationFields (schema.ts): derive the brain's
  relationFieldsOf straight off compiled schemas, so graph-walk works with the
  built-in registry without a hand-written map.
- applyMemoryOp/rememberFact (memory-apply.ts): execute consolidateMemory's
  ADD/UPDATE/DELETE/NOOP decisions as governed MemoryItem node mutations.
- saveVectorTier/loadVectorTier (persist.ts): persist the semantic index through
  a blob store; cold/corrupt tier reports false so the caller backfills lazily.
- All structural/injected (no @xnetjs/data import); +17 tests (81 total).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0211 Phase 2)

Adds an optional retrieveContext to AiSurfaceServiceConfig that drives the query
path of createContextPack — when present, context packs come from the injected
hybrid GraphRAG retriever (@xnetjs/brain) instead of the built-in linear keyword
scan; absent it, behavior is unchanged. Backward-compatible, +2 tests. Checks off
the AiSurfaceService-wiring and vector-tier-persistence items in exploration 0211
(live apps/web injection remains the next step).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-230 June 22, 2026 02:22 — with GitHub Actions Inactive
@crs48 crs48 added the skip-changelog Exclude this PR from the changelog label Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

✓ Changelog fragment found — thanks!

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #230.

github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@crs48
crs48 merged commit 4f059cc into main Jun 22, 2026
12 of 13 checks passed
@crs48
crs48 deleted the claude/ai-second-brain-phase2 branch June 22, 2026 02:28
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
crs48 added a commit that referenced this pull request Jun 22, 2026
…(0211) (#232)

Phase 3 of exploration
[0211](docs/explorations/0211_[_]_AI_SECOND_BRAIN_GRAPHRAG_MEMORY_AND_TIERING.md)
— makes the brain **live in the app**, building on the engine
([#228](#228)) and the
`AiSurfaceService` seam
([#230](#230)).

## What ships

The AI chat's context pack now comes from `@xnetjs/brain`'s `retrieve()`
instead of a flat keyword scan.

- **`apps/web/.../ai-graph-retriever.ts`** (new, 6 tests) —
`createGraphContextRetriever` builds an `AiContextRetriever` over the
local NodeStore: keyword entry search → **bounded expansion along typed
relations** (resolved from the schema registry) → token budget → each
hit carries a **readable provenance path**.
- **`AiChatPanel`** injects it: `createAiSurfaceService({ store,
schemas, retrieveContext: createGraphContextRetriever(store) })`. So
`createContextPack({ query })` (already called per message) now walks
the graph.
- **Exports**: `AiContextRetriever` / `AiRetrievedNode` from the
`@xnetjs/plugins` barrels; `@xnetjs/brain` added to `apps/web` deps.

### Deliberately model-free
Entry search is **keyword-only** — no embedding-model download, no added
bundle weight or startup cost (the
[0204](docs/explorations/0204_[x]_FAST_LOCAL_FIRST_COLD_START_AND_CACHE_HYDRATION.md)
cold-start constraint). The value-add here is the **graph-walk + budget
+ provenance**. The vector tier can swap in behind the same seam later
without touching this call site.

## Verification
- `@xnetjs/brain` exercises (browser runtime, via Vite module graph):
`retrieve('inventory')` returned the entry node **plus** its 1-hop
neighbor with path `"My inventory→ (items) Sword of testing"`.
- App boots clean — **no console errors** with the new import in the
bundle.
- 6 retriever tests + 26 `ai-surface` tests green; `tsc --noEmit` clean
for `@xnetjs/plugins` + `xnet-web` (40 turbo tasks); eslint + prettier
clean.

## Still deferred (future enhancements, doc stays `[_]`)
Swap the **vector tier** in behind the seam (wake `@xnetjs/vectors`
in-app + backfill/persist), an `xnet_graph_expand` MCP tool, the
`data-bridge` query-path placement of the locality planner,
`WorkingSetPrewarm` consumption, and a managed `/ai/embed` hub route.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48 added a commit that referenced this pull request Jun 22, 2026
Follow-up to exploration
[0211](docs/explorations/0211_[_]_AI_SECOND_BRAIN_GRAPHRAG_MEMORY_AND_TIERING.md)
(after [#228](#228) /
[#230](#230) /
[#232](#232)) — ships the
**just-in-time expansion** half of the anti-overwhelm design.

## What ships

A built-in `AiSurfaceService` tool, **`xnet_graph_expand`**, that walks
typed relation edges out from a node to its connected neighbors, bounded
by `hops` (1–2) and a result `limit`.

- Resolves relation fields from the schema registry; returns each
neighbor with the **relation + direction** it came through (traceable
answers).
- **Auto-surfaced to MCP/CLI agents** via `getTools()` — and correctly
**deferred** (JIT-discovered via the Tool Search Tool), not eagerly
loaded into context, which is exactly the anti-overwhelm posture: the
agent discovers and calls it only when it needs a node's connections.

This pairs directly with the graph-aware retrieval shipped in #232:
`createContextPack` hands the agent a budgeted slice plus expandable
node ids; `xnet_graph_expand` is how it pulls a specific node's
connections on demand instead of over-fetching the whole graph.

## Verification
- 4 new tests (30 in `ai-surface.test.ts`): outbound walk, not-found,
limit, tool-list presence.
- `tsc --noEmit` clean for `@xnetjs/plugins` (turbo); eslint + prettier
clean.
- `fallow audit` (with coverage): *"✓ No issues in 2 changed files"* —
the new functions add zero enforced complexity findings.
- No new dependencies (uses the service's existing `store` + `schemas`).

## Still deferred (0211 doc stays `[_]`)
The in-app **vector tier** (waking `@xnetjs/vectors` behind the
retriever seam — a perf-sensitive change better suited to its own PR,
possibly via a managed server-side embedding route given browser cost),
the `data-bridge` query-path placement of the locality planner,
`WorkingSetPrewarm` consumption, and a managed `/ai/embed` hub route.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48 added a commit that referenced this pull request Jun 22, 2026
…ff) (#236)

The final piece of exploration
[0211](docs/explorations/0211_[x]_AI_SECOND_BRAIN_GRAPHRAG_MEMORY_AND_TIERING.md)
— wakes the dormant `@xnetjs/vectors` engine behind the retriever seam,
making the **semantic half** of the AI second brain live. After #228
(engine), #230 (seam), #232 (graph-aware retrieval live), and #233
(`xnet_graph_expand`), this flips 0211 to **`[x]`**.

## What ships

**`ai-vector-search.ts` — `createVectorEntrySearch`** (opt-in, lazy,
fallback-safe):
- The heavy `@xenova` model + `usearch` load via a **dynamic import only
on the first search after opt-in** — zero boot/bundle cost when off (the
[0204](docs/explorations/0204_[x]_FAST_LOCAL_FIRST_COLD_START_AND_CACHE_HYDRATION.md)
constraint).
- Once warm it **RRF-fuses vector + keyword**; until warm and on **any**
failure it falls back to keyword — so enabling it can only ever match or
improve results, **never break or slow** the chat.
- **`ai-vector-storage.ts`** — IndexedDB `BlobStore` so the index
**restores across sessions** instead of re-embedding the graph each
time.
- **`AiChatPanel`** — an opt-in toggle (off by default); the flag is
read via a ref so toggling **never tears down the AI runtime**
mid-conversation.

## Adversarial review caught 3 real bugs (fixed here)
A 22-agent review (18 findings → 15 false positives killed by
verification → 3 confirmed) surfaced:
1. `loadVectorTier` returned `false` on a *partial* restore but left the
index half-populated → now **clears on failure** so "false ⇒ cold" is
honest.
2. Persistence was never wired (no `storage`) → re-embedded every
session and the realm-fix was dead in prod → now **wired via
IndexedDB**.
3. Toggling the flag rebuilt `surface` → **reset the active thread**
mid-conversation → now decoupled via a ref.
Plus a realm-robustness fix in `brain/persist.ts`: a plain `instanceof
Uint8Array` mangles bytes across the dynamic-import / IndexedDB
structured-clone boundary; the `Symbol.toStringTag` brand holds.

## Verification
- **End-to-end in the real browser**: warm → hybrid hit (`source:
"hybrid"`); IndexedDB persist → restore (no re-embed) → query found the
node. The cross-realm persist fix exercised under real production
conditions.
- 17 unit tests (vector tier lazy/fallback/persist + the persist
clear-on-failure regression + graph retriever); `tsc` clean for
`@xnetjs/brain` + `xnet-web`; eslint + prettier clean; `fallow audit` —
*"✓ No issues in 10 changed files."*

## Justified defers (documented in the 0211 doc; none block the core)
- **Managed `/ai/embed` route** — the server-side embedding upgrade
path; dead infra until demand, so it follows demand.
- **data-bridge locality** — *blocked*: `apps/web` never instantiates a
`remoteNodeQueryClient`, so there's no remote read path to route to.
- **`WorkingSetPrewarm` planner scores** — marginal until per-node
frequency/pinned signals are tracked at boot (it already ranks by
recency).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude this PR from the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant