跳到主要内容

oh-my-pi — 架构与原理(总览与阅读地图)

30 秒导读: oh-my-pi(命令行叫 omp)是一个装在终端里的 AI 编码 agent——你打字说需求,它读代码、跑命令、改文件、调试器都能上。它是 Mario Zechner 的 Pi 的 fork,主打"开箱即全"(batteries-included):40+ 模型 provider、32 个内置工具、把 IDE 的 LSP/DAP 直接接进 agent。工程上分两层——TypeScript 做编排,Rust 做重活且在同进程内跑。本章只做全景和阅读地图,每个机制的原理交给后面六章。

本页定位: 这是 omp 子库的全长总览(子库总览);货架层另有一张同名的短卡片(../index.md,只给一句话本质 + 阅读地图入口)。想要一句话判断相关性看短卡片;想在进任何一章前先建立全景,读本页。


1. 这是什么(零基础也能懂)

一句话定义: omp 是一个跑在终端里的编码 agent——你像跟同事聊天一样描述任务,它自己决定去读哪些文件、跑哪些命令、改哪几行,直到活干完。

解决谁的什么问题。 假设你在一个几十万行的项目里,想让 AI "把 formatBytes 改个名、顺带修掉调用点"。裸调模型做不到:模型只会说话,不会真的落到磁盘、不会知道你 IDE 里的符号引用。omp 补上的就是这套"手脚 + 感官":

模型缺的omp 补的东西
落到真实文件的能力edit / write / ast_edit 等 32 个工具
知道符号在哪、引用有几处内置 LSP(14 类操作)、DAP 调试器(28 类操作)
跑命令、开子进程进程内的 Rust bash(brush shell)、grepfind
换模型不掉链子40+ provider,一个 /model 就切

用起来什么样。 三种最常见的启动姿势:

omp # 打开交互式 TUI,像聊天一样干活(默认)
omp -p "修好 CI 里失败的那个测试" # 一次性跑完就退出,适合脚本/管道
omp acp # 让 Zed 之类编辑器来驱动它

一句话直觉。 把 omp 想成"一个会用你整套开发工具的实习生":它有嘴(模型)、有手脚(工具)、有 IDE 的眼睛(LSP/DAP),而且这套工具箱是焊死在它身上、开箱即用的,不用你一个个插件去配。

本节到此不碰任何代码细节。记住一件事:omp = 模型 + 一整套已经接好线的开发工具。


2. 顶层全景(它大概怎么转)

2.1 两层结构:TS 编排,Rust 干重活

omp 是个 monorepo,东西分两大层。看懂这张分工图,后面所有章节都好定位:

┌───────────────────────────────────────────────────────────────┐
│ TypeScript 编排层 (packages/*, 跑在 Bun 上) │
│ │
│ coding-agent ── CLI/TUI/会话/工具装配 (最外层,你启动的就是它) │
│ agent-core ── 回合主循环:prompt→模型→工具→再来一轮 │
│ pi-ai ── 统一 LLM 客户端 + 方言层 (40+ provider) │
│ catalog ── 模型目录:哪些模型、能力、等价关系 │
│ hashline ── edit 工具背后的补丁语言 │
│ tui/wire/utils/mnemopi/snapcompact/collab-web/stats/swarm ... │
└───────────────────────────┬───────────────────────────────────┘
│ N-API(同进程调用,无 fork-exec)

┌───────────────────────────────────────────────────────────────┐
│ Rust 原生层 (crates/*, 编成一个平台标记的 .node 插件) │
│ │
│ pi-natives ── 聚合入口:grep / 剪贴板 / 图像 / 高亮 / PTY │
│ pi-shell ── 嵌入式 bash(brush)、PTY、进程管理 │
│ pi-ast ── tree-sitter 代码摘要(50+ 语言语法) │
│ pi-iso ── 任务隔离后端:APFS clone / reflink / overlayfs │
└───────────────────────────────────────────────────────────────┘

为什么这么切。 TS 适合写"决策与胶水"(解析参数、装配工具、管会话、和模型流式对话);而 grepbash、语法解析这些又热又重的活,shell 出去调 rg/grep 会有 fork-exec 的往返开销、还依赖机器上装没装这些二进制。omp 把这些用 Rust 实现、通过 N-API 链进同一个进程,一次调用不再是一次进程启动(依据:根 README『Native Core』一节、packages/natives 的 N-API 绑定)。原生层的规模与"无 fork-exec"的确切范围见 05-native-core(实测约 6.2 万行 Rust,不含 vendor)。

2.2 包与 crate 的一句话职责

发布的 14 个 npm 包(packages/ 下另有 terminal-benchtypescript-edit-benchmark 两个内部基准包,不在发布清单里;依据:根 README『Monorepo Packages』表):

包 (@oh-my-pi/…)目录一句话职责
pi-coding-agentpackages/coding-agent主包:CLI、TUI、会话管理、把所有工具/模型/技能装配起来
pi-agent-corepackages/agentAgent 运行时:回合主循环、工具调用、状态与 compaction
pi-aipackages/ai统一 LLM 客户端:流式、provider 接入、方言(dialect)层
pi-catalogpackages/catalog模型目录:内置模型库、provider 发现描述、身份/分类/等价
hashlinepackages/hashline行锚定补丁语言 + applier,edit 工具的底座
pi-nativespackages/nativesRust 原生层的 N-API 绑定(grep/shell/图像/高亮/PTY…)
pi-tuipackages/tui终端 UI 库,差分渲染
pi-mnemopipackages/mnemopi本地 SQLite 记忆引擎(Hindsight 记忆的存储)
snapcompactpackages/snapcompact面向视觉模型的位图帧上下文压缩
pi-wirepackages/wirecollab 实时会话的共享协议类型与 relay 常量
collab-webpackages/collab-web浏览器 guest 客户端 + 本地 relay(/collab 共享会话)
omp-statspackages/stats本地可观测性面板:AI 用量统计
pi-utilspackages/utils共享工具:日志、流、目录/环境/进程助手、CLI runner
swarm-extensionpackages/swarm-extensionswarm 编排扩展

4 个主 Rust crate(下表是根 README『Rust Crates』表列出的四个主 crate;精确点说,crates/ 下非 vendor 的 crate 共 6 个——除下表四个,另有 pi-uu-greppi-uutils-ctx 两个较小的支撑 crate;再加 crates/vendor/ 下 vendored 的 brush-core/brush-builtins(嵌入式 bash 的来源)与一批 uu-*(uutils coreutils 重实现)。依据:根 README『Rust Crates』表 + crates/ 目录):

Crate一句话职责
pi-natives核心 N-API cdylib,聚合下面几个 crate 对外供 @oh-my-pi/pi-natives 调用
pi-shell嵌入式 shell / PTY / 进程管理(包了 vendored brush-*)
pi-ast基于 tree-sitter 的代码摘要与 AST 工具(50+ 语言语法)
pi-iso任务隔离后端解析:APFS clone、btrfs/zfs reflink、overlayfs、projfs、rcopy

2.3 四种入口(同一个引擎,四张脸)

omp 一套核心逻辑,对外开了四个使用面。判断你在走哪条:看是不是人在终端里聊、看是不是一次跑完、看是不是别的程序在驱动。

入口怎么启动谁在用内部 mode
交互式 TUIomp(默认)人,在终端里对话text(默认)
一次性 printomp -p "…"脚本、CI、管道--print / 管道自动 print
RPC 宿主omp --mode rpc(或 rpc-ui)嵌入方,走 JSON-RPC over stdiorpc / rpc-ui
ACP(编辑器)omp acpZed 等编辑器驱动acp

四条路在 runRootCommand 里分叉,前面是共享的启动准备(解析参数、建会话、装工具),尾部才按 mode 各走各的 runner:

┌─────────────── 共享启动 (runRootCommand) ───────────────┐
argv ──▶ cli.ts:runCli ──▶ launch 命令 ──▶ 解析/建会话/装配工具/选模型 ──▶ 分叉:
└──────────────────────────────────────────────────────────┘

┌───────────────┬───────────────┼───────────────┐
▼ ▼ ▼ ▼
InteractiveMode print-mode rpc-mode acp-mode
(TUI 循环读输入) (跑完即退) (JSON-RPC 宿主) (编辑器驱动)

真源码锚点:入口 dispatch 在 packages/coding-agent/src/cli.ts:231(runCli);launch 子命令注册在 packages/coding-agent/src/cli-commands.ts:15;共享启动 + 四路分叉的大编排在 packages/coding-agent/src/main.ts:955(runRootCommand),mode 判定见 main.ts:1046(isProtocolMode);omp acp 只是把 mode 强制成 "acp" 的薄包装(packages/coding-agent/src/commands/acp.ts:20)。


3. 主线走一遍(一个 prompt 的端到端旅程)

这节把"你敲下一句话到文件被改"的高层流走一遍,不进任何单个机制的实现(那些在后面章节)。

先给结论: 一次 prompt 就是一个"回合循环"——模型说话、要用工具就停下来执行、把结果喂回去、再让模型接着说,直到它不再要工具为止。

① 你打字 "把 formatBytes 改名为 humanBytes"


② CLI 层 main.ts:runRootCommand 建好 AgentSession(会话=历史+模型+工具集)
│ session.prompt(text) ── coding-agent 这一侧的入口

③ 回合循环 agent-core:agentLoop / runLoop ── 一个回合的心脏
│ 把历史规整成 provider 认识的形状,发起一次流式请求

④ 模型流 pi-ai:stream() ── 按 provider 的方言(dialect)流式收 token,
│ 边收边解析出"文本"和"工具调用"(有些模型工具调用是 in-band 文本)

⑤ 工具执行 coding-agent/src/tools/* ── 模型要调 edit/bash/lsp… 就在这跑
│ edit 走 hashline 补丁;grep/bash 落到 Rust 原生层

⑥ 回写 工具结果作为新消息塞回历史 ──▶ 回到 ③ 再来一轮
│ 模型看到结果后,要么继续调工具,要么给出最终答复

⑦ 结束 模型不再要工具 → 回合结束 → TUI 渲染 / print 退出

每一站的真源码锚点(核实过行号,便于你下钻):

干什么文件:行 · 符号
② 会话建立装配工具/技能/扩展,产出 AgentSessionpackages/coding-agent/src/sdk.ts:1087 · createAgentSession
② prompt 入口coding-agent 侧接住一句用户输入packages/coding-agent/src/session/agent-session.ts:6670 · AgentSession.prompt
③ 回合循环起一个 agent 回合、驱动到 stoppackages/agent/src/agent-loop.ts:299 · agentLooprunLoop (:628)
④ 模型流式按 provider/dialect 发起流式补全packages/ai/src/stream.ts:685 · stream
⑤ 工具面32 个内置工具的实现与注册packages/coding-agent/src/tools/index.ts
⑦ 交互渲染TUI 读输入→提交→渲染的常驻循环packages/coding-agent/src/main.ts:503(runInteractiveMode 内的 while 循环)

别在这里深挖。 "回合循环怎么判停、怎么重试" → 看 01-agent-loop;"40+ provider 怎么统一、in-band 工具调用怎么解析" → 看 02-providers-dialects;"edit 为什么用哈希锚点" → 看 04-hashline-edit


4. 阅读地图(建议按这个顺序读)

六章由浅入深,像剥洋葱:先懂"一个回合怎么转",再懂"话怎么变成动作",最后到"重活怎么在进程内落地"。

顺序章节读完你会懂什么时候读
0index.md(本章)全景、四种入口、主线、包地图现在
101-agent-loop一个回合从 prompt 到 stop 的模型:何时调工具、何时重试、何时结束想懂"心脏怎么跳"
202-providers-dialects40+ provider 如何被统一成一套接口;方言层如何把不同模型的工具调用格式(含 in-band 文本)对齐想懂"换模型为什么不掉链子"
303-tool-surface32 个工具为什么都长成"文件系统"的形状;read pr://…conflict://N 这类 :// 内部 URL 怎么复用同一套工具想懂"工具面的统一设计"
404-hashline-editedit 背后的 hashline:用内容哈希锚定要改的行,躲开空白之战和"string not found"死循环想懂"编辑为什么一次就中"
505-native-coreRust 原生层:grep/shell/AST/PTY 如何在进程内跑、无 fork-exec、跨三平台想懂"重活怎么落地、为什么快"
606-context-and-steering长程上下文治理:compaction 压缩、TTSR 时光倒流的转向规则、typed subagents、第二个模型 advisor 盯梢想懂"长会话怎么不失控"

5. 巧妙之处清单(读者要带走的精华)

下面每条都是 omp 值得借鉴的设计取舍。这里只点"妙在哪 + 在哪章看",细节交给对应章节。

  • hashline:按内容哈希锚定的编辑。 模型指着"锚点"而不是重打整行,少了 token、躲了空白差异;文件过期锚点对不上就直接拒补丁,不让它改坏。→ 04
  • TTSR(时光倒流的转向规则,time-traveling stream rules)。 规则平时休眠,一旦模型跑偏,正则命中就中途掐断这次流式、注入规则、从同一点重试;修正还能扛过 compaction。→ 06
  • 方言层(dialect)。 40+ provider、多种工具调用格式(原生 tool-calls 和 in-band 文本)被统一成一套流,换模型无感。→ 02(方言文件见 packages/ai/src/dialect/)
  • 原生进程内、无 fork-exec。 ripgrep / glob / find / bash(brush)全链进进程,一次调用不再是一次进程启动;同一个二进制跨 macOS/Linux/Windows,不要 WSL 桥。→ 05
  • typed subagents(带类型的子 agent)。 task 把活扇出到隔离 worktree,子 agent 回来的是 schema 校验过的对象,不是要人解析的散文,兄弟之间不打架。→ 06
  • :// 内部 URL scheme。 read pr://1428search 走 diff、conflict://N 解冲突——把 PR/issue/子 agent 输出/技能都当"路径"塞进同一套 FS 形状工具,少教模型一堆专用工具。→ 03

6. 顶层代码地图(agent 的跳转表)

想直接跳进源码时,从这几个符号入手(用符号名 grep 比行号抗漂移):

主题文件路径符号名
CLI 进程入口 / argv 分发packages/coding-agent/src/cli.tsrunCli
子命令注册表(launch/acp/join/commit…)packages/coding-agent/src/cli-commands.tscommands
启动大编排 + 四路 mode 分叉packages/coding-agent/src/main.tsrunRootCommand
交互式 TUI 常驻循环packages/coding-agent/src/main.tsrunInteractiveMode
会话装配(工具/技能/扩展)packages/coding-agent/src/sdk.tscreateAgentSession
会话对象 / prompt 入口packages/coding-agent/src/session/agent-session.tsAgentSession · prompt
回合主循环(agent-core)packages/agent/src/agent-loop.tsagentLoop · runLoop
统一模型流式客户端packages/ai/src/stream.tsstream
方言层(工具调用格式统一)packages/ai/src/dialect/dialect/*.ts
内置工具面(32 个)packages/coding-agent/src/tools/index.tstool 注册表
SDK 对外导出面packages/coding-agent/src/index.tsexport * from "./main" / "./sdk"
Rust 原生绑定入口packages/natives + crates/pi-nativesN-API cdylib

边界提醒(诚实优先): 本章只做全景与导航,刻意不进任何机制的实现细节——回合的判停/重试、方言的具体解析、hashline 的哈希算法、原生层的 N-API 细节,都在对应章节里带 file:line 讲。凡本章未展开的技术论断,以后续章节的源码引用为准。