9.2 KiB
连续记忆第一阶段:实现与验证
状态:2026-10-09,源码分支 feat/supervisor-memory-v1。涉及 deskpaw_agent 和
deskpaw-memory;尚未替换或重启正式服务。本文描述已实现边界,不把架构目标当作已有能力。
执行路径
Runtime 持久事件
├─ 上层收件箱:终态、阻塞、需要输入 → 下次决策上下文(只读)
└─ MemoryBridge 后台同步 → Memory 事件表 → 观察批次租约
→ 模型提案 → 校验并原子发布
用户消息 → 限时索引召回/缓存 → 上层自然语言+控制工具 → Runtime 校验与执行
└─ 迟到结果持久化 → 上层收件箱/按请求 ID 查询
上层是主要消费者。Pi 在派发后的执行准备阶段也能读取有限工作包,并通过
memory_evidence_get 按 source_refs 展开证据;证据读取没有电脑操作权限。
上层不等待观察模型、记忆写入或 Pi 停止完成。本地只读检索有 75 毫秒等待预算;
超时继续后台处理,上层得到 pending,不能据此回答“没有记忆”。这是共享 Node 进程中的
等待上限设计,不是 CPU 饱和时的硬实时保证。执行器已发出的原子操作仍需合作式停止。
谁在什么时候处理什么
| 部分 | 输入与触发 | 模型与限制 |
|---|---|---|
| 事件筛选和任务投影 | 每秒检查持久事件;任务结束主动唤醒 | 程序,不调用模型 |
| 新观察 Worker | 最多 20 条事件;最早事件等约 30 秒;结束、失败、阻塞、需输入提前触发 | 独立记忆角色,reasoning=low、输出上限 3000、90 秒超时、并发 1 |
| 旧话题/事实整理 | 新观察队列没有可处理批次时;每分钟检查;闲置或夜间复核 | 同一记忆角色;保留旧协议,90 秒超时;不占 Pi FIFO |
| 上层决策 | 用户消息 | 当前上层角色;自然语言正文和一次原生控制调用分开 |
| 召回与来源展开 | 用户问题、Pi 任务准备、显式证据读取 | 中文/英文索引、ID 与 RRF,不另开模型 |
本机检查到的执行和上层模型配置均为 deepseek / deepseek-v4-flash;本次不修改运行配置。
新观察角色默认回退到执行角色,允许独立指定进程环境:
DESKPAW_MEMORY_MODEL_PROVIDER=deepseek
DESKPAW_MEMORY_MODEL=deepseek-v4-flash
DESKPAW_SUPERVISOR_CONTEXT_BYTES=32000
这些是 Agent 进程使用的变量,不是要求 Memory SQLite 服务直接连接模型。 新变量暂未加入设备配置工具的可编辑白名单。现有上层模型/reasoning/timeout 变量继续有效。 是否换更小的观察模型,应以带来源提取、遗漏率、错误归属率的离线样本评估决定;本轮没有调用付费模型做质量测评。
事件与持久性
来源包括已完成的用户交互及上层回应、任务启动与终态、Pi 的失败/改策略/里程碑/阻塞/需输入报告。
没有把 agent_message_end、隐藏推理、普通点击、图片和原始工具大对象交给观察模型。
无变化的日程终态和日程启动被过滤。重复内容在同一批次内合并计数,仍保留每个原始事件 ID。
敏感文本做关键词防线式脱敏;这不是能识别任意未标注凭据的完整 DLP 系统。
Runtime 的 events.seq 是发送位置;memory_event_cursor_v1 只在 Memory 确认所有 ID 后推进。
Memory 接入以 event_id 幂等,重复 ID 不同内容会被拒绝。旧任务卡投影也会跳过完全相同的重复写入。
事件每条正文上限 2400 UTF-8 字节,观察工作包选取预算 12000 字节。队列状态为:
待处理事件 → 带 token 的 120 秒租约 → 成功事务:经历+索引+来源已消费+回执
└→ 失败:保留来源,退避重试
租约过期可重领同一批;新 token 会拒绝旧 Worker 的迟到提交。提交成功而响应丢失时, 同一租约重试返回原回执,不重复发布。模型必须引用或明确丢弃所有本批事件,程序核对完整覆盖。 来源不在批次、对象无相应来源、候选偏好缺少用户逐字证据的提案均不能消费批次。
首版经历为只追加记录,带设备发布序号;未实现可覆盖事实的对象级 CAS 和自动冲突消解。 反复失败的批次会持续退避,不会默默丢弃;死信队列和人工重放界面是后续管理工作。
召回、回调和安全边界
新经历使用倒排表查找,不限于最近 100 次任务。融合正文、召回线索和显式对象 ID;正文证据的
排序权重高于模型生成的线索。召回结果携带 reported/candidate 状态,不能被当作已验证事实或已批准规则。
新经历与旧资料库并存;旧话题仍有全表排序成本,后续需要迁移,不宣称所有旧数据已完成索引化。
上层总文本预算默认 32000 UTF-8 字节,先扣除固定提示词和工具模式,再保留完整当前请求与实时状态。 日志、历史和记忆有单独裁剪及省略标记。过长的当前请求拒绝派发,不静默截断后执行。 这不是供应商精确 token 计数;中文不能用“4 字符等于 1 token”来保证预算。
暂停返回“已暂停队列、正在停止”,恢复返回“已接收,待检查”。改向/取消当前任务在模型调用前 捕获任务 ID 和控制修订号,持久化决定后再核对;A 完成、B 接替或 A 已被另一路修改时,旧指令不执行。
关键执行事件和召回结果进入 supervisor_inbox,重复阻塞按任务控制版本合并,用户交互优先消费。
interaction_seq 区分迟到召回;迟到结果保留,但不自动操作新的任务。
新增只读 Agent 接口:
GET /v2/attention:未消费的上层注意事项。GET /v2/memory/requests/<request_id>:召回状态及有界结果。
这一步没有新增主动聊天通知,也没有让回调自行调用模型继续执行。 现有任务结果仍走原来的 Gateway 输出渠道;新记忆的迟到查询结果进入缓存和收件箱, 在后续上层交互中使用,也可按 ID 查询。下一步才加入可中断的只读回调评估、通知去重和渠道投递确认。 首次查询超过等待预算时,当前回复可能只说明查询中,不承诺已自动补发最终回答。
当前 owner=device:<session_id> 约束新索引与证据接口。旧全局资料仍按单设备共享会话使用。
这不是多租户身份认证;本地 socket 的受信边界没有改变。
开源复用落点
- T-Mem:实际移植 RRF 算法到
src/rank-fusion.js,保留 MIT 声明及版本;增加通道内去重和正文加权。 - Mastra:借鉴观察批次、显式来源引用、准备/发布分离与异步结果归属;没有把整个框架或其可能等待后台工作的激活路径嵌进上层。
- Letta Code:借鉴小型常驻区、按需证据展开、提案成功落地后才消费输入,以及偏好/经历/操作技能分开;没有复制 Git 工作树管理来替代 SQLite。
Mastra 与 Letta 本阶段属于协议和流程适配,不是宣称原样导入它们的模块。
后续顺序
- 对象注册和关联:责任领域、跨月项目、软件资产与多个日程共用稳定 ID;目前适配器自动归属仅到会话/任务/日程,未自动识别所有项目。
- 只读回调评估及主动通知:用户消息能抢占,过期回调归档,通知有去重和送达确认;操作仍须满足原授权。
- 候选复核、纠正取代、遗忘/删除、保留期限与人工重放;然后迁移旧话题索引和补充向量通道。
- 将观察 Worker/Pi 隔离到独立进程,做持续负载和模型限流测试,保证 H618 上的调度公平性。
财务示例中,发票日常整理与月度提交应归同一报销领域,月度提交使用每月实例;维护软件则绑定 资产和独立修复实例,休眠时只保留索引,相关报错再次出现才展开经历。这些对象关系是下一步,而不是在 首版按时间摘要上贴一个“项目记忆”名称。
验证
2026-10-09 本机结果:Memory 25 项通过;Agent 上述四个测试文件共 54 项通过;
Agent 完整 npm run check 通过;两个仓库的 git diff --check 通过。
Memory:npm test,隔离数据库与临时 Unix socket,覆盖幂等、租约恢复、来源校验、中文跨窗口检索、
重复观察、敏感文本、跨日复核,以及真实服务进程重启后的回执与证据读取。
Agent:仓库根目录 npm run check(包括格式、锁文件、TS、浏览器冒烟检查);
在 packages/coding-agent 执行:
node ../../node_modules/vitest/dist/cli.js --run test/device-continuity.test.ts test/device-runtime.test.ts test/device-memory.test.ts test/device-session.test.ts
这些测试使用离线模型与假 Executor,不操作真实桌面,不访问真实模型 API。覆盖慢记忆服务、 后台观察与前台解耦、暂停回执、过期目标、天然语言与控制分离、会话恢复、日程以及证据筛选。 正式环境验证和部署仍需单独进行;部署前备份 Runtime/Memory SQLite(含 WAL 一致性),先更新 Memory 协议再更新 Agent。