十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

DeepSeek Harness 会话归档实现解析:注册表级全局集合与全快照同步设计

DeepSeek Harness 会话归档实现解析:注册表级全局集合与全快照同步设计 人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载本文基于 DeepSeek Harness 仓库中的 Agent Note 文档 2026-07-31-session-archive-global-set.md 编写辅以 workspace 域、workspace-controller API、client 运行时与派生层源码进行纵深验证。导读本文深入解析 DeepSeek HarnessEverything is a Plugin 的 Agent Harness 项目中「会话归档」Session Archive功能的完整落地从产品决策「归档而非删除」出发讲述为什么归档集合必须挂在 workspace 域的全局单例上、如何在不触碰「一个会话只被一个 workspace 记账」不变式的前提下让会话从 workspace 分组、Ungrouped、搜索、平铺列表四个表面同时消失并解释 wire 面为何统一采用「全快照」而非增量帧。读完你将掌握该功能的存储 schema、注册表串行化写入、RPC 应答、client 快照模型、tree.ts派生过滤与导航回退策略的完整调用链以及对应的测试验证路径。一、问题背景Delete 占位符背后的产品决策在 Sidebar 的 workspace 浏览器中会话行菜单里一直存在一个纯视觉的「Delete session」占位项——它没有任何 handler。产品口径最终定为归档archive而非删除delete原因在于会话日志session log与 workspace 记账workspace accounting都不应被改动该会话只是从所有分组视图里隐藏workspace 分组、Ungrouped、搜索、平铺列表未来要支持取消归档unarchive因此数据与记账槽位必须完好保留。由此引出第一个设计难点归档记录需要一个「家」。一个 Ungrouped 会话不属于任何 workspace 实体所以「per-workspace 字段」天然放不下它——这正是最初方案被否决的原因也是最终转向注册表级全局集合的动因详见后文「替代方案评估」。二、核心决策全局单例 派生层过滤 全快照文档给出的一句话决策是归档集合是 workspace domain 全局单例workspaceDomainState.archivedSessionIds上的一个新字段覆盖在 workspace 记账之上显示过滤全部收敛在 client 的tree.ts派生层wire 面走全快照姿态。这条决策可以拆成三个正交的层面层面落点职责存储workspaceDomainState.archivedSessionIds持久化归档集合纯新增字段零迁移注册表ctx.workspaceRegistry.archiveSession(id)与 create/delete 串行化写入校验会话存在性显示过滤clienttree.ts的sessionVisible判据四个分组表面共用同一过滤源host 不做投影过滤为什么显示过滤放在 client 派生层而不是 host 侧文档明确否定了「host 侧在workspaceView/sessionIdsgetter 过滤」的方案理由非常关键归档 ≠ 改变记账投影过滤会把两个概念搅浑未来恢复入口也需要 client 拿到全量记账。也就是说host 的sessionIds永远反映真实记账accountingclient 拿到全量数据后在派生层决定「显示什么」。这保证了 unarchive 恢复时数据完整性零损失。这一点可以在源码中得到印证WorkspaceRegistry.list()返回的实体sessionIds只做 canonical-cwd 头部索引过滤与归档无关见 packages/workspace/workspace/src/index.ts。三、存储层纯新增字段与 schema 默认值升级归档集合的持久化定义在 workspace 域的 schema 中// packages/workspace/workspace/src/spec.ts export const workspaceDomainState z.object({ initialized: z.boolean(), workspaceIds: z.array(workspaceId), archivedSessionIds: z.array(z.string().transform(SessionId)).default([]), pendingMutation: workspacePendingMutation.optional(), })要点如下z.array(sessionId).default([])archivedSessionIds是有默认值的数组。字段写入介质后旧介质pre-field media在解析时经 schema default 得到空集合因此无需任何迁移代码。domain version 保持 2这是纯粹的 add-only 字段扩展不 bump version。initial初始状态同样携带该字段{ initialized: false, workspaceIds: [], archivedSessionIds: [] }见 spec.ts。与「一个会话只被一个 workspace 记账」不变式零纠缠归档的会话保留其在sessionIds数组中的 slot——数组顺序即显示顺序slot 保留意味着未来 unarchive 能恢复原位置。因此归档集合从不参与 one-owner accounting 不变式的校验该不变式由validateStoredState强制见 index.ts。所有修改全局状态的路径create/delete/bootstrap/recoverPendingMutation都显式透传archivedSessionIds: state.archivedSessionIds保证归档集合在其它 workspace 写操作中不被破坏例如 createCanonical 与 bootstrap。四、注册表服务层串行化写入与未知会话拒绝WorkspaceRegistry新增了两个公开面见 packages/workspace/workspace/src/index.tsget archivedSessionIds(): readonly SessionId[] { return this.requireState().archivedSessionIds } archiveSession(sessionId: SessionId): Promisevoid { return this.enqueueOperation(async () { // 串行槽保证 check-then-write 不会与其它 archive 交错 if (this.requireState().archivedSessionIds.includes(sessionId)) return if (!(await this.sessionKnown(sessionId))) { throw new WorkspaceUnknownSessionError(sessionId) } const state this.requireState() await this.setState({ ...state, archivedSessionIds: [...state.archivedSessionIds, sessionId] }) }) }值得展开的四个实现事实enqueueOperation串行化归档与 create/delete 一样通过operationTailpromise 链排队同一时刻只有一个注册表写操作在执行。这保证「先查后写」的配对在并发下也不会交错——两个并发 archive 同一 id 不会出现重复写入。幂等已归档的 id 直接 return不写盘、不发事件。源码注释明确「An already archived id resolves without writing.」未知会话拒绝sessionKnown()依次检查 live sessions、头部索引、以及一次新鲜的sessionPersistence.list()重索引——只有「确定不存在」才返回 false存储故障会原样传播绝不伪装成「未知会话」。WorkspaceUnknownSessionError专门承载「live 与持久化都查无此会话」的确定缺失index.ts携带sessionId供上层构造错误详情。五、RPC 与传输面应答完整集合 基线携带 全快照帧5.1 命令层WorkspaceCommands.archiveSession见 packages/api/workspace-controller/src/commands.tsasync archiveSession(request: WorkspaceArchiveSessionRequest): PromiseWorkspaceArchiveValue { try { await this.ctx.workspaceRegistry.archiveSession(request.sessionId) } catch (error) { if (!(error instanceof WorkspaceUnknownSessionError)) throw error throw failure(session-not-found, error.message, { sessionId: request.sessionId }) } return { archivedSessionIds: [...this.ctx.workspaceRegistry.archivedSessionIds] } }wire 形态workspace.archiveSession({ sessionId }) → { archivedSessionIds }应答的是更新后的完整集合而非被归档的单条 id未知会话复用既有错误码session-not-found不新增错误码client 侧已有处理路径天然兼容。5.2 重连基线与增量帧workspace.list的响应即 follow 流的 baseline现在携带完整归档集合作为 client 重连时的基线见 packages/api/workspace-controller/src/feed.tsbaseline(): WorkspaceBaseline { return { items: this.ctx.workspaceRegistry.list().map(workspaceView), archivedSessionIds: [...this.ctx.workspaceRegistry.archivedSessionIds], } }持久化变更后的推送走的是domain/changed事件在global put 分支中的集合比对见 feed.tsconst nextArchived state.archivedSessionIds.map(String) if (!sameStrings(this.archived, nextArchived)) { this.archived nextArchived this.publish({ type: archived, archivedSessionIds: [...state.archivedSessionIds] }) }即只有归档集合成员实际变化sameStrings比较失败时才发布一帧完整快照。这与既有workspace-changedorder 帧完全同姿态——orderChanged同样由字符串数组比对触发feed.ts。这正是文档所称「新 host 帧host/archived-sessions-changed」在实现中的形态follow 流中的{ type: archived }帧由 global put 分支的集合比对驱动。六、Client 运行时快照模型、引用替换与 Set 的取舍6.1 快照状态为何用数组而非 SetClientWorkspaceModel中归档集合是WorkspaceSnapshot.archivedSessionIds类型为readonly SessionId[]见 packages/api/workspace-controller/src/client/model.ts。这里有一个很深的工程约束公有快照状态要保持在 store 引擎的纯数据词汇plain-data vocabulary之内immer 的 draft 在不开启 MapSet 插件时拒绝 Set因此归档集合必须以数组形态存入快照成员查询membership lookup则在派生函数内自建临时 Set——这与expandedProjects是同一个成熟模式。6.2 引用只在成员变化时替换installArchived是这套状态模型的点睛之笔model.tsprivate installArchived(archivedSessionIds: WorkspaceArchiveValue[archivedSessionIds]): void { if (archivedSessionIds.length this.archivedSessionIds.length archivedSessionIds.every((id, index) id this.archivedSessionIds[index])) return this.archivedSessionIds [...archivedSessionIds] this.invalidate() }成员不变 → 不替换引用React 层的useSyncExternalStore风格订阅getSnapshot/subscribe因此不会收到无谓的失效通知成员变化 → 整体替换三路来源——list 基线replaceBaseline、unary 回声archiveSession成功后、changed 帧replaceArchived——都调用installArchived安装完整集合天然去重无需 client 侧合并逻辑。6.3 帧/回声与 in-flight 基线的竞态屏蔽文档强调一个细节帧/回声落在 in-flightworkspace.list期间时会屏蔽旧基线对新集合的回滚。因为replaceBaseline会先installArchived(baseline.archivedSessionIds)再进入 ready 态而 unary 回声和 changed 帧同样走installArchived——配合sameStrings式的成员比对无论到达顺序如何后到的、更新的集合总是胜出源码中对 order 帧有同样策略orderFrameGeneration递增使流帧压制旧 unary 回声见 model.ts。七、派生层一处判据四个表面同源显示过滤全部收敛在tree.ts的sessionVisible判据上见 packages/client/ui-workspace/src/client/tree.tsfunction sessionVisible(session: SessionSummary, current: SessionId | undefined, archived: ReadonlySetSessionId): boolean { return session.origin ! subagent !archived.has(session.id) (!session.blank || session.id current) }归档成为判据中的一档!archived.has(session.id)。随后deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, view)入口处const archived new Set(archivedSessionIds)传入groupByWorkspace后同时覆盖「workspace 分组循环」与「stray 兜底Ungrouped 桶」两条路径tree.tsderiveFlat平铺列表In one list 模式同样在入口构建临时 Set 后逐行过滤tree.tsderiveSearchResults搜索结果的本地行与 content 命中行都经过sessionVisible——归档会话永不进入搜索匹配tree.ts。于是四个显示表面分组循环、stray 桶、搜索、平铺共享同一个sessionVisible源改一处即全量生效。八、导航策略归档当前会话 → 回到 hero文档记录了一个明确的用户体验决策归档当前打开的会话时主视图回到 heroNew Session 空态。实现位于UiWorkspaceService的导航监听中见 packages/client/ui-workspace/src/client/navigation.tsprivate clearArchivedCurrent(): boolean { const current this.sessions.list.getSnapshot().current if (current undefined || !this.workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false this.sessions.clear() return true }关键点一条规则覆盖三种到达路径——本地 unary 回声本 tab 归档了正在看的会话archiveSession成功回调安装新集合后监听器发现当前 selection 已入集合其它标签页的 changed 帧另一个 tab 归档了本 tab 正选中的会话重连基线本 client 离线期间会话被归档重连 baseline 恢复后发现 selection 已在集合中。watchNavigation的reconcile在每次快照失效时先跑clearArchivedCurrent()navigation.tssessions.clear()将主视图送回 hero。九、UI从 visual-only 的 delete 到非破坏性 archive菜单项的变化遵循「非破坏性动作」的产品原则原delete行visual-only、无 handler改为archivelabel 为Archive session非 danger 样式不渲染成红色危险操作无确认对话框——因为归档是非破坏性动作最坏的误触结果只是「列表里被隐藏」数据与记账完好未来可恢复。十、替代方案评估为什么是全局集合文档记录了四个候选方案及其否决理由理解它们有助于把握边界方案否决理由per-workspacearchivedSessionIds最初表述Ungrouped 会话没有归属 workspaceper-workspace 字段无家可归用户改口全局SessionSummary 上加 archived 标记session.list 层把 workspace domain 的事实 join 进 sessions domain 的投影属于跨域耦合且 summary 没有增量帧仍需另发通知收益不抵复杂度host 侧在workspaceView/sessionIdsgetter 过滤归档 ≠ 改记账投影过滤混淆两概念未来恢复入口需要 client 拿到全量记账增量帧archived/removed 单条集合极小、变更频率极低全快照免去 client 合并逻辑与去重状态与既有 workspace-changed 姿态一致其中「全快照 vs 增量」的取舍尤其值得注意正因为集合「tiny and rarely changes」全快照的传输成本可忽略而收益是 client 无需维护任何增量合并状态——这与本仓库在 workspace order 上的既有做法保持了一致性。十一、测试验证全链路钉住11.1 域层测试packages/workspace/workspace/tests/workspace.spec.ts针对归档集合钉住了四类行为见 workspace.spec.ts幂等对同一 id 连续 archive 两次集合不重复[gone]而非[gone,gone]未知 id 拒绝archiveSession(SessionId(ghost))抛WorkspaceUnknownSessionError且介质中集合保持原状跨重启恢复第一个 registry 归档s1后第二个 registry重启后重建的archivedSessionIds仍是[s1]pre-field 介质默认升级写入不含该字段的旧介质后重新打开升级结果为archivedSessionIds: []schema default 生效零迁移代码。此外早期测试断言storedState(pool)中始终携带archivedSessionIds: []确保所有全局写路径bootstrap、recover、create/delete 收尾都不丢字段例如 workspace.spec.ts。11.2 e2e 测试workspace-management 的 e2e 钉住完整链路归档 → 行消失 → reload 后仍然隐藏、日志仍在。最后一步「日志仍在」正是归档与删除的本质区别。十二、已知限制与未来扩展按文档记录本期范围的已知限制与后续路径无查看/取消归档入口归档后 UI 没有 unarchive 表面已记录在 README 的 Known Limitation 中未来恢复成本极低因为数据与 slot 完好未来加恢复面 一个 UI 表面 一个逆向 RPC如workspace.unarchiveSessionworkspace.list响应形状变化属于 pre-release 直接修改不设兼容层。结语「会话归档」是 DeepSeek Harness 中一次教科书式的领域建模实践它用注册表级全局集合解决了 Ungrouped 会话的归属问题用纯新增字段 schema default实现了零迁移升级用client 派生层过滤保持了 host accounting 的纯粹用全快照同步免除了增量合并复杂度并用引用替换 临时 Set兼顾了 React 订阅效率与 immer 数据词汇约束。从 spec.ts 到 tree.ts再到 workspace.spec.ts 的测试闭环完整展示了「决策 → 分层实现 → 竞态防护 → 测试钉住」的工程链条值得作为 Agent Harness 中「隐藏而非删除」类功能的参考范式。赞分享人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载相关推荐终极解决方案如何用sguard_limit轻松限制腾讯游戏ACE-Guard资源占用终极解决方案如何用sguard_limit轻松限制腾讯游戏ACE Guard资源占用 你是否在玩腾讯游戏时遇到过电脑突然变卡、风扇狂转、游戏帧率骤降的情况这人工智能AI AgentAgent 框架DeepSeekECC 2.0 Session Adapter Discovery面向多 Harness 的会话快照归一化设计深度解析ECC 2.0 Session Adapter Discovery面向多 Harness 的会话快照归一化设计深度解析 导读 本文系统拆解 ECC 2.0 控人工智能AI 技能AI 插件AI 评测Agent 评测MCP Clients开发工具DeepSeek Harness Python SDK 递归子代理会话树通知祖先图路由机制的设计与实现解析DeepSeek Harness Python SDK 递归子代理会话树通知祖先图路由机制的设计与实现解析 导读 DeepSeek Harness 的 Pyt人工智能AI AgentAgent 框架DeepSeek上一篇如何用OpenCV Quality模块实现专业级图像质量评估下一篇Firebase云函数开发指南从本地调试到生产部署创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表