Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b664688
docs(exploration): explore the coding-agent lane — retrieval, memory …
Aug 1, 2026
0c854e0
feat(brain): one retrieval factory for every agent lane
Aug 1, 2026
18ef989
feat(plugins): wire retrieval into every agent lane, and guard it
Aug 1, 2026
5480121
fix(cli): never render a degraded search as an exhaustive one
Aug 1, 2026
3da18e7
fix(cli): xnet connect merges into CLAUDE.md/AGENTS.md instead of clo…
Aug 1, 2026
8b4ee35
feat(cli): add recall — one call instead of search-then-read-eight-nodes
Aug 1, 2026
494661a
feat(cli): xnet serve — a warm process for the read verbs
Aug 1, 2026
1cb9261
feat(plugins): api.recall/api.graph in the sandbox, and a skill that …
Aug 1, 2026
b8c46f7
feat(cli): semantic tier behind xnet serve --vectors
Aug 1, 2026
9cf264f
feat(cli): memory verbs — remember, forget, memories, distill
Aug 1, 2026
fb4502f
feat(brain): local retrieval tuning that cannot regress the golden set
Aug 1, 2026
e02bb45
feat(devtools): memory console — see, edit and forget what xNet remem…
Aug 1, 2026
3680e7a
test(cli): prove the app-running and bridged lanes reach an indexed tier
Aug 1, 2026
507621f
docs(exploration): check off the coding agent lane retrieval memory a…
Aug 1, 2026
e8843ef
chore(changeset): version the agent-lane retrieval work
Aug 1, 2026
bb0ec91
docs(changelog): add fragment for the agent retrieval work
Aug 1, 2026
b8d184d
chore(data): regenerate the API report for the RetrievalProfile schema
Aug 1, 2026
f357971
fix(build): keep the published closure and the hub image honest
Aug 1, 2026
b14cdb4
fix(cli): keep @xnetjs/vectors external so tsup never bundles sharp
Aug 1, 2026
cf57e8c
fix(hub): copy brain in the runtime stage too, and check all four COP…
Aug 1, 2026
96067ca
fix(brain): drop the vectors devDependency, and teach the guard about…
Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/agent-lane-retrieval-0415.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@xnetjs/plugins': major
'@xnetjs/data': minor
'@xnetjs/cli': major
---

Coding agents now get xNet's real retrieval, and a search that admits when it
couldn't do its job.

Every agent lane — the `xnet` CLI, `xnet mcp serve`, and the bridged agent
inside the desktop app — previously built its AI surface with no retriever and
fell back to a substring scan over the first 500 nodes, then rendered that
result identically to an exhaustive search. All three now go through one
`createAgentRetrieval()` construction path (enforced by a build guard), and
`xnet search` leads with the tier it actually ran at, warning on stderr when it
degraded.

New: `xnet recall` and the `xnet_recall` MCP tool return a budgeted context pack
where each hit carries the graph path it was reached by; `xnet serve` keeps the
read path warm behind a unix socket; `xnet remember` / `forget` / `memories` /
`distill` give the agent memory across sessions; and `api.recall` / `api.graph`
let a sandboxed `xnet run` script reach past its loaded slice.

**Breaking (`@xnetjs/plugins`)**: `AgentApi` gains required `recall` and `graph`
methods and `AgentScriptSession` gains `getRequestedContext()` — implementors of
those interfaces must update. `MCP_CORE_TOOL_NAMES` gains `xnet_recall`, and
`XNET_AGENT_SKILL_MD` has been rewritten.

**Breaking (`@xnetjs/cli`)**: `AgentCliServices` gains a required `retrieval`
field, and `runSearch` output now begins with a `tier` provenance line — anything
parsing its first line as the column header must skip it.
4 changes: 2 additions & 2 deletions .changeset/atproto-projection-and-snapshot.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
"@xnetjs/data": minor
"@xnetjs/sync": minor
'@xnetjs/data': minor
'@xnetjs/sync': minor
---

Play well with the ATmosphere (exploration 0389). `@xnetjs/data` gains
Expand Down
16 changes: 16 additions & 0 deletions .changeset/brain-published-0415.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@xnetjs/brain': minor
---

`@xnetjs/brain` is now published.

It was private because nothing outside the monorepo used it. `@xnetjs/plugins`
and `@xnetjs/cli` now do, and a published package cannot depend on an
unpublished one.

The package is unchanged by this: it has **zero runtime dependencies** and is
structural over whatever store, index and schema registry you hand it — which is
what makes it safe to publish rather than a new transitive burden. Its two
previous `dependencies` (`@xnetjs/data`, `@xnetjs/vectors`) were vestigial; the
source imports neither, and `@xnetjs/vectors` is now a devDependency used only
by a test.
2 changes: 1 addition & 1 deletion .changeset/screen-recording-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Add the `Recording` and `RecordingTranscript` schemas for local-first screen
recording (exploration 0414).

A `Recording` holds immutable screen and camera track references plus the
*edit* — a `cuts` list, `chapters`, and a `cameraLayout` — so trimming a
_edit_ — a `cuts` list, `chapters`, and a `cameraLayout` — so trimming a
recording is a field write rather than a re-encode, and every cut stays
reversible. `RecordingTranscript` carries timed segments with optional
word-level timings, and a `verbatim` flag that gates filler-word editing on
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ jobs:
- name: Humane patterns (0234)
run: pnpm check:humane-patterns

# Every agent lane must build its retrieval. The omission is invisible in
# review — the call reads fine, it is what it doesn't pass that drops the
# lane to a keyword scan with no graph stage. (0415.)
- name: AI surfaces wire retrieval (0415)
run: pnpm check:ai-retrieval

# The dev-only point-and-change source stamp must never reach a
# production bundle — it would publish the repo's directory layout in the
# DOM of every page. A diff cannot show that; this can. (0399.)
Expand All @@ -76,6 +82,13 @@ jobs:
- name: Publish closure (0220)
run: pnpm check:publish-closure

# The hub Dockerfile hand-lists the packages it COPYs. A dependency added
# three packages away silently staled that list, and nothing failed until
# a six-minute image build died on "Cannot find module" — the same build
# Railway runs, so the first symptom can be a failed deploy. (0415.)
- name: Hub image copies its dependency closure (0415)
run: pnpm check:hub-image

- name: Electron parity (0238)
run: pnpm check:electron-parity

Expand Down
10 changes: 9 additions & 1 deletion apps/electron/src/main/agent-mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import type { ParkedApproval } from '@xnetjs/plugins'
import {
createAgentRetrieval,
createMCPServer,
createMcpHttpServer,
type McpHttpServerHandle,
Expand Down Expand Up @@ -72,9 +73,16 @@ export async function startAgentMcpServer(): Promise<AgentMcpServerHandle> {
// the cache only has to be warm by the time a chat turn calls a tool.
void schemas.ensurePrimed().catch(() => undefined)

const store = createNodeStoreProxy()
// Exploration 0415: the bridged agent had the weakest retrieval of the three
// lanes — no graph stage, and a `searchText`-less proxy that turned every
// search into 500 nodes over IPC. Both halves are fixed here and in the proxy.
const retrieval = createAgentRetrieval({ store, schemas })

const server = createMCPServer({
store: createNodeStoreProxy(),
store,
schemas,
retrieval,
// The 0337 ceremony (0394 Phase 2): every AI-surface tool call lands as an
// AgentAction node, and medium+ risk writes park behind the risk-tiered
// approval — medium releases on the APPROVE code the agent relays into
Expand Down
46 changes: 46 additions & 0 deletions apps/electron/src/main/renderer-store-proxy.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
createNodeStoreProxy,
createSchemaRegistryProxy,
sendStoreRequest,
setupStoreResponseHandler
Expand Down Expand Up @@ -123,3 +124,48 @@ describe('createSchemaRegistryProxy', () => {
})
})
})

/**
* The bridged agent's lane had the weakest retrieval of the three: the proxy
* carried no `searchText`, so the AI surface fell back to `list({ limit: 500 })`
* + substring match — 500 nodes over IPC per search, and a result the agent
* could not tell from an exhaustive one (exploration 0415).
*/
describe('createNodeStoreProxy searchText', () => {
it('asks the renderer, which is the process that owns nodes_fts', async () => {
const seen: StoreRequest[] = []
windows.push({
webContents: {
send: (_channel, request) => {
seen.push(request)
queueMicrotask(() => {
for (const listener of respond) {
listener(null, {
id: request.id,
result:
request.operation === 'searchText' ? [{ nodeId: 'page_1', rank: -3.2 }] : null
})
}
})
}
}
})

const store = createNodeStoreProxy()
await expect(store.searchText?.('cutover', 20, { schemaId: 'S' })).resolves.toEqual([
{ nodeId: 'page_1', rank: -3.2 }
])
expect(seen[0]).toMatchObject({
operation: 'searchText',
params: { query: 'cutover', limit: 20, schemaId: 'S' }
})
})

it('passes a renderer `null` straight through, so "no index" stays distinguishable', async () => {
// `null` means the storage has no FTS. Coercing it to `[]` here would make
// "I cannot search" read as "nothing matched".
attachWindow(() => null)
const store = createNodeStoreProxy()
await expect(store.searchText?.('cutover', 20)).resolves.toBeNull()
})
})
11 changes: 11 additions & 0 deletions apps/electron/src/main/renderer-store-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ export function createNodeStoreProxy(): NodeStoreAPI {
offset: options?.offset ?? 0
}),

// The renderer's store is the one with `nodes_fts` (exploration 0415).
// Without this hop the AI surface here finds no `searchText` and falls back
// to `list({ limit: 500 })` + substring match — 500 nodes over IPC, and a
// result the bridged agent cannot tell from an exhaustive search.
searchText: (query: string, limit: number, options?: { schemaId?: string }) =>
sendStoreRequest<Array<{ nodeId: string; rank: number }> | null>('searchText', {
query,
limit,
schemaId: options?.schemaId
}),

create: (options: { schemaId: string; properties: Record<string, unknown> }) =>
sendStoreRequest<NodeData>('create', {
schemaId: options.schemaId,
Expand Down
22 changes: 22 additions & 0 deletions apps/electron/src/renderer/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ type LocalAPIStore = {
}
): Promise<LocalAPIStoreNode>
delete(id: string): Promise<void>
/**
* FTS5-backed search (exploration 0415). Optional on the type because a
* storage adapter without FTS legitimately has none — but the desktop store
* is SQLite, so in practice this is what keeps the main process off the
* substring-scan path.
*/
searchText?(
query: string,
limit: number,
options?: { schemaId?: string }
): Promise<Array<{ nodeId: string; rank: number }> | null>
getDocumentContent(nodeId: string): Promise<Uint8Array | null>
setDocumentContent(nodeId: string, content: Uint8Array): Promise<void>
}
Expand Down Expand Up @@ -812,6 +823,17 @@ function LocalAPIStoreHandler() {
return undefined
}

// The renderer owns the only store with an FTS index, so the main
// process asks it rather than scanning (exploration 0415). `null` here
// means "this storage has no index" — a real answer the caller reports
// as degraded, never a quietly empty result set.
case 'searchText': {
if (!store.searchText) return null
return await store.searchText(params.query as string, params.limit as number, {
...(params.schemaId ? { schemaId: params.schemaId as string } : {})
})
}

// The main process has no registry of its own, so the local API and the
// agent MCP server read this one — the real thing, including schemas
// registered at runtime rather than a hardcoded core list.
Expand Down
Loading
Loading