【oh-my-pi】Hashline+TTSR+Worktree 三大原语深度解析
【oh-my-pi】Hashline + TTSR + Worktree 三大原语深度解析:21k⭐ 的 IDE 化 Coding Agent 如何重新定义 Harness 边界
引子
如果你用过 str_replace 类的 Edit 工具,你大概率被这种场景折磨过:
模型返回:
old_string: " const data = await fetch(url);"
实际文件:const data = await fetch(url, { cache: "no-store" });
结果:string not found,整个 patch 失败。
这类”差一个空格就重写整段”的失败,本质是 Edit 工具在用”内容匹配”做”位置识别”。一个本应该是定位工具(”改第 5 行”)被错当成替换工具(”把这段字串换成那段字串”),而 LLM 又恰好在 whitespace 上不稳定,所以失败率居高不下。
can1357/oh-my-pi(21,847 ⭐,MIT,Bun + Rust + TypeScript 三栈合一,commit 频率 8 月仍日更)给出的答案是 Hashline:
Edit 不再用”匹配字符串”做锚点,改用 行号 + 行内容 xxHash32 截断 做 anchor,外加文件级 4 字符指纹做 stale-detection。模型只需说”改第 5 行”——如果第 5 行真的不在了,patch 会被拒绝而不是猜。
这只是 oh-my-pi 21 个特性之一。今天我们聚焦 三个最具 Harness 价值的原语:
- Hashline — 内容哈希锚定的 Edit 协议(让 60+ 模型都能可靠做 Edit)
- TTSR (Time-Traveling Stream Rules) — 流式中断 + 规则注入(让规则”按需上场”而不是”霸占 system prompt”)
- First-class Sub-Agent + Worktree 隔离 — 用 PID + start-token 双因子防伪的 owner 标记
每一个都是其他 Harness(Claude Code、Codex、Pi-Mono、Cline)没有做对或完全没做的硬骨头。
项目概览
| 指标 | 值 |
|---|---|
| GitHub | can1357/oh-my-pi |
| 起源 | Fork of badlogic/pi-mono(Mario Zechner 的 Pi) |
| Stars | 21,847 ⭐(截至 2026-08-04) |
| Forks | 2,080 |
| 语言 | TypeScript + Rust(N-API cdylib)+ Bun runtime |
| License | MIT |
| 主仓库体积 | 473 MB(含 80k 行 Rust core) |
| 核心特性 | 31 内置 tools · 14 LSP ops · 28 DAP ops · 60+ 模型 providers |
| 最近 commit | 2026-08-04(每日更新) |
核心定位:在 Pi-Mono(极简 4 工具哲学)基础上,叠加”IDE wired in”——把所有原本需要 fork-exec 调起的 ripgrep/find/grep/bash 全部做成进程内 builtin,把 LSP/DAP 协议、Python+Bun 双核 eval、worktree 隔离原生集成到 Agent 循环里。
“The most capable agent surface that ships. Continuously tuned by real-world use — complete out of the box, open all the way down.” — 项目自述
与上游 Pi-Mono 的差异(避免重写之前的文章)
| 维度 | earendil-works/pi(2026-05-15 文章) | can1357/oh-my-pi(本文) |
|---|---|---|
| 核心哲学 | 4 工具极简 + Skill 机制 | 21 特性全开 + IDE 全集成 |
| Edit 协议 | 经典 str_replace | Hashline(内容哈希锚定) |
| 规则系统 | 静态 system prompt 注入 | TTSR(流式中断 + 按需注入) |
| 代码理解 | 无 LSP | workspace/willRenameFiles 桥接 + 14 LSP ops |
| 调试 | 无 | DAP(lldb / dlv / debugpy)+ 28 ops |
| 运行时 | Node fork-exec ripgrep/grep | Rust 内核 in-process 调用 ripgrep library |
| Shell | bash fork 子进程 | brush 嵌入式 shell + 46 coreutils builtin |
| 隔离 | 无 | Worktree + PID+start-token 双因子 owner |
| 协同 | 无 | Advisor(独立模型每轮检视) |
下面我们深入三大原语。
一、Hashline:让 Edit 协议从”匹配字串”升级为”内容锚定”
1.1 痛点:为什么 str_replace 在 2026 年还在”折磨人”
主流 Coding Agent 普遍用 str_replace(old_string, new_string) 类接口。问题有三层:
- 空白敏感:模型输出的缩进 / 换行 / tab 跟实际文件差一个字符就 miss
- 多匹配歧义:
old_string在文件出现 3 次,工具只能 reject 或”全替换” - 并发漂移:Agent 跨 tool call 编辑时,旧 anchor 已被前面工具改掉,patch 静默失败
oh-my-pi 团队在 benchmark 里实测数据如下(README 直接引用):
| 模型 | str_replace 失败率 | Hashline 失败率 | 变化 |
|---|---|---|---|
| Grok Code Fast 1 | 93.3% | 31.7% | 6.7% → 68.3% 通过率 |
| Gemini 3 Flash | 基准 | +5 pp | Hashline 超过 Google 自家最佳 |
| Grok 4 Fast | 基准 | −61% tokens | 输出 token 因重试循环消失而坍塌 |
| MiniMax | 基准 | 2.1× | 同权重同 prompt,通过率翻倍 |
Hashline 核心思想:Edit 不再是”在文件里找字串”,而是”指向文件第 N 行(用行内容哈希验证)”。
1.2 协议格式:4 段语法 + 4 字符文件指纹
一份 Hashline 写文件操作长这样(README 示例):
1 | [src/foo.ts#1A2B] |
拆开看:
graph LR
A["[src/foo.ts#1A2B]"] --> B["文件头\npath + 4 字符\nxxHash32 截断"]
C["PUT 5.=7:"] --> D["Hunk 头\n行范围 5-7"]
E["+export function..."] --> F["Body 行\n+ 前缀 = 新增"]
G["PUT >12:"] --> H["Gap 头\n>12 = 第 12 行之后\n<$ = 文件末尾"]
style A fill:#C7CEEA,stroke:#888,color:#333
style C fill:#FFDAB9,stroke:#888,color:#333
style E fill:#B5EAD7,stroke:#888,color:#333
style G fill:#E8D5F5,stroke:#888,color:#333每个语法元素对应 packages/hashline/src/format.ts 的常量:
1 | // packages/hashline/src/format.ts (核心常量) |
1.3 文件指纹算法:xxHash32 截断 4 字符
为什么是 4 字符?这是一个精心设计的 collision budget。4 hex = 16 bit = 65,536 桶,碰撞概率按工作集大小估算:
1 | // packages/hashline/src/format.ts (computeFileHash) |
两个关键设计:
- normalize 行尾空白 —— CRLF 文件 vs LF 文件,git 改过换行符的文件,hash 都一致。这让”我们改了一行换行符”不会让所有 anchor 失效
- 取低 16 位 —— 完整 xxHash32 是 8 hex,4 hex 足够区分”当前 agent session 内能看见的几个文件”。即使发生 collision,patcher 会在 recovery 阶段用
diffLineRuns进一步验证
1.4 行内 anchor:行号本身就是稳定标识
Hashline 不需要重新发明行号——它直接复用 1-indexed 行号。关键创新是 “anchor + 行内容校验”:
1 | // packages/coding-agent/src/edit/hashline/block-resolver.ts (节选) |
意思是:如果第 N 行已被改动(找不到),不要立即报错,而是向后 / 向前 64 行扫,用 tree-sitter 解析”最近的语法块”作为 fallback anchor。
1.5 真实可运行代码:用 Node 验证 xxHash32 哈希
oh-my-pi 内部用 Bun 的 Bun.hash.xxHash32,但协议本身是平台无关的。我们用 Node 22 自带的 Bun.hash 替代品(node:crypto 的 FNV-1a 截断 16 位作为可观察的等价实现)来验证:
1 | // verify-hashline-tag.js |
运行结果:
1 | file1 tag: 4B3A |
这就是 Hashline 的 stale-detection 心智模型:文件整体指纹决定 patch 是否还能用,而不是逐行匹配。
1.6 Recovery 算法:anchor 失效时如何不静默错
packages/hashline/src/recovery.ts 的 buildLineMap 是 Hashline 最优雅的部分:
1 | // packages/hashline/src/recovery.ts |
如果 patch 里的 anchor 5 在当前文件已经移到 7,patcher 查 map.get(5) === 7,把 anchor 5 替换为 7 然后 replay。但 recovery 是 best-effort + 失败时 refuse(fail-closed),从不”猜”。
1.7 规则应用:ast-grep 作为 TTSR 的二次校验
Hashline 不止是 anchor 协议。当 TTSR 规则在 edit 工具流上检测时,Hashline 还能用 ast-grep 校验 patch 后 AST 是否仍合法:
1 | // packages/coding-agent/src/export/ttsr.ts |
为什么这个分层重要:如果只用正则,会被”注释 / 字符串字面量”骗过;只用 AST,又无法在”还没成 AST 的中间态”(模型正在 stream 输出 old_string)触发。两者并存 → 文本流靠正则,结构性破坏靠 AST。
二、TTSR:流式中断 + 按需注入的”时间旅行”规则
2.1 痛点:Rule 系统的”上下文税”
传统 Rule 系统(Cursor Rules、Cline Rules、AGENTS.md)都有同一个问题:
规则写在 system prompt,每轮都付上下文税。即使这一轮完全用不上”不要用
Box::leak“,这 50 token 也要跟着 prompt 走完整个会话。
oh-my-pi 的解法叫 TTSR (Time-Traveling Stream Rules):
规则平时不在 system prompt。当 agent 的 stream 输出(文本/thinking/tool call)匹配规则的 condition 时,当场 abort 当前 stream,把规则作为 system reminder 注入,从同一断点 retry。
sequenceDiagram
participant U as User
participant A as Agent (LLM)
participant S as Stream
participant T as TtsrManager
participant R as Rule Repo
U->>A: "Refactor src.rs"
A->>S: stream token 1, 2, 3...
S->>T: checkMessageUpdate("...write Box::leak...")
T->>R: compile "Box::leak" conditions
R-->>T: 2 regex + 0 ast
T->>T: match? YES (rule "no-box-leak")
T->>S: ABORT stream (保留已生成的 token)
T->>A: inject "Don't reach for Box::leak" as system reminder
A->>S: RETRY from same checkpoint
S->>T: checkMessageUpdate("...use Arc<str>...")
T->>T: match? NO
A->>U: 最终输出用 Arc<str>
style U fill:#C7CEEA,stroke:#888,color:#333
style A fill:#E8D5F5,stroke:#888,color:#333
style S fill:#FFDAB9,stroke:#888,color:#333
style T fill:#FFB3C6,stroke:#888,color:#333
style R fill:#B5EAD7,stroke:#888,color:#3332.2 TTSR 5 大配置原语
来自 docs/ttsr-injection-lifecycle.md 的实际字段:
| 原语 | 字段 | 作用 |
|---|---|---|
| condition | rule.condition: string[] | 文本/thinking 流上的正则(命中即 abort) |
| astCondition | rule.astCondition: string[] | edit/write 流上的 ast-grep 模式(仅结构性破坏触发) |
| scope | rule.scope: string[] | 限定触发通道:text / thinking / tool:edit(*.ts) 等 |
| interruptMode | rule.interruptMode: "never"|"prose-only"|"tool-only"|"always" | 何时中断 |
| repeatMode | ttsr.repeatMode: "once"|"gap:N" | 同一规则是否重复触发 |
packages/coding-agent/src/capability/rule.ts 中的标准化结构:
1 | export interface Rule { |
2.3 三大原语对照表(讲 TTSR 必查的清单)
调研 TTSR 类项目时,按以下 3 个原语对照检查,能完整覆盖 = 高价值选题:
| # | 原语 | oh-my-pi 对应实现 | 核心字段/方法 |
|---|---|---|---|
| 1 | Mid-Stream Abort | TtsrCoordinator.checkMessageUpdate() | 收到 message_update 事件立即 check,命中即返回 true 让上层 abort |
| 2 | Rule Injection 持久化 | #pendingInjections + #injectionRecords | 注入要进 system reminder,且记录 lastInjectedAt 支持 repeat gap |
| 3 | Same-Checkpoint Retry | #retryToken + #resumePromise | abort 时不丢弃已生成 token,从断点 retry,注入的 rule 跟着新 turn 走 |
2.4 真实可运行代码:TTSR 检测 + 注入的最小实现
把 TtsrManager 简化到 80 行,写一个可独立运行的 mini-TTSR:
1 | # mini_ttsr.py |
跑一次:
1 | $ python3 mini_ttsr.py |
这正是 oh-my-pi README 里那张 ttsr-poster.webp 描述的流程——只不过他们用 TypeScript + 真 LLM。
2.5 Injections 持久化:跨 compaction 还能用
TTSR 另一个被忽略的工程细节:注入的规则要在 compaction 后还能生效。docs/ttsr-injection-lifecycle.md 明确写:
“Injections survive compaction, so the fix sticks.”
意思是:如果 session 跑了 200 轮触发了 compaction,已注入的 rule 不能被压缩掉。具体实现靠 TtsrManager 内部把已触发的 rule 标记为 promoted-to-alwaysApply,compaction 时被识别为”已沉淀的工程约束”。
2.6 TTSR 与传统 Rule 的对比
| 维度 | Cursor Rules(传统) | TTSR(oh-my-pi) |
|---|---|---|
| 加载时机 | 启动时一次性进 system prompt | 按需 mid-stream 注入 |
| 上下文税 | 每轮都付 | 仅触发时付一次 |
| 误报成本 | 规则与 task 不相关也要带 | 规则不会因”未触发”被看见 |
| 结构性检测 | 仅文本检测 | ast-grep 检测 edit/write 流的 AST 破坏 |
| 触发位置 | 仅在 system prompt 层 | 文本 / thinking / tool call input 三层 |
| 跨 compaction | 永久 | 永久(promoted-to-alwaysApply) |
三、First-Class Sub-Agent + Worktree 隔离:PID + start-token 双因子防伪
3.1 痛点:Sub-Agent 写脏了 parent 怎么办
多 Agent 系统最大的灾难是 sub-agent 写脏 parent 工作区:
你让一个 sub-agent 跑”重构 auth 模块”,
它同时改了auth.ts和package.json,
但你只想要auth.ts的改动,
结果 parent 已经把 package.json 也 commit 了。
oh-my-pi 的解法:每个 sub-agent 跑在独立 worktree,结束用 merge --autostash 选择性合并。但 sub-agent 的 worktree 如何跟 parent 进程对应?答案是 PID + start-token 双因子 owner 标记。
3.2 架构全景:Sub-Agent 的 4 层隔离
graph TB
subgraph "Parent Process"
P["parent session\n(task tool caller)"]
end
subgraph "Worktree 隔离层"
W1["wt/task-abc/\n.omp-isolation-owner.json"]
W2["wt/task-def/\n.omp-isolation-owner.json"]
end
subgraph "Sub-Agent Runtime"
S1["subagent A\n独立 Settings + ToolSet"]
S2["subagent B\n独立 Settings + ToolSet"]
end
subgraph "Outcome Yield"
Y1["typed Findings (schema-validated)"]
Y2["IRC DM note (one-sided)"]
end
P -->|task fans out| S1
P -->|task fans out| S2
S1 --> W1
S2 --> W2
S1 -->|yield| Y1
S2 -->|yield| Y2
Y1 --> P
Y2 --> P
style P fill:#C7CEEA,stroke:#888,color:#333
style W1 fill:#FFDAB9,stroke:#888,color:#333
style W2 fill:#FFDAB9,stroke:#888,color:#333
style S1 fill:#E8D5F5,stroke:#888,color:#333
style S2 fill:#E8D5F5,stroke:#888,color:#333
style Y1 fill:#B5EAD7,stroke:#888,color:#333
style Y2 fill:#FFB3C6,stroke:#888,color:#3333.3 PID + start-token 双因子:为什么 PID 单独不够
packages/coding-agent/src/task/isolation-ownership.ts 的核心设计:
1 | // packages/coding-agent/src/task/isolation-ownership.ts |
PID 单独不够的原因:Linux PID 是 32-bit 整数,会被回收。假设:
ompPID 12345 创建了 worktree A,写 owner{pid: 12345}omp进程崩溃,PID 12345 被回收- 几小时后另一个
node进程拿到 PID 12345 omp worktree clear看到 worktree A 的 owner PID “活着”,但它已经不是创建它的那个 omp 进程——如果清掉,会清掉别人正在用的 worktree
start-token 救场:从 /proc/<pid>/stat 的 field 22(启动时间,单位是 clock ticks since boot)读出来。Linux 保证同一 (pid, starttime) 不会同时对应两个进程。字符串对比即”是不是同一个进程”。
3.4 真实可运行代码:双因子 owner 验证
1 | # verify_isolation_owner.py |
跑一次:
1 | $ python3 verify_isolation_owner.py |
oh-my-pi 用 Bun + N-API 调 /proc/<pid>/stat 拿 starttime;我们用 Python pathlib 演示等价逻辑。核心安全属性一致:仅靠 PID 校验会被 PID 回收绕过,仅靠 path 校验会被移动绕过,必须 (pid, starttime) 二者都吻合。
3.5 实战流程:sub-agent 从 spawn 到 yield
来自 docs/task-agent-discovery.md 的完整链路:
sequenceDiagram
participant P as Parent
participant T as task tool
participant D as Discovery
participant W as Worktree Manager
participant S as Sub-Agent
participant Y as Yield Schema
P->>T: task("review", {agent: "scout", schema: Findings})
T->>D: loadCapability("agents")
D-->>T: AgentDefinition{tools, spawns, model}
T->>W: ensureIsolation("wt/task-abc")
W->>W: write owner {pid, startToken}
W-->>T: worktree ready
T->>S: createAgentSession(worktree, isolated settings)
S->>S: independent loop
S-->>Y: yield typed object
Y->>Y: schema.validate(Findings)
Y-->>P: Findings (validated)
P->>W: merge --autostash wt/task-abc
style P fill:#C7CEEA,stroke:#888,color:#333
style T fill:#E8D5F5,stroke:#888,color:#333
style D fill:#FFDAB9,stroke:#888,color:#333
style W fill:#FFB3C6,stroke:#888,color:#333
style S fill:#B5EAD7,stroke:#888,color:#333
style Y fill:#FFF9C4,stroke:#888,color:#333关键设计:
- AgentDefinition 是 frontmatter 描述的——
name、description、systemPrompt、tools、spawns、model、output(typed schema) - output 是 schema-validated——sub-agent 必须 yield 符合 schema 的对象,否则 parent 拿到的是
null+ error,不是 prose - merge 是 selective——只 merge sub-agent 实际改的文件,不会污染 parent 的 working tree
3.6 与其他 Sub-Agent 实现的对比
| 维度 | LangChain AgentExecutor | OpenHands | oh-my-pi task |
|---|---|---|---|
| 隔离 | 无(共享 working dir) | Docker container | Worktree + owner marker |
| Spawn 协议 | 函数调用 | RPC + 镜像 | frontmatter + capability discovery |
| Yield 类型 | str | str | schema-validated object |
| 跨 sub-agent 通信 | 通过 parent | 通过消息总线 | 通过 parent yield 链 |
| 失败恢复 | 抛异常 | 重试 | 保留 typed error 让 parent 决定 |
| 上下文隔离 | 共享 memory | 独立 memory | 独立 Settings + ToolSet |
最关键差异:schema-validated yield。LangChain 的 sub-agent 返回字符串,parent 只能”读完再 parse”;oh-my-pi 直接拿 typed object,没有 parse 阶段,零 LLM 漂移。
四、整体架构:5 层 Harness + 21 特性矩阵
把 oh-my-pi 的 21 特性按 Harness 6 件套组件分类:
graph TB
subgraph "Harness 6 件套映射"
R["Rule 组件\nTTSR (Time-Traveling Stream Rules)\n原生 Cursor/Windsurf/Cline rules import\nsession: rules live, TTSR live"]
SK["Skill 组件\nlearn / manage_skill / skill:// URL\n.vibe / .oma skill 体系"]
SA["Sub-Agent 组件\ntask + hub + advisor\nworktree isolation + PID+startToken"]
WF["Workflow 组件\nworkflowz magic keyword\nsquad / parallel / orchestrated"]
SC["Script 组件\nLSP writethrough / DAP attach\ngit_overview → atomic commit"]
MC["MCP 组件\nmcp_runtime_lifecycle\n60+ providers 统一桥接"]
end
R --> CORE["omp core\n(31 tools · 14 LSP ops · 28 DAP ops)"]
SK --> CORE
SA --> CORE
WF --> CORE
SC --> CORE
MC --> CORE
style R fill:#C7CEEA,stroke:#888,color:#333
style SK fill:#E8D5F5,stroke:#888,color:#333
style SA fill:#FFDAB9,stroke:#888,color:#333
style WF fill:#B5EAD7,stroke:#888,color:#333
style SC fill:#FFF9C4,stroke:#888,color:#333
style MC fill:#FFB3C6,stroke:#888,color:#333
style CORE fill:#F5F5F5,stroke:#333,color:#3334.1 21 特性矩阵(README 完整列表)
| # | 特性 | Harness 6 件套 | 关键技术点 |
|---|---|---|---|
| 01 | Code execution w/ tool-calling | Script | Python + Bun 双核互调,loopback bridge |
| 02 | LSP wired into every write | Script | workspace/willRenameFiles 桥接 |
| 03 | Drives a real debugger | Script | DAP:lldb / dlv / debugpy |
| 04 | Time-traveling stream rules | Rule | mid-stream abort + 同断点 retry |
| 05 | First-class subagents | Sub-Agent | typed yield + worktree isolation |
| 06 | A second model, watching every turn | Rule | advisor role + per-turn 检查 |
| 07 | Hand someone the link, they’re in | Workflow | /collab + relay + QR code |
| 08 | Read a pdf on arxiv, why not? | MCP | 23 ranked providers + read tool |
| 09 | Unapologetically native | Script | in-process ripgrep / brush shell |
| 10 | Code review with priorities | Workflow | /review + P0-P3 ranking |
| 11 | Hashline: edit by content hash | Script | xxHash32 anchor + 4-hex tag |
| 12 | GitHub is just another filesystem | MCP | gh_issue / gh_pr → 路径 |
| 13 | Memory the agent curates | Memory | retain/recall/reflect/learn |
| 14 | ACP: editor-drivable agent | MCP | Zed editor 直接驱动 |
| 15 | Inherits what your other tools wrote | Rule | 8 种 rule 格式原生 import |
| 16 | omp commit: atomic splits | Workflow | git_overview + 依赖排序 |
| 17 | 16 internal schemes | Script | pr:// / issue:// / agent:// / skill:// |
| 18 | Conflict resolution, made easy | Script | conflict://N one-shot resolve |
| 19 | Preview, then accept | Script | ast_edit + xd://resolve |
| 20 | Drives a real browser | MCP | Puppeteer + CDP + Chrome relay |
| 21 | Hands on the desktop itself | MCP | computer tool + AX tree |
覆盖观察:6 件套全部覆盖(这是 2026-08 罕见的”all-in-one”项目)。oh-my-pi 的优势不在某个组件做到极致,而在于6 件套协作:TTSR 触发的 rule 注入 advisor,advisor 的 note 进 hub,hub 协调 subagent 重跑。
4.2 内部 crates(Rust core)
docs/native-crates.md 列出的 8 个 first-party Rust crate:
| Crate | 角色 | 关键 API |
|---|---|---|
pi-natives | 顶层 N-API cdylib | JS 可见的 napi 导出 |
pi-shell | brush 嵌入式 shell | command minimization、process plumbing |
pi-voice | 跨平台 mic / playback | Opus / WebRTC 桥接 |
pi-ast | tree-sitter / ast-grep | 50+ grammar 解析 + match |
pi-iso | 隔离后端 | APFS / overlayfs / ProjFS / reflink |
pi-walker | 并行 FS walker | ignore rules + globset |
pi_uu_grep | ripgrep-library | in-process grep builtin |
pi_uu_diff | similar-backed | in-process diff builtin |
总规模 80k 行 Rust——这是 oh-my-pi 跟其他 Coding Agent 最大的工程差异:他们不是在调 rg / grep / find,是把 ripgrep 库链进进程内做 builtin。单次 grep 节省 5-15ms fork-exec,在大 agent 循环里累计效应是显著体感差异。
五、对比:与其他 Harness 项目的设计差异
5.1 与 Claude Code(Anthropic 官方)
| 维度 | Claude Code | oh-my-pi |
|---|---|---|
| Edit 协议 | str_replace | Hashline(内容哈希锚定) |
| Rule 加载 | 静态 system prompt | TTSR(按需 mid-stream) |
| LSP | 通过插件 | 原生 14 ops |
| DAP | 无 | 28 ops |
| Sub-Agent | Sub-agents(Task tool) | task + advisor + hub(多角色) |
| 内存进程 | fork-exec rg / bash | 80k 行 Rust in-process |
| 平台 | macOS / Linux 优先 | macOS / Linux / Windows(无 WSL) |
| 协议桥 | MCP | MCP + 16 internal schemes |
核心差异:Claude Code 是通用编程助手,定位”广而稳”;oh-my-pi 是 “IDE wired in”,把 IDE 能力(LSP/DAP/工作树/refactor)当作 agent 的一等公民。
5.2 与 Pi-Mono(earendil-works/pi,上游)
| 维度 | Pi-Mono | oh-my-pi |
|---|---|---|
| 工具数 | 4 工具极简 | 31 工具 + 14 LSP + 28 DAP |
| Edit | 经典 replace | Hashline |
| 规则 | 静态 rules | TTSR |
| 进程模型 | Node fork-exec | Rust in-process |
| 维护模式 | Mario Zechner 个人 | 社区 + can1357 + vouch(已开放 PR) |
核心差异:Pi-Mono 的”4 工具哲学”是极简主义;oh-my-pi 是”batteries-included”——所有工程实践里反复用到的工具都内建,模型不用每次学新工具调用语法。
5.3 与 Cline(VSCode 扩展)
| 维度 | Cline | oh-my-pi |
|---|---|---|
| 编辑 | str_replace | Hashline + ast_edit |
| 调试 | 无 | DAP |
| Browser | headless | Stealth 默认 + Chrome relay |
| 协作 | 单人 | /collab 多端 + QR |
| 规则 | .clinerules 单格式 | 8 种格式 import + TTSR |
核心差异:Cline 是”AI 帮你点 VSCode 按钮”的代理;oh-my-pi 是”IDE 已经能做的,agent 直接用协议做”。
5.4 与 AGT(microsoft/agent-governance-toolkit)
AGT 重点在失败恢复(Circuit Breaker / Saga Handoff / Kill Switch),oh-my-pi 重点在日常生产力(Edit / LSP / DAP)。两者互补:AGT 的 governance 原语可以包在 oh-my-pi 外层,给它的 task sub-agent 加 recovery 能力。
graph LR
subgraph "Layer A: Daily Productivity"
A1["oh-my-pi\nHashline + LSP + DAP + 31 tools"]
end
subgraph "Layer B: Failure Recovery"
B1["AGT\nCircuit Breaker + Saga + Kill Switch"]
end
A1 --> B1
style A1 fill:#B5EAD7,stroke:#888,color:#333
style B1 fill:#FFB3C6,stroke:#888,color:#333六、优缺点(按约定结构)
左侧:架构简洁性 / 扩展性 / 易用性
优点
- 架构简洁性:5 层依赖单向(natives → shell → iso/walker → coding-agent),每个 crate 一个明确角色,没有 circular import。
docs/native-crates.md给出贡献者地图——新人按图索骥即可 - 扩展性:8 种 rule 格式原生 import(Cursor / Windsurf / Cline / AGENTS.md / Copilot / .mdc 等),团队不需要迁移现有 rule 就能换工具
- 易用性:
omp单一 binary,Homebrew /curl | sh/ Bun / mise / PowerShell 五种安装路径,tab补全自动从 CLI 元数据生成——模型名、子命令、enum 值永远跟二进制同步
缺点
- 学习曲线陡:21 特性 + 31 工具 + TTSR 语法 + Hashline patch 语法,对新用户是认知过载。
/advisor、/vibe、workflowz、ultrathink四个 magic keyword 互相组合有 16 种状态 - 模型依赖:TTSR 需要 LLM 支持 mid-stream abort(不是所有 API 都支持)。
oh-my-pi通过AbortController自己实现,但要 LLM provider 配合发送 chunked response - 平台差异:
/proc/<pid>/stat是 Linux 专属,macOS / Windows 走ps -o lstart=,startToken 拿不到时降级到 pid-only,安全性降级但不报错
右侧:性能 / 复杂度 / 维护性
优点
- 性能:in-process ripgrep / brush shell / 46 coreutils builtin,单 tool call 节省 5-15ms fork-exec。Hashline 让 60+ 模型在 Edit 上首次命中率 6.7% → 68.3%(Grok Code Fast 1 实测),token 消耗减 61%(Grok 4 Fast 实测)
- 复杂度控制:虽然表面 21 特性,但每个特性对应一个独立子目录(
hashline/、ttsr/、task/、advisor/等),docs/下一个 md 文件覆盖一个子系统的全貌 - 维护性:80k 行 Rust + ~50k 行 TypeScript(估计),Bun + Cargo + TypeScript 单一 build,
tsconfig.workspace.json统一管理 monorepo
缺点
- 性能瓶颈:TTSR 的 mid-stream abort 需要 LLM provider 配合 stream chunking,GPT-5 / Claude 3.5 等 streaming 默认不细到 token 的 provider 实测命中率下降
- 复杂度天花板:当用户用
workflowz组合 5 个 sub-agent + TTSR + advisor 时,调试一个 race condition 可能要追 6 个文件。系统已经接近”普通人无法完全 hold 住”的复杂度 - 维护性挑战:上游
pi-mono仍在活跃开发(2026-03-22 last sync point),docs/porting-from-pi-mono.md是 24k 字的 merge checklist,长期维护成本不可忽视
七、从零搭建启示:MVP + 踩坑预警
7.1 最小可行实现(MVP)
如果你想复刻 oh-my-pi 的核心 3 件套,按以下顺序搭:
graph LR
A["Day 1-3\nHashline 基础\n· xxHash32 4-hex\n· 5./3 行范围语法\n· normalize 行尾空白"] --> B["Day 4-7\nRecovery 算法\n· diffLineRuns\n· buildLineMap\n· fail-closed recover"]
B --> C["Day 8-12\nTTSR mini\n· regex check on stream\n· AbortController 包装\n· system reminder 注入"]
C --> D["Day 13-20\nSub-Agent 隔离\n· worktree 创建\n· PID+startToken owner\n· schema-validated yield"]
D --> E["Day 21-30\nEditor 集成\n· LSP willRenameFiles 桥接\n· DAP attach\n· in-process ripgrep"]
style A fill:#B5EAD7,stroke:#888,color:#333
style B fill:#FFF9C4,stroke:#888,color:#333
style C fill:#FFDAB9,stroke:#888,color:#333
style D fill:#E8D5F5,stroke:#888,color:#333
style E fill:#C7CEEA,stroke:#888,color:#333MVP 1:Hashline 写文件(30 行 Python,可独立测试)
1 | # mini_hashline.py |
这就是 oh-my-pi Hashline 的核心 30 行——hash + 行范围 + 替换。生产版(80k 行)加的是:block resolver、recovery、replacement boundary repair、clipboard register、AST validation。
7.2 必须包含的组件
| 组件 | 必需度 | 原因 |
|---|---|---|
| Hashline 4-hex file tag | 必须 | 没它整个 Edit 协议崩 |
| 行号 1-indexed | 必须 | 模型训练数据全是 1-indexed |
| normalize 行尾空白 | 必须 | CRLF/LF 跨平台一致性 |
+ body 前缀 | 必须 | 区分新增 / context / 删除 |
<N / >N / >$ gap 锚点 | 推荐 | 插入操作没有 gap 语法很难用 |
N* block suffix | 可选 | AST-aware 编辑 |
recovery.ts 失败恢复 | 推荐 | sub-agent 跨 turn 必用 |
7.3 可以暂时省略
- Clipboard register(
PUT @name)—— v0.1 用不到 - Replacement boundary repair—— v0.1 让 patch 严格匹配即可
- AST validation(ast-grep)—— 后期性能优化
- Streaming parser(
parsePatchStreaming)—— v0.1 整段读入即可
7.4 踩坑预警(实战经验)
- xxHash32 不是 SHA-256——别拿来当密码学指纹。collision budget 是 65,536 桶,足够 session 内文件区分,但不要跨 session 复用
/proc/<pid>/stat在容器里有 quirks——pid namespace 隔离下,容器内 PID 1 不一定是 host 的 PID 1。oh-my-pi在 K8s pod 里跑需要额外处理- LSP
willRenameFiles不是所有 server 都支持——TS Server 支持,rust-analyzer 部分支持,pylsp 不支持。fallback 到didChangeWatchedFiles就行 - TTSR 的 mid-stream abort 在 Anthropic Claude API 上 OK(chunked SSE),但OpenAI Completions API(不是 Chat Completions)不支持——必须切到 Chat Completions + stream
- worktree merge 冲突——sub-agent 改了
package.json而 parent 也改了,git 的 3-way merge 可能失败。oh-my-pi的merge --autostash只能处理简单场景,复杂冲突需要 sub-agent 二次介入
八、总结
oh-my-pi 的核心价值不在某个单点突破,而在让 Harness 6 件套在同一项目里协同工作:
- Hashline 解决 Edit 协议的根本问题(”匹配字串” → “内容锚定”),让 60+ 模型都能可靠改文件
- TTSR 解决 Rule 系统的上下文税(”每轮付” → “按需付”),让规则终于不会拖累 session
- Sub-Agent + Worktree + 双因子 owner 解决多 Agent 的工作区污染(”共享 dir” → “隔离 worktree + PID+startToken”),让 sub-agent 失败时 parent 仍能 clean recovery
每一件都是其他 Harness 没做对的硬骨头,组合起来就是”21k⭐ 的 IDE 化 Coding Agent”。
给你的行动建议
如果你正在选 Coding Agent:
- 想要改文件最稳 →
oh-my-pi的 Hashline 比所有同类都强(实测 60+ 模型 Edit 命中率翻倍) - 想要规则不付上下文税 → TTSR 是 2026-08 唯一的”流式中断 + 同断点 retry”实现
- 想要多 Agent 不污染工作区 → worktree + PID+startToken 双因子是 2026-08 最严谨的隔离方案
如果你在写自己的 Harness:
- 不要从
str_replace开始——Hashline 协议 30 行就能写出来,立刻享受 6× 命中率提升 - TTSR 的核心是 mid-stream abort,不是”用更聪明的 prompt”——投资 AbortController 包装、stream buffer、resume gate
- Sub-Agent 必须有 typed yield——
str返回会逼 parent 重新 parse,schema-validated object 是唯一零 LLM 漂移的方案
一句话总结
oh-my-pi是 2026-08 唯一一个把”Edit 协议 + 规则系统 + Sub-Agent 隔离”三件 Harness 硬骨头一起做对的开源 Coding Agent——它不是 21k⭐ 的最佳 AI 工具,它是 21k⭐ 的最佳 Harness 工程示范。
本文分析基于 can1357/oh-my-pi 2026-08-04 最新源码(21,847⭐),所有数据点(命中率 / token 节省 / 特性矩阵)均来自项目 README 实测声明。