别吹 Demo 了:读完 Pi 的 Harness v2 和 PR

标签: post | 发表时间:2026-08-24 04:45 | 作者:
出处:https://nexmoe.com

别吹 Demo 了:读完 Pi 的 Harness v2 和 PR #8172,聊聊写 Agent 的那些烂坑

现在的 Agent 圈子很浮躁。

大家喜欢看炫酷的控制台动效,喜欢接几十个工具,喜欢等下一个更强的大模型。

但只要你把 Agent 放进真实的工程环境跑几天,就会发现现实很残酷:

  • 跑了十分钟的任务,网络一抖或者内存一爆,进程挂了,会话直接报废。
  • 执行一个 npm test,吐出三万行日志,上下文当场被撑爆,模型开始胡言乱语。
  • 为了让用户实时插话,框架往会话中间塞了一条消息,底层的 KV Cache 全废了,Token 费用翻了五倍,响应慢得像蜗牛。
  • 搞多 Agent 并发,多个任务抢着写同一个状态文件,数据直接写坏。

模型不是瓶颈,Demo 也没有意义。包裹在模型外面的 Harness(执行底盘)太烂,才是 Agent 落不了地的根本原因。

最近, Pi( pi.dev / earendil-works/pi) 发布了 harness-v2.md 规范,随后合入了 PR #8172。这是一份没有废话的工业级 Agent 底盘设计。


1. Pi 是谁?

在开源 Agent 项目里,Pi 很低调,但地位很硬。

  • 核心作者与团队:Mario Zechner(GitHub: @badlogic,著名开源游戏引擎 libGDX 的缔造者,典型的老派系统级程序员)与核心团队成员 David Brailovsky( @davidbrai)、Vegar Stikbakke( @vegarsti)。在设计文档中甚至明确写着: “如果设计不成立,停下来在 Discord 上找 Mario 商讨。”
  • PR #8172 作者:Adam Teale( @adamteale)。这个仓库门槛极高,非协作者提 PR 会被 Bot 秒关,只有 Maintainer 明确给 lgtm 才能进入流程。代码质量把控极严。
  • 项目定位: “Primitives, not features”(只做原语,不做死板特性)。它不搞花哨的 UI,只提供 @earendil-works/pi-agent-core(运行时)、 pi-ai(模型层)和 pi-tui(终端差分渲染)。像 OpenClaw 这类复杂的自主 Agent 框架,底层直接依赖 Pi 的 SDK。

2. 竞品锐评:市面上的 Harness 错在哪?

把 Pi 的设计和主流竞品放在一起看,很多流行框架的做法其实很业余。

锐评 LangChain / LangGraph:自找麻烦的“图状态机”

LangGraph 试图用有向图和 Generator 状态机去模拟每一步执行,搞出一堆复杂的类型转换和模板代码。一旦中途报错,状态机极难恢复。

Pi 的选择:Pi 早期也写过一套 Generator 状态机( harness-v2-generator.md),后来直接废弃。Mario 的理由很简单: 直线的 async/await 最容易调试和理解。 不需要把代码切成碎块,只要把副作用边界划清就行。

锐评 DeepSeek Harness (dsh):粗暴丢数据的截断器

处理工具返回的超大文本时,DeepSeek 的 Harness(dsh)采用固定截断策略(保留头 4096 字符、尾 1024 字符,中间直接扔掉)。

PR #8172 的爆破:PR #8172 作者 Adam Teale 指出: dsh 的做法是永久丢失数据。 报错堆栈的关键信息通常就在中间,扔掉之后模型就彻底瞎了。PR #8172 提出了 Spill-copy-on-prune:裁剪 Context 的同时,把完整内容写入磁盘文件,并保留精确的字符偏移。模型不仅知道中间被裁了,还能用 grep 去磁盘文件里把那几行找出来。

锐评 AutoGPT / CrewAI:没有容灾的纸糊玩具

这些项目擅长展示“多智能体开会”,但根本没有真正的持久化与崩溃恢复(Durability)。任务跑了 30 步,第 29 步崩溃,整局重来。


3. Agent 开发者天天踩的五个坑,Pi 怎么解?

坑 1:中途插消息,KV Cache 当场报废

现象: Agent 正在跑工具,用户发了一条修正指令(Steer)。很多框架直接把这条消息插到对话历史里。 大模型的 Prompt Cache 依赖严格的前缀匹配。你在中间动一个字,后面的缓存全部失效。费用暴涨,延迟翻倍。

Pi 的解法:Append-Only Context(只增上下文) Pi 确立了一条铁律: 上下文只能在尾部增长。

  • 运行中产生的配置变动、用户插话、延迟写入,先暂存到内存队列。
  • 等当前回合(Turn)跑完,到达 Checkpoint(检查点) 时,统一追加到上下文末尾。
  • 缓存前缀永远不变,KV Cache 命中率永远最高。

坑 2:进程挂了,留下半截死状态

现象: 模型发起了两个工具调用,第一个执行完了,进程崩溃。重启后,对话历史里有调用指令却没有工具返回,下一次请求直接报 API 格式错误。

Pi 的解法:WAL 意图日志与预分配 ID Pi 借用了数据库系统的 Write-Ahead Logging 思想:

  1. 执行工具前:先写一条 step_attempt 意图记录到磁盘,里面包含预先分配好的结果 ID( resultEntryId)。
  2. 执行完成后:用这个 ID 写入真实的工具结果。
  interface StepAttemptRecord extends RecordBase {
  type: "step_attempt";
  runId: string;
  step: "assistant" | "compaction" | "branch_summary";
  attempt: number;        // 重试次数写在磁盘上,进程重启也不会无限重试
  resultEntryId: string; // 预先指定的 Entry ID
}

系统重启时,恢复程序扫描日志。如果发现有意图记录但没有对应的结果,就能精准知道崩溃发生在哪个位置:能重试的自动重试,不能重试的自动写入一条 "interrupted" 占位, 绝不会出现残缺的孤儿状态。


坑 3:超大输出撑爆上下文,盲目截断导致模型幻觉

现象: 执行一个命令吐出 50KB 日志,塞进上下文直接超限;如果直接把中间砍掉,模型看不到错误信息,开始胡乱猜测。

Pi 的解法(PR #8172 三级无损管道):

  1. > 50K 字符:内容全部写入磁盘文件( ~/.pi/agent/cache/tool-spill/),上下文里只给简短预览和文件路径。
  2. > 8K 字符:
    • 完整内容写入磁盘。
    • 上下文保留头 4096 字符 + 尾 1024 字符。
    • 在字符串索引 0 处写入标记 [pruned — full at /path...],并标明被裁剪的精确范围 [start, end)。
  3. 防死循环:模型调用读取工具去读溢出文件时,Pruner 自动放行,绝不二次截断。

实战测试数据(基于 GLM-5.3 和 DeepSeek-V4-Flash 的 19 次真实测试):

  • 未命中缓存的 Prefill Token 减少 72% ~ 88%。
  • 每轮请求上下文占用减少 26% ~ 35%。
  • 幻觉测试通过率 100%(0/9 幻觉),模型能通过精确偏移用 grep/ sed 从磁盘精准读回丢失的日志。

坑 4:多任务并发,状态文件被写烂

现象: 多 Agent 系统让多个子任务并发修改同一个状态文件,在 Node.js 或 Python 异步环境下极易产生写覆盖和文件损坏。

Pi 的解法:Lanes(泳道)与不可变树 Pi 把会话结构分成了两层:

   会话树(只增、共享、无状态):   a ─── b ─── c ─── d
                                       └─── e ─── f
 
 泳道(各自独立):               main ──> 指向 d
                                slack_thread_1 ──> 指向 f
  • 底层树(Tree):所有节点只增不减,所有泳道共享,只读不改。
  • 泳道(Lane):相当于 Git 分支。每个 Lane 只有自己的指针和操作队列,两个 Lane 并行工作完全不需要加锁互斥。
  • 单写者(Single Writer):整个会话只有一个写入器,所有并发写操作通过单调递增的序列号排队追加。

坑 5:异步 Check-Then-Act 竞态

现象: 代码判断“当前任务已结束”,准备关闭会话;就在这一瞬间,用户发来了 abort 或新消息。时序交错,导致中止失败或消息丢失。

Pi 的解法:Lane Mutation Line(泳道原子排队线) Pi 用一条极简的 Promise 链解决了所有竞态问题:

  let tail: Promise<unknown> = Promise.resolve();

function mutateLane<T>(job: () => Promise<T>): Promise<T> {
  const result = tail.then(job);
  tail = result.then(() => undefined, () => undefined);
  return result;
}
  • 状态检查和落盘写入,必须打包成一个同步 Job 放进队列执行。
  • 网络请求、模型调用、工具执行在队列外面跑,跑完了再排队写状态。
  • 任何两个并发操作,在时间线上永远只有 [先 A 后 B] 或 [先 B 后 A],没有第三种可能。

4. 确定性测试:线上代码怎么写,测试就怎么跑

很多框架的单测都是在 Mock 数据,一到线上就出 Bug。

Pi 定义了一个纯粹的副作用接口 Effects( fx):

  interface Effects {
  appendEntry(...): Promise<Entry>;
  appendRecord(...): Promise<T>;
  streamAssistant(...): Promise<SettledAssistantMessage>;
  executeTool(...): Promise<{ result: AgentToolResult; isError: boolean }>;
}

Agent 的所有逻辑,只能通过 fx 操作外部世界。

这带来了一个巨大的优势:

  • 生产环境( drive: "automatic"): fx 直通系统,异步全速运行。
  • 测试环境( drive: "manual"):每一个操作都会在 fx 边界停住。测试脚本可以像单步调试器一样,一步一步推着 Agent 走——可以在执行工具前强制断电,也可以在两个工具调用中间强行插入中断信号。

测试测的,就是线上跑的原版代码。


5. 总结

做 Agent,写 Prompt 只是第一步。

当系统进入真实工程,决定生死的是这几件事:

  1. 保住 KV Cache:不要随意插队改前缀。
  2. 做好 WAL 与崩溃恢复:把每一次动作先记下来再执行。
  3. 无损管理上下文:大输出落盘,保留偏移,给模型找回数据的能力。
  4. 管好并发与时序:单写者、只增树、排队写。

把这些脏活累活做干净,Agent 才能从玩具变成工具。

相关 [demo pi harness] 推荐:

别吹 Demo 了:读完 Pi 的 Harness v2 和 PR

- - Nexmoe
别吹 Demo 了:读完 Pi 的 Harness v2 和 PR #8172,聊聊写 Agent 的那些烂坑. 现在的 Agent 圈子很浮躁. 大家喜欢看炫酷的控制台动效,喜欢接几十个工具,喜欢等下一个更强的大模型. 但只要你把 Agent 放进真实的工程环境跑几天,就会发现现实很残酷:. 跑了十分钟的任务,网络一抖或者内存一爆,进程挂了,会话直接报废.

Harness最佳实践: Learn Harness Engineering

- -
欢迎来到 Learn Harness Engineering. Learn Harness Engineering 是一门专注于 AI 编程智能体工程化落地的课程. 本课程深度研究并总结了业内最前沿的 Harness Engineering(工具马具/脚手架工程)理论与实践,参考资料包括:. 通过系统的环境设计、状态管理、验证与控制机制,本课程旨在帮助你让 Codex 和 Claude Code 等 AI Agent 能够真正可靠地完成真实工程任务.

RaspBerry Pi连接WiFi

- - 平凡的世界
推荐 EDUP EP-N8508GS无线网卡 树莓派专用,这个直接免驱,省去很多麻烦事.

安装树莓派 Raspberry PI

- - CSDN博客综合推荐文章
树莓派终于到货了,是这个样子的. 上面有一行日期是 Raspberry PI (c) 2011.12. 选择这个镜像: RASPBIAN Debian 2014-01-07. 780M的压缩包,很大的样子. 似乎还有个NOOBS的安装方式,完全无感. 顺便展示一下SD卡,通过查阅可用SD卡列表,似乎是支持个别的64G Class10的卡的,就像这个,编号是 Transcend SDXC 64G Class10 TS64GSDXC10.

Raspberry Pi 4 開賣,USD$35

- - Gea-Suan Lin's BLOG
Raspberry Pi 4 開賣,目前推出的是 Model B,最低規格的 1GB RAM 版本與之前 RPi 3 相同都是 USD$35,另外這次提供了以前沒有的 2GB 與 4GB 版本,分別是 USD$45 與 USD$55:「 Raspberry Pi 4 on sale now from $35」.

Activiti工作流demo

- - CSDN博客综合推荐文章
继上篇《 Activiti工作流的环境配置》.        前几篇对Activiti工作流进行了介绍,并讲解了其环境配置. 本篇将会用一个demo来展示Activiti工作流具体的体现,直接上干货.        以HelloWorld程序为例.       首先说一下业务流程,员工张三提交了一个申请,然后由部门经理李四审核,审核通过后再由总经理王五审核,通过则张三申请成功.

Y Combinator 举办 Demo Day

- Radar - 丕子
世界上最大的创业公司孵化器 Y Combinator 今天举办他们的 Demo Day,这次一共有 63 家创业公司参加演示,其中有 31 家愿意向媒体和投资人曝光自己,下面是这些创业公司的名字以及一句话描述:. Aisle50: 杂货版 Groupon. Interstate: 项目管理软件,可以跟客户分享开发路线图.

地形模拟演示Demo

- kongshanzhanglao - 博客园-首页原创精华区
地形渲染的首先是创建一个三角网络平面,然后调整平面顶点的y高度值,模拟地面的山丘和山谷,最后再绘制贴图效果. 本文首先介绍如何生成三角网络平面. 然后介绍如何通过高度图调整平面高度. 以及使用BlendMap和3种材质绘制贴图效果的方法. 最后演示如何调整摄像机位置和移动速度,在地面上行走. 一个m*n个顶点的平面由2*(m-1)*(n-1)个三角形组成.

android的Notifications的例子demo

- - 博客园_首页
android的Notifications通知的原理和Demo.   在APP中经常会用到通知. 比如网易新闻客户端,有什么重大新闻的话会在通知栏弹出一条通知.   在做程序过程中我也遇到这个需求. 每隔7天就自动弹出通知,提醒用户. 在网上搜了搜,用了2天时间实现了.   一:通知要调用闹钟功能来实现,第一步设置闹钟.

IKAnalyzer和Ansj切词Demo

- - ITeye博客
        IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包. String content = "Java编程思想(第4版)";.         Ansj中文分词这是一个ictclas的java实现.基本上重写了所有的数据结构和算法.词典是用的开源版的ictclas所提供的.切词Demo代码如下:.