ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Qwen Code ACP 会话接入 Managed Auto-Memory:Recall / Extract / Dream 全生命周期设计解析

Qwen Code ACP 会话接入 Managed Auto-Memory:Recall / Extract / Dream 全生命周期设计解析 Qwen Code ACP 会话接入 Managed Auto-MemoryRecall / Extract / Dream 全生命周期设计解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本设计文档docs/design/auto-memory/acp-managed-auto-memory.md针对 Qwen Code 的 ACPAgent Client Protocol会话阐述了如何让 ACP 会话复用已有的托管自动记忆Managed Auto-Memory实现Recall 投递语义与交互式会话完全一致Extract 与 Dream 在完整且成功的逻辑用户轮次之后各调度一次。阅读本文后你将掌握 ACP 记忆生命周期的主权划分、逻辑轮次判定规则、Recall 的 fast/refined 双阶段投递模型、以及LlmClient公开生命周期 API 与MemoryManager后台任务的协作方式并能依据源码路径深入验证每一处实现细节。背景ACP 会话缺失的记忆生命周期Qwen Code 的托管自动记忆系统docs/design/auto-memory/memory-system.md由四个核心操作构成Extract每轮对话后自动提取知识写入记忆文件、Dream周期性后台整合去重、Recall每轮对话前检索相关记忆注入系统提示、Forget用户/forget命令手动删除。在目标基线0.22.0对应上游 commit43d46be912f41e797e6a5aaa34bfaf4a16e06efa上交互式会话interactive/headless已经通过LlmClient.sendMessageStream()完整拥有了这一套状态机一次全新的交互式UserQuery在UserPromptSubmit钩子放行请求之后启动 Recall但查询文本使用钩子处理前的原始 prompt 文本初始 Recall 有100 ms 有界等待上限等待在确定性 fast 结果到达时提前结束模型选出的 refined 结果继续 pendingrefined 结果可在后续 ToolResult 发送时被消费投递前会剔除 fast 阶段已投递过的文档pending Recall 会在新查询、父级 abort、reset、shutdown或一轮没有后续安全投递点时被取消LlmClient追踪已 surfaced 的记忆路径会话级去重与最近完成的工具名Recall 相关性过滤Extract 与 Dream 由MemoryManager实现并受其门控保护游标、尾随请求合并、锁、压力门控与 Dream 阈值均不依赖 UI。而 ACP 会话则绕开了上述编排Session.#sendMessageStreamWithAutoCompression()直接调用LlmChat.sendMessageStream()Session.#buildInitialSystemReminders()上方的注释明确记录了 managed Recall 的缺失ACP 也从未在完整的工具循环与 Stop-hook 循环之后调度 Extract 或 Dream。因此该设计要解决的缺失是生命周期接线lifecycle wiring而不是缺失记忆算法本身。设计目标与非目标目标让每一次全新的 ACP 用户轮次都获得现有的 fast/refined 双阶段 Recall 行为保留钩子处理前的原始 prompt 作为 Recall 查询文本保持 provider 的硬性要求所有前置functionResponseparts 必须位于注入的 reminder 文本之前在一次成功的逻辑用户轮次之后使用最终完整聊天历史精确调度一次 Extract 与一次 Dream复用现有的取消、去重、遥测、游标、队列、锁与压力门控语义将生产代码改动控制在可穷尽审计的小范围内。非目标不在 ACP、钩子、sidecar 或 proxy 中重新实现 Recall / Extract / Dream不新增 ACP 方法或通知来上报记忆任务进度不把LlmClient的所有后台工作包括 auto-skill重构进一个新的共享调度器不改变记忆路径、workspace 作用域、文件格式、设置默认值或 Dream 阈值首个 PR 不为 ACP Cron、后台通知或 runtime-origin Goal 轮次启用 Recall后续可用同一 API 独立补齐 Cron parity不保证立即关闭的 ACP 会话会等待后台 Extract/Dream 完成——保留其既有后台语义不改变chrome-acp-v2镜像持久化与启动顺序属下游部署关注点。主权划分不新增协调器该设计刻意不引入新的 coordinator 类。理由是每个 ACPSession已拥有一个会话级Config而Config已经持有唯一一个LlmClient。因此LlmClient就是 pending Recall handle、surfaced 文档去重、recent-tool 上下文、取消逻辑与 Recall 投递遥测的既有且正确的所有者。关注点所有者原因Pending Recall promise 与 abort controller会话级LlmClient交互式轮次已由它持有该状态且 ACPSession共享同一实例fast/refined 仲裁、surfaced 路径去重、遥测会话级LlmClient这些不变量不能被复制进 ACP全新 / 重试 / 续跑 / runtime 轮次分类ACPSession只有Session知道 ACP 准入元数据与 Goal originToolResult 的 provider 发送顺序ACPSession它拥有自主工具循环与最终输出Part[]成功逻辑轮次完成判定ACPSession只有它横跨模型调用、工具、mid-turn 输入、Stop hook 与 todo-stop 续跑Extract/Dream 任务与持久化状态既有MemoryManager游标、合并、锁、门控与任务记录本就归它所有这种拆分同时避免了两个极端既不在Session.ts中复制 Recall 状态机也不引入一个只是包装已有正确生命周期对象的通用协调器。并发边界Config目前为每个会话构造一个MemoryManager因此 Extract 的尾随请求合并是按Config维度的而不是 daemon 全局或跨进程锁。两个同时活跃、指向同一记忆根的会话是记忆子系统既有的更宽泛并发边界本设计不得宣称仅仅因为两者都调用scheduleExtract()就被串行化。本 PR 既不削弱也不试图扩展当前保证在单个 ACPSession/Config内尾随 Extract 请求与 Dream 门控保持既有行为。更改 manager 所有权或新增文件系统 Extract 锁会牵涉交互式多进程使用、runtime-output-dir 隔离与 manager 清理属于独立设计而非本次生命周期对齐的臆测范围。逻辑轮次契约只有全新的模型绑定用户轮次才启动生命周期ACP 的输入类型必须被精确分类判定谓词基于既有事实而非新轮次类型const isFreshUserTurn !isRetry !isContinue !isRestoreAskUserQuestion goalTurn?.origin ! runtime;ACP 输入RecallExtract/Dream说明普通session/prompt是是一次含经过认证的 channel prompt模型绑定的 slash 命令或 skill是是一次本地处理的 slash 命令在 Recall 开始前就返回用户来源 Goal 轮次是是一次goalTurn.origin userRetry否否是对同一逻辑轮次的另一次尝试中断轮次续跑否否恢复持久化历史不是新用户轮次恢复的ask_user_question回答否否恢复工具循环不是新用户轮次Runtime 来源 Goal 续跑否否机器生成的续跑Stop-hook / todo-stop / 工具 / mid-turn 续跑复用 pending Recall不再额外调度全部处于原逻辑轮次内部钩子拦截或本地处理的请求否否Recall 只在这些提前退出之后开始取消、API 错误、循环失败的轮次pending Recall 被 finalize否不做部分历史提取Cron 或后台通知否否首个 PR 明确排除local-slash 与 blocked-hook 路径在 begin 调用之前就已返回因此无需额外标志。该谓词在源码中位于 Session.ts 的#executePromptInner()实现与设计完全一致。设计细节七个落地点1. 在LlmClient上暴露既有 Recall 生命周期把当前内联/私有编排抽取为已导出LlmClient上的三个公开方法标记为internal表示这是跨包 CLI 实现面而非新的受支持 SDK 契约beginManagedAutoMemoryRecall(query: string, signal: AbortSignal): void; consumeManagedAutoMemoryRecall( deliveryPoint: initial | tool_result, ): PromiseRelevantAutoMemoryPromptResult | null; finishManagedAutoMemoryRecall(): void;这些方法不引入新状态或新策略beginManagedAutoMemoryRecall()执行现有的 enabled/available 门控以new_query原因取消上一个 handle桥接父级 signal启动MemoryManager.recall()发布确定性 fast 结果安装同一个MemoryPrefetchHandle。当信号已中止时父级 abort 通过监听器先以abort原因取消 handle因此父级 abort 始终优先于幂等的 final close。consumeManagedAutoMemoryRecall(initial)使用既有 100 ms 有界 fast 结果等待consumeManagedAutoMemoryRecall(tool_result)是对已 settle 的 refined 结果的零等待消费。两条消费路径都会更新 surfaced-path 状态、剔除 fast/refined 重叠并发射既有投递遥测。finishManagedAutoMemoryRecall()是幂等的 handle 关闭原因记为no_safe_delivery_point。在 client.ts 的实现中可以看到beginManagedAutoMemoryRecall会先检查isManagedMemoryAvailable()与getManagedAutoMemoryEnabled()随后cancelPendingMemoryPrefetch(new_query)防止慢 side-query 变成孤儿持续运行再通过 AbortController 桥接父信号并注入recentTools与excludedFilePaths。现有的底层 helper 可以保持私有以便交互式 Cron 路径保留其零等待初始轮询不导出新的等待常量或 Recall 类型。LlmClient.sendMessageStream()在同一变更中迁移到这些方法使交互式路径继续锻炼共享 API——既有 Recall 测试由此同时保护两个调用方而非让新公开路径成为仅 ACP 使用、测试薄弱的分支。完整下游消费者清单刻意保持极小只有三个拥有方法LlmClient.sendMessageStream()——交互式/headless 的 UserQuery 与 ToolResult 发送Session.#executePromptInner()——ACP 的 begin、initial consume 与最终清理Session.#sendMessageStreamWithAutoCompression()——ACP 的 refined ToolResult consume。不新增任何 coordinator 文件、类、接口、设置或 barrel 导出。2. prompt 准入后、使用钩子前文本启动 ACP Recall在Session.#executePromptInner()中顺序为从原始 ACP 请求解析promptText与现状一致按现状解析本地 slash 命令按现状运行UserPromptSubmit——block 或错误直接返回不启动 Recall对isFreshUserTurn调用this.config .getLlmClient() .beginManagedAutoMemoryRecall(promptText, pendingSend.signal);该调用发生在钩子决策之后但仍使用promptText——既不用钩子附加的additionalContext也不用解析后的文件/编辑器内容。这与交互式路径一致同时让 Recall 与快照和 reminder 准备并发执行。源码证实 Session.ts 在钩子 block 检查之后、快照创建之前调用 begin且传入的是promptText而非submittedPrompt。Retry、中断续跑、恢复的ask_user_question与 runtime-origin Goal 路径都不调用beginManagedAutoMemoryRecall()。3. 用既有 reminders 注入初始结果对全新用户轮次紧接#buildInitialSystemReminders()返回之后消费初始结果const memory await this.config .getLlmClient() .consumeManagedAutoMemoryRecall(initial); if (memory?.prompt) { systemReminders.unshift({ text: memory.prompt }); }既有的insertAfterFunctionResponses()调用仍是 reminder 组的唯一插入规则。必须保持的不变量是leading functionResponse parts → one-shot/session reminders包括 memory → user 或 continuation 内容普通全新 prompt 没有前置 function response通用排序对未来的复用仍然重要可防止意外的协议回归。4. 在最终 provider 发送边界消费 refined Recall不要在#buildNextMessageAfterToolRun()内消费 refined Recall——该方法构造的消息可能随后被循环保护、todo-stop 校验、取消或发送前准备丢弃。在那里消费会把记忆标记为已投递而消息实际从未到达 provider 发送。正确位置是扩展Session.#sendMessageStreamWithAutoCompression()在它的所有显式发送丢弃门控prepareBeforeCompression、自动压缩、session-token 限制、压缩诊断、beforeSend、最终 abort 检查全部通过之后、构造请求并调用LlmChat.sendMessageStream()之前检测出站消息是否以一个或多个functionResponseparts 开头零等待消费tool_resultRecall用insertAfterFunctionResponses()插入返回的 reminder直接继续进入既有 provider 发送。这是普通工具、Stop-hook 工具、todo-stop 工具与 mid-turn 工具共同的最窄安全点。现有的压缩与 session-token 检查检查的是聊天状态而非未发送的message因此把插入移到这些检查之后不会改变它们的记账同时避免了已知的发送前决策把记忆标记为已 surfaced 而实际没有发起 provider 请求。同步的 provider 发送失败仍然可能发生与交互式路径在消费后一致。出站顺序始终为functionResponse[0..n] → 相关记忆若 refined 结果已 settle → active-todo / repeated-failure / mid-turn 文本若 Recall 尚未 settle调用立即返回null后续工具发送会再次尝试没有任何模型请求等待 refined selector。5. 在 ACP 的所有退出路径上 finalize Recall在Session.#executePromptInner()中对Storage.runWithRuntimeBaseDir()promise 附加一个finally当轮次启动过 Recall 时调用finishManagedAutoMemoryRecall()。链式finally保留既有方法体避免产生纯缩进变化的 diff。这覆盖了正常无工具完成、API 异常、循环退出与未来可能的提前 return无需在每个 return 语句处添加清理代码。取消与会话销毁已经 abortpendingSend.signalRecall 信号桥在幂等最终关闭运行之前会把这些记录为abort。由于 ACP 在一次prompt()调用内执行完整的工具循环pending Recall handle 跨所有 ToolResult 发送保持存活只在真正的逻辑轮次边界被 finalize。6. 在成功逻辑轮次边界调度 Extract 与 Dream在#handleStopHookLoop()返回之后、返回成功的 ACP prompt 响应之前仅当以下条件全部满足时才调度后台记忆结果为stopReason end_turn结果不是优雅的循环保护停止graceful loop-protection stop调用是全新用户轮次managed auto-memory 已启用。前台用户 prompt 在循环检测时抛异常已绕过此点而认证 channel prompt 与 Goal 轮次有意把同一条件转换为优雅end_turn因此仅凭stopReason不够。为此给既有内部StopContinuationResult/Stop-loop 结果增加一个可选字段loopProtectionStopped且只从两个既有循环保护出口toolRun.loopDetected与stoppedByRepeatedToolFailure传播——这是结果溯源outcome provenance而非第二个轮次状态机。源码中该字段在 Session.ts 定义并在两处循环保护退出L7555、L7592置为true。到达此点时所有模型流与MessageDisplay均已结束、工具循环无 pending 调用、消息重写已排空、Stop-hook/todo-stop 循环已达终局决策。使用一次最终历史快照进行 Extractconst memoryManager this.config.getMemoryManager(); const projectRoot this.config.getProjectRoot(); const sessionId this.config.getSessionId(); const history this.#getCurrentChat().getHistoryShallow(); void memoryManager .scheduleExtract({ projectRoot, sessionId, history, config: this.config }) .catch((error: unknown) { debugLogger.warn( Failed to schedule ACP managed auto-memory extraction., error, ); }); void memoryManager .scheduleDream({ projectRoot, sessionId, config: this.config }) .catch((error: unknown) { debugLogger.warn( Failed to schedule ACP managed auto-memory dream., error, ); });两个任务都不 await也不把它们的 promise 存入LlmClient.pendingMemoryTaskPromises——ACP 没有 TUI 的memory_saved项去消费该队列MemoryManager已自行跟踪工作。两个 rejection handler 是防止未处理的后台拒绝而非提供 fallback 路径。源码中的#recordPromptCompletionEffects()Session.ts完整实现了该门控非 fresh 轮次、非end_turn、loopProtectionStopped或记忆未启用时直接 return否则用getHistoryShallow()取最终历史快照并 fire-and-forget 调度。没有新增 once 字段——调度代码只有一个可达调用点位于终止 fresh 逻辑轮次的那一次#handleStopHookLoop()之后工具与 Stop-hook 续跑永远不会独立经过该调用点。7. 从 ACP 喂入既有 recent-tool 状态ACP 当前执行工具时不调用已公开的LlmClient.recordCompletedToolCall()。设计利用Session.runTool()既有的外层finally此时terminalStatus与有效args已可知在注册工具到达非取消终态时调用一次this.config.getLlmClient().recordCompletedToolCall(toolName, args);这使 Recall 的 recent-tool 噪声过滤与交互式会话对齐来自重复历史的调用不会进入runTool()、不会被计数取消的调用仍被排除成功与错误终态的工具都会被计数与交互式路径一致。不新增 ACP 专属的 recent-tool 收集。该调用同时会更新方法既有的工具调用计数与 skill-write 标记见 client.ts 的recordCompletedToolCall内部经rememberCompletedToolName维护最近工具名列表、经SKILL_WRITE_TOOL_NAMES检查 skill 写入并递增toolCallCount。ACP 不调用交互式 auto-skill 调度器因此本 PR 不引入任何 ACP auto-skill 行为。端到端时序文件级变更集生产代码文件变更packages/core/src/core/client.ts把既有 Recall 的 begin/consume/finalize 操作抽取为公开方法并把当前 UserQuery/ToolResult 调用点迁移到它们交互式使用无行为变化packages/cli/src/acp-integration/session/Session.ts分类 fresh 用户轮次、调用共享 Recall API、在安全发送点注入 initial/refined 结果、finalize Recall、保留优雅循环停止溯源、记录完成工具、在干净完成后恰好一次调度 Extract/Dream不需要其他生产文件尤其不要在packages/core/src/memory/下新增文件——记忆算法与 manager API 已经存在。测试文件用途packages/core/src/core/client.test.ts保持现有 fast/refined、超时、取消、去重与遥测测试通过新的公开生命周期方法仅补充一条sendMessageStream()未覆盖的直接生命周期断言packages/cli/src/acp-integration/session/Session.test.ts钉住 ACP 轮次分类、原始钩子前查询、注入顺序、清理、轮次后恰好一次调度、最终历史快照、错误/取消排除与 recent-tool 记录integration-tests/cli/acp-integration.test.ts通过打包 CLI 与 fake OpenAI server 做确定性的真实 ACP Recall 测试测试计划Core 单元测试保留既有断言100 ms 上限内的确定性 fast 结果慢 refined selector 不阻塞初始主请求function responses 之后的 refined 投递fast/refined 路径去重new-query、abort、reset、shutdown 与无安全点的取消Recall 投递遥测。重构可接受的前提是这些测试通过交互式调用方锻炼抽取出的公开方法——不要在测试专用 mock 中复制 Recall 状态机。ACP 单元测试聚焦以下可观察契约普通 prompt 以原始 ACP 文本调用beginManagedAutoMemoryRecall()即使UserPromptSubmit追加了 contextblocked hook 与本地处理的 slash 命令永不开始 RecallRetry、中断续跑、恢复的ask_user_question与 runtime-origin Goal 轮次既不 begin Recall 也不调度 Extract/Dream初始记忆结果出现在第一个模型请求中refined 结果出现在每个前置functionResponse之后、后续文本之前下一次实际 tool-result provider 发送未 settle 的 refined 结果不增加延迟在后续工具发送时重试取消、销毁、provider 错误与循环失败会 finalize Recall 且不调度 Extract/Dream完整工具循环加一个或多个 Stop-hook 续跑以最终浅拷贝历史各调度一次 Extract 与一次 Dream前台与优雅 channel/Goal 循环保护退出不调度 Extract 或 Dream非取消的 ACP 工具调用一次既有 completed-tool recorder重复与取消工具不调用。ACP 集成测试在既有 ACP 集成套件中用startFakeOpenAIServer()与真实打包的qwen --acp进程增加一个确定性用例隔离QWEN_HOME并设置QWEN_CODE_MEMORY_LOCAL1以获得透明测试路径用唯一标记种子化一个有效的项目主题文件把 Recall selector 响应延迟到 100 ms 之后提交一个词法匹配的查询检查 fake server 的第一个匹配主模型流式请求断言唯一标记已存在证明真实 ACP 传输、Recall 与确定性 fast 投递在没有 selector 延迟的情况下成立。Extraction、持久化记忆扫描与 Dream 执行已有聚焦的MemoryManager生命周期集成测试ACP 单元测试证明scheduleExtract()与scheduleDream()收到正确的最终边界。在 ACP 传输测试中重跑完整提取 agent 会与这些测试重复并让边界测试依赖于与 ACP 无关的脚本化 agent 工具循环。在各自包目录运行聚焦测试然后执行必要的构建与类型检查cd packages/core npx vitest run src/core/client.test.ts cd packages/cli npx vitest run src/acp-integration/session/Session.test.ts npm run build npm run bundle cd integration-tests QWEN_SANDBOXfalse npx vitest run cli/acp-integration.test.ts -t managed auto-memory cd .. npm run typecheck正确性不变量实现完整必须满足全部以下条件Recall 查询文本永不包含UserPromptSubmit的 additional context一个 fresh 逻辑 ACP 用户轮次至多持有一个 pending Recall handleRetry 与机器续跑永不创建新 handle初始 provider 请求的等待不超过既有有界 fast Recall 上限refined Recall 永不先于前置functionResponse一条记忆路径在 fast/refined 结果及后续用户轮次中每个 ACP 会话至多 surfaced 一次每个启动的 ACP Recall 在所有退出路径上被消费或 finalizeExtract 看到的是最终工具与 Stop-hook 续跑之后的历史Extract 与 Dream 在每个 fresh 成功用户轮次中各至多调度一次且绝不用于取消、失败或优雅循环停止的轮次ACP prompt 响应不等待 Extract 或 Dream 完成。被否决的备选方案新增ManagedMemoryTurnCoordinator类被否决。它会把同样一组字段搬出LlmClient、新增导出实体并强制构造函数/生命周期接线——尽管 ACP 已经共享同一个LlmClient实例。在既有状态所有者上提供公开生命周期方法能以更小的证明面实现复用。仅当未来某个 runtime 不持有LlmClient却仍需要同一 Recall 状态机时独立协调器才合理。把 Recall 状态复制进Session.ts被否决。会重复 fast/refined 仲裁、路径去重、取消监听器、终态遥测以及未来每一次 Recall 变更。把所有轮次后调度放进LlmClient被否决。ACP 拥有权威的完整轮次边界而LlmClient.sendMessageStream()在交互式流程中是物理模型发送边界强制 ACP 调用交互式后台任务方法还会入队仅 TUI 使用的通知 promise并把本变更耦合到 auto-skill 调度。在#buildNextMessageAfterToolRun()消费 refined Recall被否决。构建出的消息不一定被发送在最终 provider 发送边界消费既更晚又为所有 ACP 工具续跑所共享。用 hook 或 proxy 实现被否决。无法在不脱离状态所有者重建 Qwen Code 内部机制的情况下保留现有 fast/refined 投递、会话去重、abort 传播与遥测。下游镜像与持久化工作本上游 PR 让 Qwen Code 内部的 ACP 记忆行为正确但不会让容器的文件系统持久化。chrome-acp-v2的发布仍需要独立的、由部署方负责的变更用打过补丁的 Qwen Code 版本构建镜像若镜像策略要求钉住默认值在生产设置中显式启用 managed auto-memory 与 Dream同时持久化项目 runtime 状态与用户级memories目录在启动 ACP proxy 前完成恢复或显式声明降级的首轮一致性策略在共享 NAS 存储前按认证用户/租户为用户级记忆做命名空间隔离在部署镜像中验证write - sandbox restart - new ACP session first-turn recall链路。ACP/WebSocket proxy 保持纯传输层无需任何记忆逻辑。实现顺序把 fork 分支 rebase 到目标基线43d46be或更新的上游 commit并复查命名的符号以零行为变化重构LlmClientRecall 为公开生命周期方法运行 core 测试添加 ACP begin/initial/refined/finalize 接线与聚焦的 Session 测试添加恰好一次 Extract/Dream 调度与 recent-tool 记录测试添加真实进程 ACP 集成测试提交 PR 前运行 build、typecheck、聚焦测试与两轮干净的 diff 审计。总结该设计把 ACP 会话的记忆行为补齐到与交互式会话完全对齐逻辑轮次边界归SessionRecall 状态机归LlmClientExtract/Dream 算法归MemoryManager——三者各司其职、互不复制。开发者若要在自己的 Qwen Code 集成中验证或扩展该机制可从 Session.ts 的#executePromptInner()与#recordPromptCompletionEffects()两个入口切入沿beginManagedAutoMemoryRecall → consumeManagedAutoMemoryRecall → finishManagedAutoMemoryRecall的生命周期链路最终落到 client.ts 的共享实现与 memory-system.md 记载的 Extract/Dream/Recall 完整算法上。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表