oh-my-pi Sharpshooter 决策提取解析:从用户消息到项目记忆 Delta 的输入信封与证据门控
2026/9/12 1:44:32 网站建设 项目流程

oh-my-pi Sharpshooter 决策提取解析:从用户消息到项目记忆 Delta 的输入信封与证据门控

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

导读

本篇技术指南以 oh-my-pi 项目中的sharpshooter-extract-input.md提示词模板为核心,剖析 Sharpshooter —— 一个"摩擦门控"(friction-gated)的项目决策记忆子系统——如何从编码会话的单条用户消息中异步提取可持久化的项目决策 Delta。读者将掌握提取阶段输入信封(envelope)的三段式构建逻辑、record_deltas工具与证据(evidence)硬性校验规则、Delta 队列的落盘与消费机制,以及它与 5 分钟合并(consolidation)流水线之间的完整数据流,并可直接在配置中启用该记忆后端。

Sharpshooter 在 oh-my-pi 记忆体系中的定位

oh-my-pi 的编码 Agent(位于 packages/coding-agent)面临一个普遍难题:会话中用户做出的项目级决策("用 X 不用 Y""这是有意为之,不是 bug")会在会话结束后丢失,下次会话的 Agent 可能把代码里已反映的决策再次做错。为此,项目在 记忆后端选择器 中提供了off / local / hindsight / mnemopi / sharpshooter五种后端,其中 Sharpshooter 的定位是:

Friction-gated project decision files (architecture/product/style), consolidated in the background

以摩擦为门控的项目决策记忆:只有"踩过坑"的决策才值得记住,记忆沉淀为architecture.mdproduct.mdstyle.md三个 Markdown 决策文件,并注入后续编码会话的上下文中。从源码结构看,整个子系统分为三个阶段,本文聚焦第一阶段:

  1. 提取(extract)——每条用户消息后异步触发,产出决策 Delta,写入按会话分组的队列(见 extract.ts);
  2. 排队(queue)——每个 Delta 一个 JSON 文件,生产者无锁追加(见 queue.ts);
  3. 合并(consolidate)——按项目每 5 分钟(可配)执行一次,将队列 Delta 按"准入法"合并进三个记忆文件(见 consolidate.ts)。

提取输入信封:关联文档的完整结构

本指南的关联文档 sharpshooter-extract-input.md 是提取阶段的用户消息模板(Handlebars 模板,运行期由prompt.render(extractInputTemplate, { ...envelope })渲染,见 extract.ts)。其完整内容如下:

{{#if previousHuman}} <previous-user-message purpose="referent context only, never evidence"> {{previousHuman}} </previous-user-message> {{/if}} {{#if assistantContext}} <assistant-context purpose="referent context only, never evidence"> {{assistantContext}} </assistant-context> {{/if}} <user-prompt> {{prompt}} </user-prompt>

模板刻意将输入拆成三个区块,每个区块的用途各不相同:

区块变量purpose 标注在提取中的作用
<user-prompt>prompt(无标注,即唯一证据来源)触发本次提取的当前用户消息原文,是唯一可被引用为evidence的文本
<previous-user-message>previousHumanreferent context only, never evidence上一条用户消息,仅用于理解"短回复"类决策的语义
<assistant-context>assistantContextreferent context only, never evidence紧邻的助手回复(如选项列表),仅用于解析"opt 2""go for it"选择了什么

这种"证据与参照物严格分离"的设计,是整条证据链防伪的第一道防线:参照上下文只辅助理解,绝不允许充当证据。对应地,提取系统提示词 sharpshooter-extract-system.md 明确要求"previous user message and assistant context are referents for interpretation ONLY; they are never evidence, and nothing stated only by the assistant may become a delta"。

信封在源码中如何构建

输入信封由 buildSharpshooterEnvelope 从会话转录快照构建,其关键行为包括:

  • 定位触发消息:以当前用户消息为起点,向前扫描;若消息尚未追加进转录(message_start事件先于转录落盘),则退化为取转录中最后一条用户消息。
  • 反向查找参照物:从触发消息向前,取最近一条用户消息作为previousHuman最近一条助手消息作为assistantContext,两者都找到即停止。
  • 上下文清洗与截断cleanEnvelopeContext会将代码块(```~~~)整体替换为[code omitted],压缩空白后截断——previousHuman上限 400 字符、assistantContext上限 800 字符。这既防止大段代码撑爆上下文,也从源头杜绝"从粘贴的日志/代码中提取决策"。
  • 空消息不触发:当前消息为空或不存在时直接返回undefined,不启动提取。

系统提示词:什么才算一个 Delta

提取阶段的系统提示词 sharpshooter-extract-system.md 定义了 Delta 的六种类型(与 types.ts 的SharpshooterDeltaKind完全一致):

  • architecture_decision—— 运行时/组件边界、选定的抽象、协议或存储方向、显式的"X 优于 Y";
  • product_decision—— 行为、UX、默认值、命名/术语、产品范围(含什么、不含什么);
  • style_decision—— 视觉/审美语言、呈现约定、面向项目文本的措辞/语气规则;
  • constraint—— 用户声明的不容协商项(隐私、性能包络、兼容性、部署);
  • rejected_approach—— 被用户拒绝的尝试过或提议过的方案,以及拒绝理由(若给出);
  • correction—— 用户纠正先前已定论的行为("这不是我们约定的""X 是有意为之,Y 是 bug")。

同时规定了两类来源(SharpshooterDeltaSource):用户在消息中直接陈述的用explicit_user短回复("opt 2""go for it""split + airgap""no X plz")需要借助助手上下文解析出选择了什么时,用contextual_resolution

证据规则(硬性要求)

提示词规定:evidence必须是当前用户提示词的精确连续子串,逐字节复制。这条规则不是模型自律,而是有宿主端强制校验兜底的——见下文"准入门控"。

摩擦标签(Friction tags)

每个 Delta 必须诚实打上三个布尔摩擦标签,因为合并阶段"按摩擦准入,而非按存在准入":

  • corrective: true—— 用户在重申或纠正先前已定论的内容;
  • regression: true—— 用户报告原本正常的行为破坏、漂移或"回退"了;
  • subtle: true—— 仅凭代码难以一眼看出的非显式不变量(跨组件预期、"A 与 B 有别"的区分、代码无法自描述的意图)。

三个全为false也是合法结果,代表一次干净的首决决策。

陈述规则

  • 永恒规范式:"Status bar uses powerline-style segments",绝不用"用户想要""目前""我们刚修了";
  • 不含文件路径、行号、函数/类型名、提交 ID 或 issue 号;产品组件词汇(composer、status bar、daemon 名)允许;
  • 排除当前任务态:正在修的 bug 不是决策;但关于先前已定论行为的 bug 报告是correctionregression: true
  • 不提取与项目决策无关的个人品味(偏好语言、通用编码哲学、提交风格);
  • 不从不带引号的粘贴材料(日志、diff、文档)提取,只从人类话语中提取;
  • rejectedAlternative/rationale仅在用户确实给出时才填,严禁臆造。

record_deltas 工具与准入门控

提取模型被要求恰好调用一次record_deltas工具(toolChoice: "required"),空数组是合法且常见的结果——大多数消息不包含持久决策。工具的参数 schema 在 extract.ts 中由 omptype 定义:

const deltaSchema = type({ kind: "'architecture_decision' | 'product_decision' | 'style_decision' | 'constraint' | 'rejected_approach' | 'correction'", statement: "string", "rejectedAlternative?": "string", "rationale?": "string", source: "'explicit_user' | 'contextual_resolution'", evidence: "string", friction: { corrective: "boolean", regression: "boolean", subtle: "boolean", }, });

模型吐出的每个候选 Delta 都要经过 admitDelta 的宿主端强制校验(而非仅依赖模型自觉),任一条件不满足即被丢弃:

  1. statement必须是非空字符串;
  2. evidence必须是非空字符串,且必须是当前 prompt 的包含子串currentPrompt.includes(raw.evidence))——直接实现"证据硬规则",防止模型用参照上下文或自编内容充当证据;
  3. kind必须在六种类别白名单内(SHARPSHOOTER_DELTA_KINDS);
  4. source必须在两类来源白名单内(SHARPSHOOTER_DELTA_SOURCES);
  5. friction三个字段必须均为布尔值(parseFriction)。

通过校验后,Delta 被落盘为结构化对象(v: 1版本号 +sessionId+ts时间戳),进入队列。另外,提取模型解析、推理开销被刻意压低(maxTokens: 2048Effort.Low思维级别),并默认回退到smol角色模型,且整个流程不阻塞主会话(fire-and-forget,maybeStartSharpshooterExtraction只记录 in-flight Promise)。

队列:无锁、幂等、可重投递

队列实现见 queue.ts,核心设计是每个 Delta 一个 JSON 文件,路径为queue/<sessionId>/<ts36>-<nonce>.json

  • 文件名即排序键:时间戳以 36 进制、零填充 10 位,保证字典序等于时间序;4 位随机 nonce 化解同一毫秒的并发碰撞;
  • 生产者无锁:没有共享的追加 fd 与消费者的重命名竞争,崩溃安全;
  • 幂等消费:合并是先全量重写记忆文件再删除队列文件,若"应用后、删除前"崩溃,只会导致同批 Delta 被重投递一次,而合并本身"最新者胜"(newest-wins),重复应用无副作用;
  • 脏数据免疫:读取时校验v === 1statement为字符串,无法解析的撕裂文件跳过不消费(listSharpshooterDeltas),不会卡死队列;
  • 会话 ID 净化sanitizeSessionId把路径不友好字符替换为-,作为目录名防线;
  • 消费清理consumeSharpshooterDeltas删除已消费文件并递归清理空会话目录。

队列深度的聚合由sharpshooterQueueDepth提供,供状态展示与调度器判断是否到期。

合并:摩擦准入法与三文件记忆库

提取只是上游,真正决定"什么值得记住"的是合并阶段,其系统提示词 sharpshooter-consolidate-system.md 定义了三条核心法则:

  1. 准入法(Admission law)——记忆靠摩擦挣得,而非靠"决策存在":只有血统中至少出现以下之一才准入:
    • 回归(Regression):已定论的行为在任何时点被破坏或漂移(一次即可);
    • 微妙性(Subtlety):仅凭代码看不出的规则——隐形不变量、跨组件预期、"A 与 B 有别"、代码无法自描述的观感意图;
    • 重复(Repetition):跨会话两次以上纠正,或一次明确反转且被拒方案仍是"活诱惑"。
    • 反例:"一次性决策、实现后再没做错过"不是记忆——代码已反映它,单次交流存储细节正是错误记忆的温床。拿不准就略过
  2. 具体性测试(Concreteness test):每条要点必须能改变新 Agent 的行为。"提供可配置、兼容引用的交互"是删除级;"状态栏:powerline 分段、实心右缘、内收左三角、不加粗、耗时 ≤3 字符"是保留级。
  3. 单归属规则(Single-home rule):每条决策只住一个文件——architecture.md(结构/运行时)、product.md(行为/UX/范围/命名)、style.md(视觉/排版/语气)。

此外还有硬排除项(与项目自身文档重复的内容、全局个人品味、进行中的任务态、路径/行号/符号/提交号)、更新语义(最新者胜、跨会话同主题合并为一条、保留仍有效的旧条目、##分节、每条要点一行、每文件硬性 ≤120 行预算SHARPSHOOTER_MAX_FILE_LINES,见 types.ts)、无状态陈述)。

合并实现(consolidate.ts)还包含多项工程化保障:

  • 跨进程单写者锁withFileLock("consolidate.lock"),重试 1 次,拿不到锁即返回locked
  • 完整重写而非补丁:模型必须恰好一次调用replace_memory_files并给出三个文件的完整新内容(toolChoice: "required"maxTokens: 8192Effort.Medium);
  • 防清库护栏:若现有记忆文件非空而模型返回全空内容,直接拒绝("refusing to wipe memory files");
  • 密钥脱敏redactSecrets对 sk/pk/token/password 类长串、JWT 形串、AWS/GitHub/npm/Slack/Google API 密钥模式统一替换为[REDACTED]
  • 临时文件原子替换:先写.name.pid.uuid.tmp,再rename覆盖,失败时清理残留;
  • 项目文档去重:读取cwdAGENTS.md/CLAUDE.md(截断至 6000 token)作为"已文档化内容",禁止记忆重复;
  • 调度器(scheduler.ts):按bankDir共享定时器、引用计数释放,每分钟 tick,队列非空或距上次合并超过intervalMinutes才执行。

配置启用与存储布局

要在 oh-my-pi 编码 Agent 中启用 Sharpshooter,配置项位于 settings-schema.ts:

配置键类型/默认值说明
memory.backendenum,默认off设为sharpshooter即启用该决策记忆后端
sharpshooter.modelstring,默认空提取/合并使用的模型选择器,留空回退smol角色
sharpshooter.intervalMinutesnumber,默认5合并轮询间隔(分钟),与调度器DEFAULT_INTERVAL_MINUTES一致
sharpshooter.injectionTokenLimitnumber,默认15000记忆文件注入会话上下文的 token 上限

存储全部位于主目录作用域下的<agentDir>/memories/sharpshooter/<bank>/(见 paths.ts),不写入项目工作树<bank>cwd通过projectBankSegment稳定推导,同一项目跨会话共享同一记忆库。布局为:

  • architecture.md/product.md/style.md—— 三个决策记忆文件(按序注入上下文);
  • queue/<sessionId>/<ts36>-<nonce>.json—— 每个排队 Delta 一个文件;
  • state.json—— 合并记账(lastConsolidatedAtlastResultlastError,供/memory stats/memory diagnose使用);
  • consolidate.lock—— 跨进程单写者锁。

小结

从 sharpshooter-extract-input.md 这个短小的输入模板出发,可以看到 oh-my-pi Sharpshooter 记忆后端的一整套严谨设计:三段式输入信封严格隔离"证据"与"参照物",宿主端 evidence 子串校验摩擦标签共同杜绝错误记忆,无锁文件队列 + 幂等消费保证崩溃安全,5 分钟合并 + 准入法 + 120 行预算把记忆收敛为精炼、可改变新 Agent 行为的项目决策文件。对于希望为编码 Agent 构建"会记住项目约定"的记忆系统的开发者,这套"提取 → 排队 → 摩擦门控合并 → 注入上下文"的流水线,是一个可复用的完整参考实现。

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询