
oh-my-piretain工具深度解析双后端长期记忆持久化的队列、直写与 Bank 作用域策略【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本文以 oh-my-pi 仓库的工具文档 docs/tools/retain.md 为主体结合 retain 工具实现、Hindsight 会话状态 与 Mnemopi 配置解析 等源码完整讲解retain工具的注册条件、输入输出协议、两条后端执行路径Hindsight 防抖批量队列 / Mnemopi 本地 SQLite 直写、bank 作用域推导规则、全部配置项与默认值、错误模型及副作用边界。读完本文你可以准确配置memory.backend并理解每次retain调用在存储层究竟发生了什么。retain 是什么面向模型的持久化事实存储工具retain是 oh-my-pi coding-agent 暴露给 LLM 的工具职责单一把可复用、跨会话的持久事实写入当前激活的长期记忆后端。工具对模型暴露的描述来自 模型侧 prompt 文件原文如下Store ≥1 fact in long-term memory for future sessions.Use: durable, reusable knowledge—user preferences, project decisions, architectural choices; anything improving future responses. No ephemeral task state.Each item MUST be specific, self-contained: who, what, when, why. Batch related facts per call; deduplicated and consolidated.这段话划定了三个使用边界只存持久的、可复用的知识用户偏好、项目决策、架构选型不存临时任务状态每条记忆必须自包含谁、什么、何时、为什么相关事实应在一次调用中批量提交后端会做去重与合并。工具元数据在 memory-retain.ts 中定义几个看似矛盾的属性值得注意元数据值含义nameretain工具名approvalread审批级别为只读——尽管成功调用会入队或执行记忆写入stricttrue严格模式校验参数loadModediscoverable可被发现discoverable加载而非默认随会话注册summaryStore important facts in long-term memory一句话摘要注册与可见性只有两个后端能点亮这个工具MemoryRetainTool.createIf(...)是注册的唯一闸门memory-retain.ts#L29-L33static createIf(session: ToolSession): MemoryRetainTool | null { const backend session.settings.get(memory.backend); if (backend ! hindsight backend ! mnemopi) return null; return new MemoryRetainTool(session); }结合 settings-schema.ts#L3007-L3033 中memory.backend的定义可见完整行为矩阵// settings-schema.ts 中的定义精简 memory.backend: { type: enum, values: [off, local, hindsight, mnemopi, sharpshooter], default: off }memory.backend默认off此时retain不存在local与sharpshooter也不注册该工具仅hindsight和mnemopi两种取值会让retain可用。注册点在 tools/index.ts#L481retain: MemoryRetainTool.createIf与recall、reflect走同一套后端选择逻辑。在无限制会话unrestricted session中若显式指定了工具列表注册逻辑会自动补齐共享的recall/retain/reflect三件套任一受支持后端均可受限工具列表则不会被拓宽。在普通tools.xdev会话中discoverable 内建工具可能以xd://retain形式呈现显式请求的工具保持顶层注册。retain执行只返回一个最终结果没有进度回调也没有取消参数execute()不接收 abort signal。输入与输出协议输入items数组输入 schema 定义于 memory-retain.ts#L6-L14const memoryRetainSchema type({ items: type({ content: type(string).describe(information to remember), context?: type(string).describe(source context), }) .array() .atLeastLength(1) .describe(memories to retain), });字段类型必填说明itemsArray{ content: string; context?: string }是一条或多条要存储的记忆minItems: 1。每条content必须自包含context是可选的逐条来源标注per-item provenance注意schema 层只约束items.length 1content字符串本身没有最小长度约束。输出由后端决定queued还是stored两个后端返回相同结构content[0].type textdetails { count: number }但文案与语义不同Hindsighttext为count memory queued./count memories queued.。写入在工具返回时并未被确认——条目只是进入会话级队列稍后由防抖或批量阈值触发 flushflush 失败会发出会话级 warning notice不会回传给模型源码注释明确写着 the LLM is not informed见 memory-retain.ts#L74-L77。Mnemopitext为count memory stored./count memories stored.。工具同步触发本地写入但rememberScoped(...)会捕获每条写入失败并返回undefinedretain忽略该返回值、仍然报告请求条数。因此该响应不是逐条持久化收据per-item durability receipt。Hindsight 路径会话级防抖批量队列当memory.backend hindsight时execute(...)重新读取一次memory.backendmemory-retain.ts#L36取session.getHindsightSessionState()若后端未启动则抛出Hindsight backend is not initialised for this session.随后把每个条目交给HindsightSessionState.enqueueRetain(content, context)。真正的核心是 HindsightRetainQueue它是每个 Hindsight 会话状态独占的防抖批量队列两个关键常量定义在 state.ts#L23-L24const RETAIN_FLUSH_BATCH_SIZE 16; // 队列深度达到 16 立即 flush const RETAIN_FLUSH_INTERVAL_MS 5_000; // 否则启动 5 秒防抖计时器enqueue(...)的行为state.ts#L84-L101队列已dispose()时抛出Hindsight retain queue is closed.条目content、context、timestamp入队队列深度 16→ 立即flush()否则若尚无计时器则启动 5 秒setTimeout防抖并且计时器unref()不为等待 retain 而挂住事件循环。flush()自身还有合并逻辑若已有一次 flush 在途则等待其完成后再检查队列是否又有新条目防止落在 flush 启动之后的条目被搁浅。flush 时的具体动作#doFlush(...)state.ts#L138-L178按序执行所有权校验state.session.getHindsightSessionState() ! state说明会话已经换状态或消失此时记录 warn 日志并丢弃整个批次——注释解释这是尽力而为的事实不是事务性写入ensureBankExists(...)尽力确保 bank 存在幂等见下文 Bank 作用域一节把每个条目映射为MemoryItemInputcontext ?? config.retainContext默认omp、metadata.session_id、bank 作用域的retainTags、入队时的时间戳发送一次异步retainBatch(..., { async: true })请求对应POST /v1/default/banks/{bank_id}/memories见 client.ts#L87-L100 的MemoryItemInput定义与 client.ts#L20 的DEFAULT_RETAIN_TIMEOUT_MS 60_000。请求超时默认 60 秒但它只约束 HTTP 往返不等待服务端 consolidation 完成。flush 阶段的 API 失败被捕获、记录日志并转换为session.emitNotice(warning, Memory retention failed for n memories: ..., Hindsight)——UI 可见模型不可见。此外HindsightSessionState.attachSessionListeners 订阅了agent_end事件每轮 agent 结束时会flushRetainQueue()主动排空队列让 bank 在轮次之间保持新鲜后端enqueue(...)/clear(...)也会显式 drain 该队列。Mnemopi 路径本地 SQLite 直写当memory.backend mnemopi时execute(...)取session.getMnemopiSessionState()未启动则抛Mnemopi backend is not initialised for this session.然后逐条同步写入memory-retain.ts#L43-L59for (const item of params.items) { state.rememberScoped(item.content, { source: coding-agent-retain, importance: 0.75, metadata: { session_id: state.sessionId, cwd: state.session.sessionManager.getCwd(), context: item.context ?? null, tool: retain, }, scope: bank, extract: true, extractEntities: true, veracity: tool, memoryType: fact, }); }每个工具调用的记忆条目都以固定身份参数落库参数值说明sourcecoding-agent-retain与自动保留的 transcript 来源coding-agent-transcript区分importance0.75高于自动保留的0.65体现模型显式要求记住的权重scopebank写入 scoped retain bank见下文作用域extract/extractEntitiestrue/true触发事实与实体抽取可能由配置的 embedding / LLM provider 执行veracitytool真实性标记为工具写入memoryTypefact事实类记忆自动保留走episodemetadata{ session_id, cwd, context, tool: retain }会话、工作目录、可选 context 溯源存储位置由 loadMnemopiConfig 决定mnemopi.dbPath未配置时默认为 agent memories 目录下的mnemopi/mnemopi.dbpath.join(getMemoriesDir(agentDir), mnemopi, mnemopi.db)每个 scoped bank 需要时各有一个数据库文件。同一会话内内容完全相同的重复条目会更新Mnemopi core 中已有的 working-memory 行而不是产生副本。失败语义与 Hindsight 不同remember(...)的失败在MnemopiSessionState.rememberInScope(...)中被捕获并记录日志不会重新抛给工具调用方因此工具响应从不暴露逐条失败。Bank 作用域策略记忆存在哪个 bankHindsightcomputeBankScope(...)三模式实现见 hindsight/bank.ts#L87-L106基础 bank id 为bankIdPrefix-bankIdbankId默认omp模式行为global单一共享 bank无项目 tagper-projectbank id 追加-project label硬隔离per-project-tagged源码默认共享 bank 每条保留记忆携带project:project labeltagrecall 侧用recallTagsMatch: any过滤使未打 tag 的全局记忆仍然可见其中 project label 的推导bank.ts#L75-L79有一个容易忽略的细节在仓库内取 git primary checkout root 的 basenamebare repo worktree 取 shared common dir仓库外取 cwd basename且整体小写化——因为 Hindsight 对 tag 做字面量匹配若不统一大小写.../General检出会写入project:General作用域而其他客户端读写的project:general永远对不上。bank 存在性保证由ensureBankExists(...)承担bank.ts#L128-L160模块级banksSet记录已 PUT 的 bank首个 flush 前以幂等PUT /v1/default/banks/{bank_id}确保 bank 存在并可选附带 reflect/retain mission失败仅记录 debug 日志并吞掉后续写入照常尝试。banksSet上限 10000超限时丢弃最旧的一半防止长驻进程下无限增长。MnemopicomputeMnemopiBankScope(...)与稳定 bank 名实现见 mnemopi/config.ts#L128-L161。由于 Mnemopi 的 recall 不支持 tag 过滤per-project-tagged被映射为项目本地写 bank 共享可读 bank模式retain 写 bankrecall 读 bankglobal共享 bank共享 bankper-project文档默认项目 bank项目 bankper-project-taggedcwd 派生的项目 bank项目 bank 共享 bank项目 bank id 由 projectBankSegment 生成cwd 绝对路径 basename净化-Bun.hash(绝对路径).toString(36)。源码注释说明了一个重要演进早期版本在哈希前先解析 git root导致在 cwd 上下删除或新增.git就会让同一会话目录改指向另一个 bank、记忆碎片化issue #2412现版本只依据 cwd 绝对路径git 查找已从该路径移除。对已碎片化的旧安装extendRecallWithLegacyBanks 会在启动时扫描dbDir/banks/下的兄弟 bank 目录上限 64 个候选把其中working_memory行的metadata_json.$.cwd全部等于当前 cwd 的安全单 cwd 银行加入 recall 集合缺失目录、不可读或损坏的 SQLite 文件一律静默跳过。会话作用域Session scope小结工具调用的 retain 是当前会话针对活跃后端的工作会话级队列 / scoped 实例持久化的 Hindsight 记忆是跨会话的服务端 bank 数据持久化的 Mnemopi 记忆是本地 SQLite 数据两个后端中subagent 都通过 alias 复用父会话的记忆状态Hindsight 的 alias 状态复用父级 bank、scope、client 与 banksSet且跳过 auto-recall / auto-retain——那些只在父级运行。配置项、默认值与限制总表以下数值来自 docs/tools/retain.md 的 Limits Caps 章节并经 hindsight/config.ts、mnemopi/config.ts 与 settings-schema.ts 交叉验证配置项默认值作用memory.backendoff后端选择器hindsight/mnemopi时retain才注册Hindsight 队列批量阈值RETAIN_FLUSH_BATCH_SIZE 16源码常量不可配队列深度达到即 flushHindsight 队列防抖RETAIN_FLUSH_INTERVAL_MS 5_000源码常量不可配5 秒内无批量则触发 flushhindsight.retainTimeoutMs60_000retain / retainBatch 客户端请求超时不含服务端 consolidationhindsight.autoRetaintrue自动保留总开关hindsight.retainEveryNTurns3每 N 个 user turn 自动保留一次hindsight.retainOverlapTurns2last-turn模式下与上一窗口的重叠轮数hindsight.retainContextomp条目缺省 context 时的填充值hindsight.retainModefull-session自动保留模式full-session/last-turnmnemopi.autoRetaintrueMnemopi 自动保留总开关mnemopi.retainEveryNTurns4每 N 个 user turn 自动保留settings-schema.ts#L3312mnemopi.scopingper-projectMnemopi bank 作用域配置解析优先级在 hindsight/config.ts#L1-L11 的头部注释中明确内置默认值 Settingshindsight.*schema 条目HINDSIGHT_*环境变量env 最后生效便于 CI / 生产环境按 shell 覆盖而不改持久化配置文件。非法枚举值会记 warn 并回退如非法retainMode回退full-session非法scoping回退per-project-tagged。错误模型与副作用边界错误一览场景行为memory.backend mnemopi但无会话状态抛Mnemopi backend is not initialised for this session.memory.backend hindsight但无会话状态抛Hindsight backend is not initialised for this session.向已dispose()的 Hindsight 队列入队抛Hindsight retain queue is closed.Hindsight flush 阶段 API 失败捕获、记日志转为 warning notice不是工具错误Hindsight bank / mission 创建失败在ensureBankExists(...)中以 debug 级别记录并吞掉后续写入照常执行Mnemopiremember(...)失败在rememberInScope(...)中捕获、记录日志、不重抛副作用清单文件系统Hindsight 不写任何本地记忆文件Mnemopi 写本地 SQLitemnemopi.dbPath默认memories dir/mnemopi/mnemopi.db每个 scoped bank 需要时各一个库文件。网络Hindsight 发起POST /v1/default/banks/{bank_id}/memoriesretainBatch以及每 bank、每会话状态首次写入前的可选PUT /v1/default/banks/{bank_id}ensureBankExistsbanksSet 在主会话状态创建并与 subagent alias 共享Mnemopi 默认无网络调用除非配置的 embedding / LLM provider 在抽取阶段发起请求。会话状态Hindsight 追加到内存队列并携带metadata.session_idsubagent 共享父级状态Mnemopi 经由会话的 scoped 实例写入携带session_id、cwd与可选contextsubagent 共享 scoped 资源。用户可见提示Hindsight 异步 flush 失败发session.emitNotice(warning, ...)模型无感知Mnemopi 写入失败仅由rememberInScope(...)记日志工具响应不暴露逐条失败。后台工作 / 取消Hindsight flush 运行在防抖计时器或队列阈值触发点flush 时所有权不匹配则记日志并丢批。Mnemopi 的事实 / 实体抽取与 embedding 可能在同步行写入之后继续执行。retain.execute()本身没有任何 abort signal 处理。与自动保留auto-retain及 clear 的关系工具路径与自动保留路径刻意分离理解这一点对排查记忆什么时候进 bank很关键继承原文档 Notes 章节Hindsight 自动保留走HindsightSessionState.retainSession(...)state.ts#L326-L373不经过工具队列它提取纯 user/assistant transcript、剥离memories/mental_models块、以单条大retain(...)提交同样async: true。full-session模式基于前缀滚动哈希retentionPrefixKey做增量缓存——rewind、分支切换、compaction 或原地编辑改写前缀都会让缓存自愈式重建避免保留陈旧内容或永久静默跳过。Mnemopi 自动保留以source: coding-agent-transcript、importance: 0.65、veracity: unknown、memoryType: episode存储准备好的 transcript——与工具路径importance: 0.75、fact形成参数上的明确分层。Hindsight mental model的 bootstrap 位于共享后端HindsightSessionState.runMentalModelLoad(...)可选解析 seeds、创建缺失模型然后把渲染好的mental_models块缓存用于 prompt 注入。内置 seeds 见 hindsight/seeds.json为user-preferences、project-conventions、project-decisionsprojectTagged: true的 seed 继承当前作用域的 retain tags未打 tag 的 seed 读整个 bank。相关默认值hindsight.mentalModelsEnabled true、hindsight.mentalModelAutoSeed true、hindsight.mentalModelRefreshIntervalMs 5 * 60 * 1000、hindsight.mentalModelMaxRenderChars 16_000首轮加载最长等待MENTAL_MODEL_FIRST_TURN_DEADLINE_MS 1500。注意seed 生命周期是 create-only修改seeds.json不会变更服务端已存在的模型。clear 语义Hindsight 存储在服务端hindsightBackend.clear(...)只 drain 本地队列、清空本地缓存/状态并明确警告上游删除须在 Hindsight UI 或deleteBank中完成Mnemopi 存储在本地mnemopiBackend.clear(...)会删除所有活跃 scoped bank 的数据库文件若会话仍活跃则重新 rehydrate 后端。同一套后端选择与作用域行为同样适用于姊妹工具可对照阅读 docs/tools/recall.md 与 docs/tools/reflect.md。小结retain是 oh-my-pi 长期记忆体系中的显式写入入口Hindsight 后端把它抽象成 16 条 / 5 秒的防抖批量异步写入失败降级为 UI 警告而非模型错误Mnemopi 后端则映射为带固定身份参数fact/importance 0.75/veracity tool的本地 SQLite 同步写入。两条路径共享同一注册闸门memory.backend、同一输入 schema但返回文案queued vs stored恰好标定了各自的持久化保证等级——queued 是尽力而为的批量投递stored 也不构成逐条持久化收据。配合三种 bank 作用域模式与 64 目录的 legacy bank 救援扫描这套设计在跨会话记忆复用、项目隔离与旧数据兼容之间给出了可配置、可验证的取舍所有关键常量与默认值均可在本文引用的源码路径中逐行核验。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考