Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
fa6f664
test(integration): pin simple-mcp-server to legacy MCP discovery path…
tanzhenxin May 15, 2026
790f2d0
refactor(serve): 1 daemon = 1 workspace (#3803 §02) (#4113)
wenshao May 15, 2026
a656930
fix(vscode): preserve thinking state and recover missing edit snapsho…
DragonnZhang May 15, 2026
da1941c
fix(cli): handle MinTTY Ctrl+Backspace as delete-previous-word
dreamWB May 15, 2026
ff63da2
refactor(serve): extract createInMemoryChannel helper (#4156 A1) (#4160)
doudouOUC May 15, 2026
6b55d01
fix(cli): preserve debug session across sandbox relaunch
kkhomej33-netizen May 15, 2026
4c18f13
feat(core): add image+video support for Qwen3.6-35B-A3B quant variant…
Dinsmoor May 15, 2026
7c2b51d
fix(hooks): inject SessionStart additionalContext into chat context (…
DennisYu07 May 15, 2026
02a65f9
fix(i18n): Correct zh-TW translations to match Traditional Chinese co…
MikeWang0316tw May 15, 2026
1c529e4
feat(hooks): Add TodoCreated and TodoCompleted hooks for todo lifecyc…
DennisYu07 May 15, 2026
f6315b3
refactor(cli): revert dynamic slash command LLM translation (#4145)
tanzhenxin May 15, 2026
78c65c8
chore(deps): re-upgrade ink 6 → 7.0.3 (upstream Static remount fix la…
chiga0 May 15, 2026
57282eb
feat(hooks): add prompt hook type with LLM evaluation support (#3388)
DennisYu07 May 15, 2026
9d20536
perf(cli): code-split lowlight to cut startup V8 parse cost (#4070)
chiga0 May 15, 2026
41bcdae
fix(core): refresh systemInstruction in setTools() so progressive MCP…
chiga0 May 15, 2026
dcf7681
feat(core,cli): add generic atomicWriteFile, wire into Write/Edit too…
doudouOUC May 15, 2026
df32345
fix(vscode-ide-companion): use existing editor group for diff instead…
yiliang114 May 15, 2026
435f711
feat(cli): warn users that rewind is disabled in IDE mode (#4122)
doudouOUC May 15, 2026
372acf1
feat(cli): argument hint + --auto completion for /rename (#4048)
qqqys May 16, 2026
96b30ee
feat(cli): add baseline /doctor memory diagnostics (#4180)
qqqys May 16, 2026
264ed82
[codex] feat(serve): add capability registry protocol versions (#4191)
doudouOUC May 16, 2026
0dde1ad
feat(cli): add session-scoped /goal command with judge-driven turn co…
qqqys May 16, 2026
379d14a
feat(rewind): add file restoration support to /rewind command (#4064)
doudouOUC May 16, 2026
784182d
feat(skills): add /stuck diagnostic skill for frozen sessions (#4133)
doudouOUC May 16, 2026
8dfbdaa
feat(telemetry): unify span creation paths for hierarchical trace tre…
doudouOUC May 16, 2026
8d765fe
refactor(core): TaskBase envelope + foreground subagent persistence (…
tanzhenxin May 16, 2026
966b040
feat(cli): readline Ctrl+P/N for history and selection navigation (#4…
dreamWB May 16, 2026
8f54ae9
feat(cli): add built-in status line presets with interactive dialog (…
DragonnZhang May 16, 2026
2c200a3
fix(core): add heap-pressure auto-compaction safety net (#4186)
yiliang114 May 16, 2026
878f35f
feat(serve): per-request sessionScope override on POST /session (#417…
doudouOUC May 16, 2026
b959028
fix(cli): pass rewind selector test props (#4211)
wenshao May 16, 2026
daaa85e
feat(cli): add fork-session resume flag (#4159)
qqqys May 16, 2026
54fd5c5
feat(telemetry): add detailed sensitive span attributes (#4097)
doudouOUC May 16, 2026
0788ed7
test(perf): add daemon baseline harness (#4175 Wave 1 PR 1) (#4205)
doudouOUC May 16, 2026
57de269
feat(sdk): add DaemonSessionClient skeleton (#4201)
chiga0 May 16, 2026
ba77ddd
fix(lsp): expose status and startup diagnostics (#3649)
yiliang114 May 16, 2026
9505246
fix(serve): align integration test + user doc with merged sessionScop…
doudouOUC May 16, 2026
0eed884
fix(rewind): restore upstream TOCTOU ordering + heal sticky failed ma…
doudouOUC May 16, 2026
07165a0
Add stop hook blocking cap (#4208)
qqqys May 16, 2026
5ce2f28
fix(test): clear boundedPromise timers to prevent unhandled rejection…
wenshao May 17, 2026
ef29700
fix(ui): trim background task results and show newest first (#4094) (…
wenshao May 17, 2026
80f1e26
feat(protocol): add typed daemon event schema v1 (#4217)
chiga0 May 17, 2026
2e773b0
[codex] Allow custom output directory for /export (#4193)
qqqys May 17, 2026
2453b82
[codex] Add daemon session load/resume (#4222)
doudouOUC May 17, 2026
b90a2c9
feat(sdk): harden daemon session client (#4225)
chiga0 May 17, 2026
d6914bd
fix(core): align shell tool description with configured shell (#4170)
kkhomej33-netizen May 17, 2026
605e5ee
fix(cli): include skill base dir in slash commands (#4224)
kkhomej33-netizen May 17, 2026
4d9cbe4
feat(serve): add daemon-stamped client identity (#4231)
chiga0 May 17, 2026
c25e22b
feat(serve): add session-scoped permission route (#4232)
chiga0 May 17, 2026
cc32ef2
test(perf): skip daemon baseline harness under sandbox (#4234)
tanzhenxin May 17, 2026
0240c31
feat(core): PR-2.5 — post-promote stream redirect + natural-exit regi…
wenshao May 17, 2026
a5e4839
fix(cli): restore ACP prompt counter on resume (#4233)
kkhomej33-netizen May 17, 2026
ab03bad
fix(core): extend DashScope provider detection with additional hostna…
yiliang114 May 17, 2026
07e0e82
feat(serve): advertise typed_event_schema + pin SDK public surface (#…
doudouOUC May 17, 2026
0a4a08e
feat(serve): add client heartbeat (#4175 Wave 2.5 PR 9) (#4235)
doudouOUC May 17, 2026
6a1d55c
fix(core): apply tool name migrations at dispatch (#4213)
Jerry2003826 May 17, 2026
d2d426f
feat(serve): SSE replay sizing + slow_client_warning backpressure (#4…
doudouOUC May 17, 2026
9985d91
feat(cli): add configurable plansDirectory for Plan Mode (#4062)
shenyankm May 17, 2026
eef06ce
feat(cli): add structured memory diagnostics JSON (#3785)
yiliang114 May 17, 2026
4e06967
feat(serve): mutation gating helper and --require-auth (#4236)
doudouOUC May 17, 2026
9abd704
fix(cli): record mid-turn queued user prompts (#4215)
Jerry2003826 May 17, 2026
aef35c3
feat(serve): session metadata and close/delete lifecycle (#4175 Wave …
doudouOUC May 17, 2026
60fe594
feat(serve): add read-only status routes (#4241)
doudouOUC May 17, 2026
672de88
fix(serve): align integration test mirrors with merged capability + E…
doudouOUC May 17, 2026
79f38e0
fix: add cache limits to prevent OOM during build/test (#4188)
xmillogx-cmd May 17, 2026
ad23c7a
docs: user + design docs for --json-schema structured output (#4051)
wenshao May 17, 2026
c93d66c
fix(serve): align build and integration test coverage (#4248)
wenshao May 17, 2026
f84ddd4
feat(core): fail impossible goals (#4230)
qqqys May 17, 2026
f44ed09
feat(serve): preflight and env diagnostics routes (#4175 Wave 3 PR 13…
doudouOUC May 17, 2026
d3cbe76
fix(core): preserve read-before-write state across idle microcompacti…
tanzhenxin May 18, 2026
11ba385
feat(channel): add daemon bridge spike (#4203)
chiga0 May 18, 2026
4ab20ff
feat(ide): add daemon connection spike (#4199)
chiga0 May 18, 2026
d07c958
feat(tui): add daemon adapter spike (#4202)
chiga0 May 18, 2026
9621992
feat(serve): MCP client guardrails (#4175 Wave 3 PR 14) (#4247)
doudouOUC May 18, 2026
495d11f
refactor(serve): add FileSystemService boundary (#4175 Wave 4 PR 18) …
doudouOUC May 18, 2026
1030906
feat(serve): workspace memory and agents CRUD (#4175 Wave 4 PR 16) (#…
doudouOUC May 18, 2026
0bd703b
fix(serve): add mcp_guardrails to E2E capabilities expectation (#4268)
wenshao May 18, 2026
8f9b979
test: reduce wait-dependent UI test delays (#3987)
yiliang114 May 18, 2026
52d2850
feat(serve): safe workspace file read routes (#4175 PR 19) (#4269)
doudouOUC May 18, 2026
b208f34
feat(serve): add /demo debug page for qwen serve daemon (#4132)
jifeng May 18, 2026
33b2e0d
fix(serve): normalize Windows path separators in workspace file read …
doudouOUC May 18, 2026
d14ffd4
fix(serve): sync E2E baseline capabilities with registry (#4284)
doudouOUC May 18, 2026
36760ca
feat(serve): auth device-flow route (#4175 Wave 4 PR 21) (#4255)
doudouOUC May 18, 2026
688d644
feat(serve): add workspace file write/edit routes (#4175 PR20) (#4280)
doudouOUC May 18, 2026
6f7a489
feat(serve): approval / tools / init / MCP-restart mutation routes (#…
doudouOUC May 18, 2026
3ffe321
feat(serve): MCP guardrail push events + hysteresis (#4175 Wave 3 PR …
doudouOUC May 18, 2026
f97cb68
refactor(acp-bridge): create skeleton + lift zero-coupling primitives…
doudouOUC May 18, 2026
3605f81
chore(vscode): run dev cli from source (#4283)
DragonnZhang May 18, 2026
6c2605f
refactor(acp-bridge): lift status, paths, errors, and bridge types (#…
doudouOUC May 18, 2026
93ad0ff
fix(serve): auth device-flow follow-up for #4255 review threads (#4291)
doudouOUC May 18, 2026
a07ee5f
refactor(serve): typed errors for channel-closed and missing-cli-entr…
doudouOUC May 18, 2026
7daf616
fix(serve): unbreak E2E after #4271 (capabilities + clientCount) (#4306)
doudouOUC May 19, 2026
8d9cc61
fix(telemetry): Phase 1.5 polish — fallback order, abort-as-result, l…
doudouOUC May 19, 2026
47f493b
feat: support active goal stream events and non-interactive /goal (#4…
qqqys May 19, 2026
bc2664d
fix(cli): commit pending retry error to history on new turn (#4169) (…
LaZzyMan May 19, 2026
68e3ec9
refactor(acp-bridge): lift BridgeOptions + introduce DaemonStatusProv…
doudouOUC May 19, 2026
b0ea9f4
fix(core): decouple auto-memory recall from main-agent request path (…
LaZzyMan May 19, 2026
a7e0530
feat(worktree): Phase C — session persistence, hooksPath, Footer + Wo…
LaZzyMan May 19, 2026
87404da
fix(core): apply defaultModalities() on env-var-only model config (#4…
LaZzyMan May 19, 2026
078bee6
fix(cli): block Windows Tab approval-mode toggle when input has a Tab…
LaZzyMan May 19, 2026
2429949
fix(core): mirror Qwen3 reasoning on outbound history (#4294)
Alexxigang May 19, 2026
5fe12d4
feat(core): extend cross-auth fast models to agents (#4153)
tanzhenxin May 19, 2026
13f5fd5
feat(cli): add /directory remove subcommand
B-A-M-N May 8, 2026
a4e27ee
fix: resolve merge conflict markers in locale files
B-A-M-N May 8, 2026
9e9b263
ci: retrigger
B-A-M-N May 9, 2026
7aaa3ad
fix(i18n): remove orphaned 'Already covered' key from zh-TW locale
B-A-M-N May 9, 2026
36c4b62
fix(cli): address review feedback for /directory remove
B-A-M-N May 10, 2026
27b5ae1
fix(cli): address second review feedback for /directory remove
B-A-M-N May 11, 2026
72eee26
fix(directory): address review feedback on PR QwenLM#3975
B-A-M-N May 11, 2026
8f7e2d0
fix(directory): address third review feedback on PR #3975
B-A-M-N May 12, 2026
a27a4e1
fix(directory): replace setValueFullSave with saveSettings for disk r…
B-A-M-N May 12, 2026
d2f2a48
fix(directory): finalize PR #3975 feedback and cleanup unused i18n ke…
B-A-M-N May 15, 2026
98d009d
fix(directory): address review blockers for PR #3975
B-A-M-N Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
206 changes: 206 additions & 0 deletions docs/design/2026-05-15-async-memory-recall-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Async Memory Recall — Design Spec

**Date:** 2026-05-15
**Status:** Approved
**Related issues:** #3761, #3759
**Related PRs:** #3814, #3866

---

## Problem

`relevanceSelector.ts` uses `AbortSignal.timeout(1_000)` (introduced by #3866). On first-session cold starts, qwen3.5-flash averages ~908 ms — consistently hitting the 1 s threshold. The outer 2.5 s deadline in `resolveAutoMemoryWithDeadline` means every UserQuery can block for up to 2.5 s even when recall always fails.

Root cause: the main-agent request path `await`s the recall result before sending to the model. Any slowness in the recall side-query directly adds to user-visible latency.

---

## Design

### Core idea

Fire recall on UserQuery and never await it. Consume the result at two opportunistic points — whichever fires first:

1. **UserQuery consume point** — synchronous `settledAt !== null` check just before `turn.run()`. Zero-wait: if already settled, use it; if not, skip.
2. **ToolResult inject point** — same check on every ToolResult turn. Injects memory as a `system-reminder` **appended after** the functionResponse parts in `requestToSend`, giving the model memory context before its next response. (Append, not prepend: the Qwen API requires the functionResponse to immediately follow the model's functionCall — see the existing `hasPendingToolCall` IDE-context skip for the same constraint.)

This matches the pattern used by Claude Code upstream (`startRelevantMemoryPrefetch` / `settledAt` polling in `query.ts`).

---

## Data structures

### New type `MemoryPrefetchHandle` (in `client.ts`)

```typescript
type MemoryPrefetchHandle = {
promise: Promise<RelevantAutoMemoryPromptResult>;
/** Set by promise.finally(). null until the promise settles. */
settledAt: number | null;
/** True after memory has been injected — prevents double-inject. */
consumed: boolean;
controller: AbortController;
};
```

### Field change on `GeminiClient`

| Remove | Add |
| ------------------------------------------------------------ | ---------------------------------------------------------- |
| `pendingRecallAbortController: AbortController \| undefined` | `pendingMemoryPrefetch: MemoryPrefetchHandle \| undefined` |

---

## Changes

### 1. `client.ts` — remove `resolveAutoMemoryWithDeadline`

Delete the function entirely. It is replaced by the `settledAt` flag mechanism.

### 2. `client.ts` — UserQuery fire path

Replace the `resolveAutoMemoryWithDeadline` call with:

```typescript
// Abort any in-flight prefetch from a previous UserQuery before installing
// the new handle (prevents orphan side-queries when the user types again
// before recall settles).
this.pendingMemoryPrefetch?.controller.abort();
this.pendingMemoryPrefetch = undefined;

const controller = new AbortController();
// Bridge the caller's signal into the prefetch controller so a user abort
// (Ctrl-C / Esc) on the parent turn also terminates the recall side-query.
const onParentAbort = () => controller.abort();
if (signal.aborted) {
controller.abort();
} else {
signal.addEventListener('abort', onParentAbort, { once: true });
}

const promise = this.config
.getMemoryManager()
.recall(projectRoot, partToString(request), {
config: this.config,
excludedFilePaths: this.surfacedRelevantAutoMemoryPaths,
abortSignal: controller.signal,
})
.catch((error: unknown) => {
if (!(error instanceof DOMException && error.name === 'AbortError')) {
debugLogger.warn('Managed auto-memory recall prefetch failed.', error);
}
return EMPTY_RELEVANT_AUTO_MEMORY_RESULT;
});

const handle: MemoryPrefetchHandle = {
promise,
settledAt: null,
consumed: false,
controller,
};
void promise.finally(() => {
handle.settledAt = Date.now();
signal.removeEventListener('abort', onParentAbort);
});
this.pendingMemoryPrefetch = handle;
// no await — continue immediately
```

### 3. `client.ts` — UserQuery consume point (replaces `await relevantAutoMemoryPromise`)

```typescript
const prefetchHandle = this.pendingMemoryPrefetch;
if (
prefetchHandle &&
prefetchHandle.settledAt !== null &&
!prefetchHandle.consumed
) {
prefetchHandle.consumed = true;
this.pendingMemoryPrefetch = undefined;
const result = await prefetchHandle.promise; // already settled, returns immediately
if (result.prompt) {
// unshift, not push: keep memory at the front of systemReminders so
// it leads the system-reminder block on UserQuery turns. (ToolResult
// turns instead append to requestToSend to preserve functionCall /
// functionResponse pairing — see below.)
systemReminders.unshift(result.prompt);
for (const doc of result.selectedDocs) {
this.surfacedRelevantAutoMemoryPaths.add(doc.filePath);
}
}
}
```

### 4. `client.ts` — ToolResult inject point (new)

After `requestToSend` is assembled, before `turn.run()`, add:

```typescript
if (messageType === SendMessageType.ToolResult) {
const prefetchHandle = this.pendingMemoryPrefetch;
if (
prefetchHandle &&
prefetchHandle.settledAt !== null &&
!prefetchHandle.consumed
) {
prefetchHandle.consumed = true;
this.pendingMemoryPrefetch = undefined;
const result = await prefetchHandle.promise;
if (result.prompt) {
// Append (not prepend) so functionResponse parts stay first
// and the model's functionCall/functionResponse pairing
// isn't broken on the native Gemini path.
requestToSend = [...requestToSend, result.prompt];
for (const doc of result.selectedDocs) {
this.surfacedRelevantAutoMemoryPaths.add(doc.filePath);
}
}
}
}
```

### 5. `client.ts` — cleanup paths

The handle is released by two distinct mechanisms:

**5 abort-and-clear sites** (the prefetch is still pending, abort the controller before dropping the reference). Replace `pendingRecallAbortController?.abort()` + `= undefined` with:

```typescript
this.pendingMemoryPrefetch?.controller.abort();
this.pendingMemoryPrefetch = undefined;
```

Sites: `resetChat()`, `MaxSessionTurns` early-return, `boundedTurns=0` early-return, `SessionTokenLimitExceeded` early-return, Arena control-signal early-return. The fire path itself also performs this abort-then-replace when a new UserQuery arrives while the previous prefetch is still in flight.

**2 clear-only sites** (the prefetch has already settled and we're consuming it — no controller to abort, just drop the reference):

```typescript
prefetchHandle.consumed = true;
this.pendingMemoryPrefetch = undefined;
```

Sites: UserQuery consume point, ToolResult inject point.

### 6. `relevanceSelector.ts` — remove `AbortSignal.timeout(1_000)`

Remove the combined `AbortSignal.any([AbortSignal.timeout(1_000), callerAbortSignal])` and pass `callerAbortSignal` directly.

---

## Behaviour comparison

| Scenario | Before | After |
| -------------------------------------------- | ------------------------------ | ------------------------------------------------------ |
| recall completes before model prep | inject on UserQuery, ~0 wait | inject on UserQuery, ~0 wait |
| recall slow (cold start) | block up to 2.5 s | skip UserQuery, inject on first ToolResult |
| recall times out (1 s) | abort, empty result, no memory | no hard timeout; inject whenever settled |
| no tool calls, recall slow | block up to 2.5 s, then skip | skip UserQuery, no ToolResult opportunity — miss |
| user sends 2nd message before recall settles | 2nd recall races 1st handle | 1st handle aborted when 2nd UserQuery fires new handle |

---

## Out of scope

- Changing the memory injection format from `system-reminder` to `tool-result` attachment (CC style)
- Per-session byte budget skip gate
- Single-word prompt skip gate
Loading
Loading