在 pi 编码智能体之上构建 Piclaw:一个自修改智能体框架的实践
Building Piclaw on Top of an Opinionated Coding Agent
作者用六个月时间在 Mario Zechner 的 pi 引擎之上构建了个人 AI 助手 Piclaw,pi 是一个可自我修改的编码智能体框架,扩展甚至能重写整个智能体循环。Piclaw 脱离终端运行,通过基于 Tailscale 的 Web UI 在 iPad 等设备间共享同一会话,用于管理 homelab、编辑 wiki、记录笔记和运行定时任务。
2026年8月21日st · 15 分钟阅读 · #agents #ai #architecture #arm64 #bun #pi #piclaw #typescript
我花了六个月的大部分时间,在 Mario Zechner 的 pi 引擎之上构建 piclaw——我的个人 AI 助手、工作空间,偶尔也是智能体集群——我想是时候写一写我的动机,以及我对在这件事上投入这么多时间的感受了。
这一点很重要(至少对我而言),因为 pi 是一个出色的编码智能体,对事情应该如何运作有着非常清晰的观点。其内部实现简洁、结构良好且易于理解,还有一个绝妙的转折:扩展可以真正接管整个系统——到了你几乎可以重写整个智能体循环的程度(无论如今你怎么定义循环)。
在它之上构建一种不同的 UI 既有教育意义,又非常令人满足,偶尔也让人抓狂,而我已经到了需要写下我所学到的东西以及许多底层理由的时候——部分是因为我现在只想用这个东西然后继续前进,部分是因为项目文档本身埋藏了很多内容(有很多是什么和怎么做,但相对很少有为什么)。
为什么选择 pi?
吸引我的关键点是,pi 实际上是一个可自我修改的智能体框架。
是的,可自我修改。就像……病毒一样。我倾向于认为,它的病毒式传播(无论你怎么表述)是它最近进入大量项目的未言明原因之一,但让我们跳过这一点,只把“可自我修改”重新表述为自我改进和自我演化,而不过多陷入 AGI 那种廉价游乐场式的戏剧性。
我已经到了这样一个地步:我认为即使没有能动性,像 pi 这样的可自我修改框架也远远比我们从大型企业那里得到的任何东西都更强大,因为它们可以演变成你自己完全个性化的环境——这正是 piclaw 对我而言所变成的样子。
当我开始摆弄最终成为 steward 的东西时,开放编码智能体领域还是一堆半成品 Python 脚本和 VS Code 分支的混乱局面,而 pi 之所以脱颖而出,是因为它在正确的方面有主见:以 TypeScript 作为运行时、干净的回合制对话模型、良好的终端用户体验,以及——关键的是——愿意快速演进。Mario Zechner 在工具调用、上下文管理和模型交互方面做出了强有力的选择,而这些选择本质上都是正确的。
如果你从字面上理解那个关键点,pi 做得最对的地方是克制。或者,如果你一直在关注关于 AI 使用的哲学辩论,那就是品味。
冒着重复大多数人已经知道的关于 pi 的基本知识的风险,它从一组小而精心挑选的内置工具开始——文件 I/O、shell、搜索和编辑——然后让 LLM 在 UNIX 世界里自生自灭,这赋予了它巨大的力量和灵活性。
将其与 AI 实验室工具正在变成的样子对比一下:一堆杂乱无章的专业工具、定制 UI 面板、内联差异、笔记本集成、代码审查模式,以及某人在季度周内想出的任何其他东西。这些功能中的每一个都会消耗上下文、增加延迟并让提示变得更长——这意味着模型用于思考你实际问题的空间更少。
pi 从另一端出发:给模型提供有效所需的最小界面,并将剩余的 token 预算花在手头的实际问题上。内省功能也非常出色——你可以看到模型在做什么、它正在调用哪些工具,以及任何时刻的上下文是什么样子。没有魔法,没有隐藏的系统提示,没有神秘感。这种透明度对我来说比功能打磨更有价值,因为这意味着当事情出错时我可以调试。
不过,我并不一定只想要一个编码代理,而且过了一段时间后,我意识到我甚至不想在终端中运行它。我想要一个可扩展的代理,它可以无头运行、管理我的家庭实验室、编辑我的 wiki、归档链接、记笔记、运行计划任务,并且通常以可能只是偶尔与编写代码有关的方式发挥作用。
pi 的小工具界面和清晰的回合模型已经足够接近我的需求,因此在其上构建似乎比从头开始更快。
逃离终端
正如我之前多次写过的那样,我不在本地运行编码代理——这就是为什么我花了相当多时间想出像 agentbox 和 webterm 这样的东西来在远程机器上运行它们。
但最大的偏离来自在 vibes 内部使用 pi,它最初是一个我可以从手机使用的通用 Web UI。凭借我的移动端背景,很明显(至少对我来说)高度优化的 Web UI 远比使用 WhatsApp 或 Telegram 作为桥梁要好得多——不仅我可以通过 Tailscale 使其更加安全(没有任何中间人),而且我可以构建一个更好、跨设备连续的体验,从任何地方重新连接并准确地从你离开的地方继续:
另一个关键因素是我几乎总是以 iPad 开始和结束我的一天,这基本上定义了其余的 UX——我可以引用时间线消息、用 Apple Pencil 注释它们等,从而形成有些简洁但非常有效的交互,尤其是在进行审查时。我只需在 Safari 标签页之间切换,即可在聊天和 GitHub 或 Gitea 差异之间移动,只需稍微调整 AGENTS.md 或按会话提示,事情就可以非常高效。
整个东西像折纸一样展开——一侧是文件,另一侧是计划跟踪器,文档打开为标签页或分离窗格,并且全程有充足的触摸支持(调整窗格大小、在聊天之间滑动等)。我还利用了 pi 出色的会话树内部结构来创建多个“代理”(它们可以在每个实例内使用 chat 工具相互交谈,甚至跨实例……)
你会注意到上面有点……拥挤。而且我甚至没有使用文件浏览器或内置终端,但这基本上就是我每天使用 `piclaw` 的方式。从顶部开始,你可以看到:
- 几个我可以快速切换的代理(在这种情况下,它们实际上正在协调修复一个附加组件,并将其部署到“集群”中)
- A timeline with rich link previews, my latest prompt, and three temporary panes:
- 模型的思考输出
- 工具输出
- 工具执行详情(做什么、当前工作目录和 git 分支)
- 模型未经提示就开始使用的
plan侧边栏(这是gpt-5.6-sol,因此它倾向于记住之前的指令) - 一个包含模型状态/计划/上下文信息的撰写框(在此截图中看不到提交内容、之前消息引用和图片附件组件)
顶部仪表板和侧边窗格是附加组件,它们设计为按需使用,因此典型的用户体验要简洁得多——但我的关键点是,尽管我们多次尝试“改进”它,但它在 iPad 上配合语音和 Apple Pencil 输入对我来说非常好用,如今我很少使用其他任何东西。
你看不到的一点是,它运行在远程虚拟机上——piclaw 有一个方便的小部件,我可以在右上角弹出,显示沙盒实例信息:
我和一位同事尝试过类似 Visual Studio 的用户体验,但我仍然更喜欢完全响应的前端,因为它已经演变为匹配我独特的工作风格:
- 不断在设备之间切换(iPad、工作笔记本电脑、iPhone、工作 AVD、MacBook、Linux 笔记本电脑等)
- 每个实例中有多个并发会话(通常每个项目一个)。
- 可以持续数周的超长会话。
与那些拥有不受打扰的专注时间、可以一次只做一件事的人不同,我必须不断在会议、编码、规划、研究、写作等之间切换。我倾向于在很长一段时间内以非常短的爆发方式处理某些事情——而将某些东西卡在某个地方的终端中,即使通过 tmux 和 ssh,也永远无法满足需求。
这可能是我从未将 piclaw 视为“产品”的最大原因,尽管它自然演化出了其他编码代理的许多特征——包括外部附加组件。
将 pi 扩展到浏览器
但关键点是,以上所有内容都只是扩展,因为我大力依赖 pi 的扩展机制,将其扩展以支持 Web 界面。
pi 有一个非常强大的终端 UI,其扩展契约暴露了很多内容,但它的许多灵活性并没有(或曾经没有)跨越 IPC 接口,因此我给 piclaw 提供了自己的浏览器 UI 契约:扩展是路由和事件感知的,外壳扩展宿主可以挂载、聚焦、调整大小、保存、分离或处置它们。
它们保留了标准的 pi 扩展功能,但可以提供编辑器、文件查看器、终端甚至 VNC 客户端,而无需触及主应用程序包。当然,它们可以在时间线中执行各种花哨的小部件操作(时间线从一开始就使用 SQLite,并且可以序列化小部件状态)。
当然,有许多东西是 piclaw 固有的。piclaw Web 服务器提供了大量用于管理 UI 窗格、SSE 事件、设置等一切的脚手架,此外还有一个加密的 keychain、数据库和状态内省,用于聊天/会话和时间线中的文件附件等。
但大多数已发布的扩展都是这样开始的:
{
"piclaw": {
"compatibleVersions": ">=1.8.0",
"tags": ["example"]
},
"pi": {
"extensions": ["index.ts"],
"web": {
"entries": ["web/index.ts"]
}
}
}
这转化为实际代码,如下所示:
// index.ts -- a normal Pi extension, loaded by the agent runtime
// schema and handlers omitted
export default function example(pi: ExtensionAPI) {
pi.registerTool({ name: "example", parameters, execute });
pi.on("session_shutdown", closeResources);
}
// web/index.ts -- loaded separately by Piclaw in the browser
__piclaw_web.registerPane({
id: "example-viewer",
label: "Example",
capabilities: ["preview"],
placement: "tabs",
canHandle: ({ path }) => path?.endsWith(".example") ? 100 : false,
mount(container, context) {
return createExamplePane(container, context);
}
});
慢慢地,逐渐地,piclaw 实际上用基本上全是 pi 扩展的东西构建出了自己的用户体验,我认为这很好地证明了最初的设计有多好。
Earendil 的移动城堡
现在,这个设计依然很好,但在别人有主见的引擎之上构建的问题在于,对外部观察者来说,他们的主见一直在变。pi 的开发非常活跃——这是好事——但有一段时间,每次上游升级都会带来不小的风险,导致一些难以诊断的问题。
作为工具,我对 piclaw 非常满意,以至于我很少用别的东西,但我想要真正使用它,而不是花这么多时间维护它,而在六个多月里,我处理了:
- 工具调用接口形态变化
- 上下文窗口管理在上游重构后行为不同,导致我较长的系统提示被静默截断。
- 模型配置层被重构,每次都需要修改我的设置和引导代码。
- 流式行为变化微妙到我的 Web UI 的 SSE 处理偶尔会丢 token 或重复思考指示器。
后来对模型处理的某次重构幸好与我的做法趋同,但仍然需要花一天时间伺候 GPT-5.6——就在我花了两天集成并测试一种新的上下文压缩方法之后不久。
某种程度上,以上所有都是有用的,因为 piclaw 现在有海量的回归测试——不仅针对核心和事件循环(因为我的事件循环必须做的不只是“仅仅”运行一个 TUI,比如路由 SSE 事件、更新表格等),还针对用户体验(包括一份非常漂亮的 PDF 报告,涵盖了几十个 Playwright 测试)。
所以,从整件事里我学到的第一件“新”事,就是如何把我在几个客户项目里不得不遵循的 TDD 信条应用到 piclaw 上,包括让 LLM 为测试生成像样的用户故事(以及 Gherkin),并确保整个东西能在 Actions 里运行。我以前做过,但没在单个项目上做到这种规模和深度。
这让构建变慢,但这与我一贯的流程完全一致,即确保 LLM 生成有用的代码,而且相当有回报。
另一面是,当上游破坏了我没预料到的东西时,花三个半晚上做兼容性工作很糟糕。不过,我不怪 Mario。这是必然要发生的。
pi 的核心必定是非常难以重塑和演进的(尤其是考虑到它一直享有的爆炸式流行度),而持续的模型和提供商更迭肯定也没帮上忙。如果你往引擎盖下看一眼,投入到 pi-ai 仅仅为了与提供商端点通信 的工作量简直疯狂至极,而且可能追踪的移动目标比 NORAD 还多。
十六个版本之后
自从我在6月19日首次标注这份草稿以来,piclaw已经经历了十六个不同的Earendil版本,从0.79.8到0.84.1。大多数只是平淡无奇的依赖更新,但包/API的拆分以及针对提供方发现、凭据和模型目录的ModelRuntime重写,又让我熬了几个深夜,因为我现在依赖后者来构建我自己精心打造的Azure和GitHub扩展(因为,嗯,我需要动态解析那些非公开的模型)。
我当然可以让上游稳定下来,但照目前的情况看,我敢肯定那得花上好几年,而且上述这些都不需要pi承诺API稳定性。但既然仅仅使用SDK和IPC对我来说并不现实(至少目前还不是——稍后会详细说明),我就必须接受这些,把它们当作在一个不断变动的基础上塑造自己工作环境的自然结果——Earendil要操心的事情比我这个有些古怪的用例多得多。
压缩功能把这一点推到了极致,因为我的工作方式适合非常长的会话,而且我能使用数百万token的模型,所以我的使用模式似乎与大多数人截然不同。
上游增加了原因、重试元数据、压缩后估算,最终还有重试生命周期事件;我的智能压缩插件围绕这些钩子增加了摘要验证、连续性检查、提供方特定执行和请求认证保留。当我实现提供方原生的远程压缩时,又不得不处理某个提供方可以创建但无法通过另一个提供方重放的摘要,等等。顺便说一句,这让我更加敬佩pi-ai中所做的工作……
幸运的是,较新的上游工作已经与我已经做出的决定趋同。缓存友好的动态工具加载、完整的提供方扩展,以及压缩元数据加上对失败摘要的重试,这些都是长期运行的助手所需要的,也让我得以放弃一些变通方案。我仍然需要适配piclaw的凭据存储、模型刷新、调度器和扩展加载器,但我不再完全在引擎之外维护这些想法,其中一些脚手架变成了集成代码。
我终于赶上了进度,现在正主动跟踪新的上游代理框架,并开始把piclaw拆分成可单独测试的“效应器”(找不到更好的词),它们可以在新代理循环改变状态时直接插入。
运气好的话,我应该能在新框架发布时就采用它。最坏的情况是,我得多重构一些,但说实话,有些重构早就该做了。
不过,让我们稍微绕个弯。
TypeScript、Bun和RAM问题
我学到的另一件事——这与其说是关于pi,不如说是关于整个TypeScript生态系统——是运行时开销在资源受限的设备上极其沉重。对我来说,Bun相比Node.js是巨大的改进(它最近升级到1.4也是我决定最终整理笔记并发布这篇文章的原因之一),但它仍然有自身的挑战。
使用 Bun 1.3 时,piclaw 在我的 ARM 设备上运行,主进程占用大约 400 到 600MB 内存,具体取决于加载了多少工具以及内存中保留了多少对话状态。在桌面或虚拟机上这不算什么,但在同时还要运行构建任务的较小 ARM 板子上,这是一块相当可观的内存占用。
1.4 的早期结果(非常早期,因为我刚基于它做了第一个 piclaw 版本)……喜忧参半。我看到有些实例的 RSS 降到了 200MB,但其他实例仍保持在同一区间。
Bun 很快,我也确实喜欢用 TypeScript 来写 Web UI 和工具层(老实说,主要是做 lint 和测试,因为我其实还是不太喜欢这门语言)。但 JavaScript 运行时开销、node_modules 依赖图,再加上一个在启动时加载几十个工具定义的进程,意味着我在支付一种编译型语言本可避免的税,而我不断被提醒这一点。
我仍然更喜欢 Python(并继续探索用它来做 harness),所以有一种力量把我从 pi 的内部实现中拉走,我无法忽视。
总之,我采用了几种不同的策略来尽量控制局面。
扩展拆分
第一个缓解措施是架构层面的,很符合 pi 的风格,也一如既往地偷懒:我把 piclaw 拆成了一个核心运行时和一组懒加载(哈!)扩展,并改造了会话管理(我还扩展了它,以支持多个并行运行的会话)来支持动态加载。
因此,像 Proxmox 工具、Portainer 集成、SSH、wiki 助手以及其他(顺便说一句)上下文很重的工具,只在模型需要时才加载,而不是从一开始就撑大基础进程(或会话)。
这有帮助,最终也与 pi 近期的版本趋同,随着核心加入了自己的动态工具加载,这不再那么像是一个 hack(有一阵子挺棘手,因为它在我自定义的处理中弄乱了缓存的提示前缀……)。我只对计划任务和一些附加组件使用子进程,但我也把它们接了进去。
问题是,把所有这些扩展都和 piclaw 本身一起发布开始变得难以管理,所以……
一切都得有个应用商店
我最终决定把较大的集成移到一个单独的 piclaw-addons 仓库中,从而可以直接从那里下载和安装东西:
这带来了更多维护工作,但也迫使我总体上以不同的方式对待扩展。该仓库现在有独立导入测试、元数据校验等,而我一直以来的一个慢工任务就是把东西从核心仓库迁移到如今已接近 50 个的附加组件库中。
这包括小众的提供商集成、基础设施客户端、查看器和编辑器、委派与压缩机制、终端渲染器,以及只在 Web 应用中才有意义的小块 UI。每个都作为 Pi 包安装,其运行时入口仍然接收 pi 的原生 ExtensionAPI,因此工具、事件、会话状态和关闭语义仍然属于核心(这也是为什么我对自己能顺利采用其即将推出的 pi harness 而不会造成大量破坏相当乐观)。
此外,我的工作流中一些最好的改进来自贡献的附加组件:特别是 delegate(它只是将 pi 作为一个或多个子进程调用,至少在我找到时间重构它之前是这样),对于“使用 sol、terra 和 opus 独立审计这个 PR 并给我一份评估”之类的事情非常有用。
每个设置都有其位置
哦,对了,上面附加组件库周围那个吓人的东西就是设置面板。piclaw 中的每个扩展(内置或附加组件)都可以访问一个设置契约,让它能够创建和渲染自己的用户体验。例如,这是计划任务面板。
设置面板使用一种相当直接的模式:浏览器与一个经过身份验证的 /agent/addons/api/<addon>/<action> 端点通信,运行时注册处理程序,非机密值进入扩展 KV 存储,密码或令牌进入钥匙串。
但这不仅仅是关于我在 piclaw 中构建的(略显过度的)功能量。使用它也迫使我反思(并学习)了很多关于如何有效使用 LLM 的知识。
知识、记忆与遗憾
在保留会话数周后,显而易见的一件事是,对话本身大多是一次性的——但也不尽然。最有用的部分是那些在对话之外存活下来的东西——工作区中的文件、AGENTS.md 中的指令以及技能。
像其他人一样,我广泛利用代理技能,并有数十个 SKILL.md 文件和脚本,描述我希望如何完成重复性工作(包括所有那些否则我每隔几个月就得重新发现的小注意事项)。
这比把旧聊天记录塞进向量数据库并希望相似性搜索能正确行事要理智得多,但我发现代理记忆有多个互补的方面,以及确保可重现行为的方法。
首先,每个新会话都获得相同的工作区和操作约定,以及足够的当前项目状态,以便在不将数月的对话碎片拖入上下文窗口的情况下恢复工作。
但我也加入了一个定期的自我诊断,对技能和一小部分“记忆”文件进行轻微调整。每天晚上,一个带外的“做梦”会话会回顾最近的活动,更新人类可读的每日笔记,并将有用的部分提炼为偏好、更正、项目状态和一个紧凑的启动索引——同时修剪那些不再需要折叠到每个会话中的内容。它故意是有损的,而且因此更好。
然后是 late-night-regrets,这是我对一个附加组件的有点尖锐的命名,它回顾交互历史并对课程更正、误解、过度工程、交付不足、上下文失败和有用的主动工作进行分类。按照我的一贯风格(如果你见过我的任何新闻处理管道),分类器是一个小型贝叶斯模型,不消耗模型令牌;一个单独的反思过程对高置信度的失败进行评分,并将它们转化为笔记或引导线索,放入一个 feedback.md 文件中,在开始新会话时会被引用。
这就是我现在愿意信任的“自我改进”程度:回顾发生了什么,总结出了什么问题,并将更改限制在(非常)有限的一组文件中。没有模型在凌晨 2:30 偷偷重写 AGENTS.md——它只是带着更好的笔记醒来,偶尔还有一份遗憾清单。
但这就是我,我非得更进一步。
跨代理的记忆
Memento 源于一种需求:在不共享聊天记录、凭据或一堆机器本地杂物的情况下,将部分知识跨机器和代理携带。持久化的概念以 Markdown 形式存放在 Git 中,按命名空间拆分,并经过提案/策展流程,我最初设计它是为了共享技能及其相关脚本和资产。我硬塞进了自己的嵌入引擎,但和往常一样,它主要依赖索引,而不是将嵌入作为真相来源——鉴于我在不透明记忆系统上的经验,这是一个重要的区别。
这仅仅是一个月左右大的项目,但已经在非常平凡的方面证明了自己的用处,比如允许 piclaw 将自己复制到新机器上,并用每个实例都可用的共享技能来引导它们:
Memento 知道我在意精确的当前状态报告、干净的 Git 历史以及在真实目标上验证事物;它包含从几个不相关项目中提炼出的工程实践;它还携带完整的技能,包括上面使用的引导技能以及我用来防止兄弟代理会话跑偏的监督者/工作者“小队”模型。
最棒的是?Memento 与其他代理框架配合得非常好,包括我 iPad 上的 OpenMinis。
这让 Piclaw 处于什么位置
piclaw 仍然是我的主要助手,运行在六台机器上,每天做着有用的工作。扩展架构使其易于维护,而 pi 的上游改进继续让它变得更好。
但是,再次强调,它是一个为一个人打造的“产品”。而且它很可能保持这样,但部分原因是我非常强烈地认为这类东西已经是一种商品,任何人投资于销售 AI 代码编辑器、shell 等都没有意义。部分原因是能够构建这些东西的人越来越可能为自己构建,部分原因是最低公分母已经以无数种形态存在了。
不管我在这个过程中学到了什么,最有趣的部分(至少对我来说)是 pi 的哲学是关键差异化因素——拥有一个可自我修改的代理框架作为构建基础,使我能够非常迅速地将 piclaw 从一个网页包装器发展成我的主要工作环境,拥有自己的界面、扩展模型和积累的习惯,我将继续遵循这种方法前进。
敬下一个版本——但首先,我需要享受我最后几天的假期。
来源:Hacker News · taoofmac.com