<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>刘宇广 | AI 应用开发</title><description>专注将 AI、数据系统与企业业务结合，构建可靠、可观测、可持续迭代的 AI 应用</description><link>https://ygrowly.github.io</link><item><title>从 pass@k 到 pass^k：为什么 Agent 评测要看「连续通过」</title><link>https://ygrowly.github.io/blog/20260911---pass-k-reliability-eval/post</link><guid isPermaLink="true">https://ygrowly.github.io/blog/20260911---pass-k-reliability-eval/post</guid><description>pass@k 衡量能力上限，pass^k 衡量生产可靠性。本文拆解两者的数学差异，以及 Ground Truth 隔离、故障注入、结果评测与轨迹评测怎样配套。</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;模型评测里最常见的指标是 &lt;code&gt;pass@k&lt;/code&gt;：跑 k 次，至少成功一次就算过。&lt;/p&gt;
&lt;p&gt;这个指标在探索性场景里没问题，但一旦系统要上线，它会系统性地高估可靠性——因为&lt;strong&gt;「至少成功一次」和「每次都成功」在生产里是两回事&lt;/strong&gt;。这篇拆一下这个差异，以及围绕它需要配哪些东西。&lt;/p&gt;
&lt;h2&gt;一个反直觉的算术&lt;/h2&gt;
&lt;p&gt;假设单次成功率是 &lt;code&gt;p&lt;/code&gt;，独立重复 k 次：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pass@k = 1 − (1 − p)^k&lt;/code&gt; —— 至少一次成功&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pass^k = p^k&lt;/code&gt; —— 每次都要成功&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;取 &lt;code&gt;p = 0.9&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;| k | pass@k | pass^k |
| --- | ---: | ---: |
| 1 | 90% | 90% |
| 3 | 99.9% | 72.9% |
| 5 | 99.999% | 59.0% |&lt;/p&gt;
&lt;p&gt;同一个系统，&lt;code&gt;pass@3&lt;/code&gt; 读作 99.9%，&lt;code&gt;pass^3&lt;/code&gt; 读作 72.9%。&lt;strong&gt;两个数字描述的是同一个东西，差距来自口径。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; 越低，剪刀差越大。取 &lt;code&gt;p = 0.7&lt;/code&gt;：&lt;code&gt;pass@3&lt;/code&gt; 是 97.3%，&lt;code&gt;pass^3&lt;/code&gt; 只有 34.3%。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[&quot;单次成功率 p&quot;] --&gt; B[&quot;pass@k = 1 − (1 − p)^k&amp;#x3C;br/&gt;能力上限：k 次里至少对一次&quot;]
    A --&gt; C[&quot;pass^k = p^k&amp;#x3C;br/&gt;连续可靠性：k 次必须全对&quot;]
    B --&gt; D[&quot;随 k 增大 → 趋近 100%&quot;]
    C --&gt; E[&quot;随 k 增大 → 单调下降&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意两条曲线的方向是&lt;strong&gt;反的&lt;/strong&gt;：k 越大，&lt;code&gt;pass@k&lt;/code&gt; 越好看，&lt;code&gt;pass^k&lt;/code&gt; 越难看。所以「我们跑了 5 次至少有 1 次对」和「我们跑了 5 次 5 次都对」在工程上是完全不同的两句话。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;补充一个精度问题：上面的公式假设各次独立同分布。实践中 &lt;code&gt;pass@k&lt;/code&gt; 通常用 Codex 论文里的无偏估计 &lt;code&gt;1 − C(n − c, k) / C(n, k)&lt;/code&gt;（n 次采样中 c 次正确），避免小样本下的偏差。&lt;code&gt;pass^k&lt;/code&gt; 的估计简单得多——就是 c = n。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;哪些场景必须看 pass^k&lt;/h2&gt;
&lt;p&gt;判断标准不是「技术先进」，而是&lt;strong&gt;失败能不能被用户重试掉&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码补全、头脑风暴、搜索建议：用户可以点「重新生成」。失败一次的成本接近于零，&lt;code&gt;pass@k&lt;/code&gt; 是合适的口径。&lt;/li&gt;
&lt;li&gt;支付诊断、资金结算、发布门禁、自动化处置：失败一次就是一次事故。用户不会接受「多试几次总有一次能诊断对」。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我在做支付转化异常归因时把这条设成了硬门禁：同一场景连续运行 k 次必须全部通过，才认为这个版本是可靠的。理由很直接——&lt;strong&gt;一个偶尔成功的诊断系统，和一个稳定的诊断系统，在生产上不是同一个产品。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;光有指标不够：三个配套件&lt;/h2&gt;
&lt;p&gt;指标本身只是分母和分子。要让 &lt;code&gt;pass^k&lt;/code&gt; 这个数字可信，还得配三样东西。&lt;/p&gt;
&lt;h3&gt;1. Ground Truth 隔离&lt;/h3&gt;
&lt;p&gt;这是最容易被忽略、也最致命的一条。&lt;/p&gt;
&lt;p&gt;如果被测的 Agent、它的工具或者它的 Prompt 有机会读到标准答案，那么评测出来的高分说明不了任何问题——&lt;strong&gt;虚假的高分比低分更危险&lt;/strong&gt;，因为低分会让你去修，高分只会让你放心发布。&lt;/p&gt;
&lt;p&gt;所以隔离必须是结构性的，不能靠自觉：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隐藏集只由评测侧维护，运行时进程&lt;strong&gt;物理上&lt;/strong&gt;读不到；&lt;/li&gt;
&lt;li&gt;评测器主动检查泄漏（而不是假设不会发生），把「GT 泄漏」做成一个和准确率同等重要的指标；&lt;/li&gt;
&lt;li&gt;隐藏集与开发集的场景&lt;strong&gt;错开构造&lt;/strong&gt;，防止针对考纲训练。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 故障注入与带标签的黄金场景&lt;/h3&gt;
&lt;p&gt;真实生产里，「失败」往往是稀疏且没有被完整记录过的。只等线上出问题来攒评测集，永远攒不够。&lt;/p&gt;
&lt;p&gt;可配置的故障注入解决这个问题：主动构造已知根因的异常（渠道超时、状态机错乱、配置发布与优惠变更叠加……），每个场景自带 Ground Truth，于是评测可以稳定复现、自动判分、版本回归。&lt;/p&gt;
&lt;p&gt;代价要说清楚：&lt;strong&gt;注入的分布由人设计，覆盖不到尚未被记录过的失败形态。&lt;/strong&gt; 这是模拟评测的固有边界，不是实现瑕疵。&lt;/p&gt;
&lt;h3&gt;3. 结果评测 + 轨迹评测，两条轨&lt;/h3&gt;
&lt;p&gt;只评最终答案会漏掉一半问题。一个 Agent 可能碰巧给出了正确答案，但过程是错的——绕了 20 步、查了不该查的数据、工具失败后没有正确降级。&lt;/p&gt;
&lt;p&gt;所以两条轨分开评：&lt;/p&gt;
&lt;p&gt;| 轨道 | 回答的问题 | 典型指标 |
| --- | --- | --- |
| 结果评测 | 最终答案对不对 | 准确率、F1、误差 |
| 轨迹评测 | 过程可不可靠 | 工具选择是否匹配范围、有无无效调用与循环、失败后是否正确降级、是否及时停止 |&lt;/p&gt;
&lt;p&gt;这两条轨经常给出相反的信号——&lt;strong&gt;结果对但过程错，恰恰是最危险的组合&lt;/strong&gt;，因为它说明系统不稳定，只是这次运气好。而这正是 &lt;code&gt;pass^k&lt;/code&gt; 要暴露的东西。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    A[&quot;故障注入&amp;#x3C;br/&gt;生成带 Ground Truth 的黄金场景&quot;] --&gt; B[&quot;隔离 GT，Agent 全程不可读&quot;]
    B --&gt; C[&quot;同一场景连续运行 k 次&quot;]
    C --&gt; D[&quot;结果评测&amp;#x3C;br/&gt;最终答案是否正确&quot;]
    C --&gt; E[&quot;轨迹评测&amp;#x3C;br/&gt;调查过程是否可靠&quot;]
    D --&gt; F[&quot;pass^k&amp;#x3C;br/&gt;k 次全部通过才计入&quot;]
    E --&gt; F
    F --&gt; G[&quot;版本门禁&amp;#x3C;br/&gt;不达标则拒绝发布&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一个容易踩的坑：预算会成为评测量纲&lt;/h2&gt;
&lt;p&gt;这是我自己撞过的墙，值得单独说。&lt;/p&gt;
&lt;p&gt;我用真实模型跑第一轮评测时，发现策略 Agent 的发现率异常低。查 Trace 才明白：90 秒的时间预算&lt;strong&gt;结构性截断&lt;/strong&gt;了 Agent——它平均 75.8 秒、13.75 步就被掐断，从未走到提交候选那一步。而 Random / BFS 这类确定性基线不受时间预算影响，于是这场对比从一开始就不公平。&lt;/p&gt;
&lt;p&gt;修正方式是&lt;strong&gt;只改一个变量&lt;/strong&gt;：把 &lt;code&gt;max_time_seconds&lt;/code&gt; 从 90 调到 300（依据实测 p95 延迟校准），Case 内容、期望答案、其他预算和门禁阈值全部不动，并声明旧结果不可复用于新口径。&lt;/p&gt;
&lt;p&gt;这里的方法论比数字重要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先测量，再改&lt;/strong&gt;——不要凭感觉调参；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只改一个变量&lt;/strong&gt;——否则无法归因；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旧结论作废&lt;/strong&gt;——口径变了，历史数字就不能再引用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果当时图省事顺手把 Prompt 也改了，那这轮评测就白跑了——你永远不会知道是预算还是 Prompt 起了作用。&lt;/p&gt;
&lt;h2&gt;关于「诚实降级」&lt;/h2&gt;
&lt;p&gt;最后一条是我认为最值得坚持的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;门禁不能因为没有好消息就变绿。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果评测口径是「发现率 ≥ 75% 才放行」，而实测只有 0–20%，那么正确的输出就是&lt;strong&gt;拒绝发布&lt;/strong&gt;，并且在文档里如实写「搜索层尚未达标」。把口径悄悄改成「发现机制可信」然后宣布通过，比指标难看危险得多。&lt;/p&gt;
&lt;p&gt;指标难看只是丢面子；门禁会为好消息让路，那它就不再是门禁了。&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pass@k&lt;/code&gt; 测能力上限，&lt;code&gt;pass^k&lt;/code&gt; 测连续可靠性，k 越大两者差距越大，方向相反；&lt;/li&gt;
&lt;li&gt;失败可重试的场景看 &lt;code&gt;pass@k&lt;/code&gt;，失败即事故的场景必须看 &lt;code&gt;pass^k&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;指标可信的前提是 GT 隔离、故障注入、双轨评测三件套；&lt;/li&gt;
&lt;li&gt;预算/步数这类资源约束会悄悄成为评测量纲，要一次只改一个变量；&lt;/li&gt;
&lt;li&gt;口径改变了，旧结论必须作废——&lt;strong&gt;拒绝发布也是评测系统的正常输出&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Prompt 定意图，Hook 定边界：Agent 的确定性从哪来</title><link>https://ygrowly.github.io/blog/20260911---prompt-intent-hook-boundary/post</link><guid isPermaLink="true">https://ygrowly.github.io/blog/20260911---prompt-intent-hook-boundary/post</guid><description>模型适合表达意图，不适合充当安全边界。拆解 Before/After Tool Hook 链如何应对越权调用、上下文膨胀与过程失忆三类 Agent 固有问题。</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在 Prompt 里写「不要删除生产数据」，和在代码里拒绝删除生产数据，是两种完全不同的保证。&lt;/p&gt;
&lt;p&gt;前者是&lt;strong&gt;概率&lt;/strong&gt;，后者是&lt;strong&gt;确定性&lt;/strong&gt;。Agent 系统里大量的问题，根源都是把本该由代码保证的事情交给了自然语言。&lt;/p&gt;
&lt;p&gt;一句话总结我的立场：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;能由代码确定性保证的事情，不交给模型自行决定。Prompt 适合表达意图，不适合充当安全边界。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这篇讲这套思路在工具调用层怎么落地——Before / After Tool Hook 链。&lt;/p&gt;
&lt;h2&gt;Agent 的三类固有问题&lt;/h2&gt;
&lt;p&gt;先把问题说清楚，因为 Hook 链的每个切面都是在回答其中一个。&lt;/p&gt;
&lt;h3&gt;1. 模型不理解操作的真实代价&lt;/h3&gt;
&lt;p&gt;对模型来说，&lt;code&gt;query_usage&lt;/code&gt;（查询用量）和 &lt;code&gt;publish_settlement&lt;/code&gt;（发布结算）只是两个不同的字符串。它没有「这个操作一旦执行，几百户的账单就定了」这种感知。&lt;/p&gt;
&lt;p&gt;这不是模型的缺陷，是它的本质——它预测的是 token，不是后果。所以&lt;strong&gt;让模型自己判断「这个操作危不危险」，等于让它做一件它结构上就做不好的事。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2. Token 预算是物理约束&lt;/h3&gt;
&lt;p&gt;一次查询返回几万行明细是常态。全塞进上下文，结果是两件事同时发生：真正需要推理的信息被挤出去了，成本还涨了。&lt;/p&gt;
&lt;h3&gt;3. 模型偏好最短路径&lt;/h3&gt;
&lt;p&gt;额外的校验、确认、记录中间状态，在模型看来都是「多出来的步骤」。它会倾向于跳过——不是不听话，而是最短路径在训练分布里天然得分更高。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[&quot;Agent 固有问题&quot;] --&gt; B[&quot;不理解操作代价&amp;#x3C;br/&gt;→ 越权与误操作&quot;]
    A --&gt; C[&quot;Token 预算有限&amp;#x3C;br/&gt;→ 上下文膨胀&quot;]
    A --&gt; D[&quot;偏好最短路径&amp;#x3C;br/&gt;→ 过程失忆&quot;]
    B --&gt; E[&quot;Before Tool Hook&amp;#x3C;br/&gt;校验失败即阻断&quot;]
    C --&gt; F[&quot;After Tool Hook&amp;#x3C;br/&gt;大结果外置 + 降级&quot;]
    D --&gt; G[&quot;After Tool + Before Model&amp;#x3C;br/&gt;强制登记与状态注入&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hook 链的三个切面&lt;/h2&gt;
&lt;h3&gt;Before Tool：执行前，失败即阻断&lt;/h3&gt;
&lt;p&gt;职责是在副作用发生之前把不该发生的挡掉：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参数 Schema 校验（类型、范围、必填）&lt;/li&gt;
&lt;li&gt;业务前置条件（对象是否存在、时间范围是否合理、数据是否完整）&lt;/li&gt;
&lt;li&gt;权限与作用域（这次调用是否在这个任务被授权的范围内）&lt;/li&gt;
&lt;li&gt;高风险操作的人工确认状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键词是&lt;strong&gt;阻断&lt;/strong&gt;。校验不通过就不执行，没有「尽力而为」的中间态。&lt;/p&gt;
&lt;h3&gt;After Tool：执行后，失败则降级&lt;/h3&gt;
&lt;p&gt;职责是登记事实、控制上下文、更新任务状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登记证据（调用 ID、查询指纹、结果哈希），让后续结论可以回溯到具体一次调用&lt;/li&gt;
&lt;li&gt;大结果外置为 Artifact，上下文里只留摘要和引用&lt;/li&gt;
&lt;li&gt;记录副作用（这次调用改变了什么）&lt;/li&gt;
&lt;li&gt;更新任务状态／假设台账&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里的关键词是&lt;strong&gt;降级&lt;/strong&gt;。外置失败不应该让整条链路挂掉——退回透传原文，功能弱一点但继续可用。&lt;/p&gt;
&lt;h3&gt;Before Model：下一轮调用前，注入状态&lt;/h3&gt;
&lt;p&gt;这一层最容易被忽略，但它解决的是「过程失忆」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把上一次工具调用的副作用注入下一轮（刚刚那次操作改变了什么）&lt;/li&gt;
&lt;li&gt;注入当前预算消耗&lt;/li&gt;
&lt;li&gt;注入尚未解决的问题，避免模型重复走已经排除的路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;没有这一层，模型每一步都在「从零开始想」，于是反复查同样的东西、反复验证已经确认过的事实。&lt;/p&gt;
&lt;h2&gt;核心设计原则：失败语义必须匹配操作代价&lt;/h2&gt;
&lt;p&gt;这是我认为整套设计里最值得抄的一条。&lt;/p&gt;
&lt;p&gt;| 方向 | 失败时应该做什么 | 为什么 |
| --- | --- | --- |
| 读（查询、检索） | &lt;strong&gt;降级&lt;/strong&gt;——退回透传原文 | 读错了代价可控，让流程继续比中断更有价值 |
| 写（发布、扣减、配置变更） | &lt;strong&gt;阻断&lt;/strong&gt;——拒绝执行 | 写错了不可撤销，宁可这次任务失败 |&lt;/p&gt;
&lt;p&gt;很多 Agent 框架的默认行为是「工具失败就重试」或者「工具失败就让模型想办法」。这两者在写操作上都是错的——&lt;strong&gt;写操作失败后的正确动作是停下来，而不是再试一次。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;风险分级不是独立机制，是 Hook 链的参数&lt;/h2&gt;
&lt;p&gt;常见的做法是给工具打风险标签，然后按标签走不同流程。听起来像两套机制，其实是一套：&lt;strong&gt;风险等级只是 Hook 链的参数配置。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;R0（只读查询）    → After Tool 登记结果即可，Before Tool 不做阻断
R1（草稿、备注）  → Before Tool 校验预览，失败阻断
R2（启停、归属、结算发布）
                  → Before Tool 校验：二次确认 + 影响说明 + 幂等键 + 审计，任一失败即阻断
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样设计的好处是：新增一个工具时，你只需要回答「它是几级」，而不是「它要走哪条特殊流程」。&lt;strong&gt;分级把组合爆炸压成了单选。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;为什么这件事不能交给 Prompt&lt;/h2&gt;
&lt;p&gt;一个自然的反驳是：把上面这些规则写进 System Prompt 不就行了？&lt;/p&gt;
&lt;p&gt;三个理由说明为什么不行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prompt 的生效是概率性的。&lt;/strong&gt; 同一条指令在长上下文里会被稀释；上下文越长，越靠后的约束越容易被忽略。安全边界不能有「大部分时候生效」这个状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt 无法感知真实状态。&lt;/strong&gt; 它不知道这次调用传进来的 &lt;code&gt;tenant_id&lt;/code&gt; 是不是当前用户所属的租户，也不知道这个对象在数据库里是否真的存在。校验需要读真实状态，Prompt 读不到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt 会被推理绕过。&lt;/strong&gt; 只要边界写在自然语言里，它就只是输入的一部分，可以被后续输入覆盖、重述、重新解释。写在代码里的边界不会。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;反过来说，Prompt 擅长的是&lt;strong&gt;意图表达&lt;/strong&gt;：这次任务的目标是什么、什么算完成、输出希望是什么形态。这部分本来就不该硬编码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两者分工，而不是二选一。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;边界与反对意见&lt;/h2&gt;
&lt;p&gt;这套东西不是没有代价，说清楚它什么时候不适用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hook 链是有成本的。&lt;/strong&gt; 每个工具都要定义 Schema、风险等级、幂等键、审计字段，新增写工具的成本远高于加一句 Prompt。工具少、全是只读的场景，上这套是过度设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它会降低灵活性。&lt;/strong&gt; R2 级操作要求逐次人工确认，批量或高频场景下的确认体验目前没有好答案。这是真实的取舍，不是可以轻易抹平的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它不解决模型能力问题。&lt;/strong&gt; Hook 链保证的是「不会发生不该发生的事」，不是「一定能做成想做的事」。两者都要，但别指望前者顺带解决后者。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等键的设计比 Hook 本身更难。&lt;/strong&gt; 重试时如果换了幂等键，约束就永远不会生效——这是 Hook 链最常见的一个静默失效点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;模型预测 token，不预测后果——所以「危险判断」不该由它做；&lt;/li&gt;
&lt;li&gt;Before Tool 阻断、After Tool 降级、Before Model 注入状态，三个切面各解决一类固有问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败语义要匹配操作代价&lt;/strong&gt;：读降级、写阻断；&lt;/li&gt;
&lt;li&gt;风险分级是 Hook 链的参数，不是第二套机制——它把组合爆炸压成单选；&lt;/li&gt;
&lt;li&gt;Prompt 负责意图，代码负责边界，这是分工不是替代；&lt;/li&gt;
&lt;li&gt;这套设计有成本，工具全是只读、调用量很低的场景不值得上。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>从一次请求读懂 Pi：Agent Runtime 与 Coding Harness 源码剖析</title><link>https://ygrowly.github.io/blog/20260801---pi-agent-runtime-coding-harness/post</link><guid isPermaLink="true">https://ygrowly.github.io/blog/20260801---pi-agent-runtime-coding-harness/post</guid><description>沿着一次读取 package.json 的请求，拆解 Pi 的模型适配、Agent Loop、工具执行、上下文投影、事件流与 Session 持久化。</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>import InterviewQuestionDeck from &apos;@/components/blog/InterviewQuestionDeck&apos;
import { piAgentInterviewQuestions } from &apos;@/data/interview/pi-agent-questions&apos;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;源码仓库：&lt;a href=&quot;https://github.com/earendil-works/pi&quot;&gt;earendil-works/pi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;分析版本：&lt;a href=&quot;https://github.com/earendil-works/pi/tree/aa0ec808b970db31822e07835a46647cb51d9d66&quot;&gt;aa0ec808b970db31822e07835a46647cb51d9d66&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Commit 时间：2026-08-01&lt;/li&gt;
&lt;li&gt;对应包版本：&lt;code&gt;0.83.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pi 既是一个可以直接使用的终端 Coding Agent，也是一套分层复用的 Agent 工程实现。本文不按仓库目录逐文件介绍，而是追踪一个请求：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;读取 &lt;code&gt;package.json&lt;/code&gt;，告诉我项目使用了哪些关键依赖。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条请求会经过运行环境组装、模型流式生成、&lt;code&gt;read&lt;/code&gt; Tool Call、参数校验、文件读取、Tool Result 回填、第二次模型生成和 Session 持久化。沿着这条链路，可以把模型适配、Agent Loop、工具系统、上下文、事件流和会话存储放进同一张地图，而不是分别背诵概念。&lt;/p&gt;
&lt;p&gt;本文中的 &lt;code&gt;pi-agent-core&lt;/code&gt; 指 npm 包 &lt;code&gt;@earendil-works/pi-agent-core&lt;/code&gt;，源码位于 &lt;code&gt;packages/agent&lt;/code&gt;。早期资料中的 &lt;code&gt;badlogic/pi-mono&lt;/code&gt;、&lt;code&gt;@mariozechner/*&lt;/code&gt; 等名称已经迁移，本文只以锁定 Commit 的实现为准。&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;第一部分：15 张问题卡，先测再读&lt;/h1&gt;
&lt;p&gt;这 15 个问题既是源码阅读入口，也是全文的覆盖检查。先尝试回答，再翻面核对面试标答、答题结构与实现证据。&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h1&gt;第二部分：沿一次请求拆解 Pi&lt;/h1&gt;
&lt;h2&gt;第 1 章：Pi Agent 到底是什么&lt;/h2&gt;
&lt;h3&gt;1.1 从 Coding Agent 到 Agent Harness&lt;/h3&gt;
&lt;p&gt;从使用者视角看，Pi 是一个能读取文件、执行命令、编辑代码并保存会话的终端 Coding Agent。从源码视角看，它更像一组可组合的基础构件：底层统一模型调用，中层负责 Agent 的运行语义，上层再补齐编码场景所需的工具、上下文、会话和交互。&lt;/p&gt;
&lt;p&gt;“极简”并不表示实现只有一个 &lt;code&gt;while&lt;/code&gt; 循环。一个真正可用的 Agent 至少要处理流式输出、工具调用、错误语义、用户中止、运行中插话、上下文投影和状态恢复。Pi 的“最小”是把这些通用机制放进内核，同时拒绝替所有使用者预设完整工作流。&lt;/p&gt;
&lt;p&gt;因此，Pi 的核心取向可以概括为：提供原子化能力，尽量少规定 features。它默认没有 MCP、子 Agent、Plan Mode、权限弹出窗口、Todo System 和 Background Bash，但允许使用 Extension、Skill 或外部环境组合这些能力。这不是“做不到”，而是明确把工作流策略留在内核之外。&lt;/p&gt;
&lt;h3&gt;1.2 为什么沿一次请求阅读&lt;/h3&gt;
&lt;p&gt;按目录阅读容易得到一堆互不相连的认识：&lt;code&gt;types.ts&lt;/code&gt; 定义了消息，&lt;code&gt;agent-loop.ts&lt;/code&gt; 有循环，&lt;code&gt;session-manager.ts&lt;/code&gt; 写 JSONL。沿一次请求阅读则会持续追问三个问题：请求现在走到哪里，Pi 在这一层做了什么，为什么由这一层负责。&lt;/p&gt;
&lt;p&gt;本文不讨论通用 ReAct 历史，也不把业务系统中的权限、幂等、审核和 Eval 强行映射进 Pi。Pi 提供的是模型适配、Agent Runtime 和 Coding Harness；上层业务的成功标准、数据可信度与操作授权仍要由具体应用负责。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 2 章：三层架构与一次请求的总路线&lt;/h2&gt;
&lt;h3&gt;2.1 三层主干与一个正交 UI 层&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    CA[&quot;pi-coding-agent&amp;#x3C;br/&gt;Coding Harness&quot;] --&gt; AC[&quot;pi-agent-core&amp;#x3C;br/&gt;Agent Runtime&quot;]
    AC --&gt; AI[&quot;pi-ai&amp;#x3C;br/&gt;Model Runtime&quot;]
    CA --&gt; TUI[&quot;pi-tui&amp;#x3C;br/&gt;交互组件&quot;]
    AI --&gt; P[&quot;Providers / APIs&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pi-ai&lt;/code&gt; 负责模型、Provider、认证、请求翻译和流式响应归一化。它能产生包含文本、思考和 Tool Call 的 &lt;code&gt;AssistantMessage&lt;/code&gt;，但不会执行工具。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pi-agent-core&lt;/code&gt; 在其上组织 Agent Run。它持有消息、工具和运行状态，把 &lt;code&gt;AgentMessage[]&lt;/code&gt; 投影成模型可理解的 &lt;code&gt;Message[]&lt;/code&gt;，调用 &lt;code&gt;pi-ai&lt;/code&gt;，执行 Tool Call，再把 Tool Result 放回下一次模型调用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pi-coding-agent&lt;/code&gt; 把通用 Runtime 组装成编码产品。它选择模型与认证，构造 System Prompt，加载项目指令、Skills、Prompt Templates 和 Extensions，注册 &lt;code&gt;read/bash/edit/write&lt;/code&gt; 等工具，并通过 &lt;code&gt;AgentSession&lt;/code&gt; 管理持久化、压缩、重试和运行中交互。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pi-tui&lt;/code&gt; 不决定 Agent 下一步做什么。它消费事件、显示增量文本与工具状态、收集用户输入。Runtime 不直接依赖某个终端界面，因此相同内核也能服务 Interactive、Print/JSON、RPC 和 SDK。&lt;/p&gt;
&lt;h3&gt;2.2 为什么依赖只能向下&lt;/h3&gt;
&lt;p&gt;依赖方向表达的是知识边界。&lt;code&gt;pi-ai&lt;/code&gt; 不需要知道文件系统、Session 或 Coding Agent；&lt;code&gt;pi-agent-core&lt;/code&gt; 只依赖统一模型协议，不关心 &lt;code&gt;read&lt;/code&gt; 的具体实现；&lt;code&gt;pi-coding-agent&lt;/code&gt; 则可以同时使用下层类型，完成场景化组装。&lt;/p&gt;
&lt;p&gt;这使复用深度变得明确：只需要多模型调用时使用 &lt;code&gt;pi-ai&lt;/code&gt;；需要通用 Agent Loop 时再引入 &lt;code&gt;pi-agent-core&lt;/code&gt;；需要完整编码 Harness 时才使用 &lt;code&gt;pi-coding-agent&lt;/code&gt;。如果反向依赖，底层模型库就会被 CLI、文件工具和 Session 规则绑住，无法作为独立能力复用。&lt;/p&gt;
&lt;p&gt;上层直接使用底层类型并不破坏分层。例如 Coding Agent 的 &lt;code&gt;convertToLlm()&lt;/code&gt; 返回 &lt;code&gt;pi-ai&lt;/code&gt; 的 &lt;code&gt;Message[]&lt;/code&gt;，自定义工具最终满足 &lt;code&gt;AgentTool&lt;/code&gt;。分层要求的是底层不知道上层，而不是禁止上层复用底层协议。&lt;/p&gt;
&lt;h3&gt;2.3 一次请求怎样穿过三层&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    U[&quot;用户请求&quot;] --&gt; S[&quot;AgentSession.prompt()&quot;]
    S --&gt; L[&quot;Agent Loop&quot;]
    L --&gt; M[&quot;pi-ai / Provider&quot;]
    M --&gt; C[&quot;AssistantMessage: read Tool Call&quot;]
    C --&gt; E[&quot;校验并执行 read&quot;]
    E --&gt; R[&quot;Tool Result 回填&quot;]
    R --&gt; M2[&quot;再次模型生成&quot;]
    M2 --&gt; A[&quot;最终回答与 Session 追加&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户输入先到 &lt;code&gt;AgentSession.prompt()&lt;/code&gt;。Prompt Template、Skill 命令和 Extension 命令等上层逻辑在这里处理，随后标准用户消息交给 &lt;code&gt;Agent.prompt()&lt;/code&gt;。Agent Loop 构造模型 Context，调用 &lt;code&gt;pi-ai&lt;/code&gt;。模型第一次生成 &lt;code&gt;read({path:&quot;package.json&quot;})&lt;/code&gt;；Runtime 校验并执行工具，把结果封装为 &lt;code&gt;ToolResultMessage&lt;/code&gt;；因为上一轮包含 Tool Call，Loop 再发起一次模型生成，最终得到自然语言答案。与此同时，&lt;code&gt;AgentSession&lt;/code&gt; 监听消息结束事件，将用户消息、AssistantMessage 和 Tool Result 依次追加到 Session Tree。&lt;/p&gt;
&lt;h3&gt;2.4 四类状态不要混为一谈&lt;/h3&gt;
&lt;p&gt;| 状态 | 持有者 | 作用 |
| --- | --- | --- |
| Provider、Model、认证与运行配置 | &lt;code&gt;pi-ai&lt;/code&gt; / Coding Agent 配置 | 决定请求发给谁、怎样认证和调用 |
| Agent Messages 与执行状态 | &lt;code&gt;pi-agent-core.Agent&lt;/code&gt; | 表达当前 transcript、流式消息和运行中工具 |
| 本次模型 Context | 每次调用前临时构造 | 只包含当前模型此刻应该看见的内容 |
| Session Tree | &lt;code&gt;pi-coding-agent.SessionManager&lt;/code&gt; | 保存可恢复、可分支的历史事实 |
| UI 展示状态 | TUI 或其他事件消费者 | 呈现流式文本、工具进度和队列状态 |&lt;/p&gt;
&lt;p&gt;同一条消息可能先成为 Session Entry，恢复时投影为 AgentMessage，再在某次调用前转换为模型 Message。三者相关，却不是同一个对象。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 3 章：&lt;code&gt;pi-ai&lt;/code&gt; 如何完成一次真实模型调用&lt;/h2&gt;
&lt;h3&gt;3.1 从统一请求到 Provider 请求&lt;/h3&gt;
&lt;p&gt;Agent Runtime 调用模型时，交给 &lt;code&gt;pi-ai&lt;/code&gt; 的核心输入是 &lt;code&gt;Model&lt;/code&gt;、&lt;code&gt;Context&lt;/code&gt; 和流式选项。&lt;code&gt;Context&lt;/code&gt; 只包含 &lt;code&gt;systemPrompt&lt;/code&gt;、&lt;code&gt;messages&lt;/code&gt; 和 &lt;code&gt;tools&lt;/code&gt;；&lt;code&gt;Model&lt;/code&gt; 则描述 provider、api、模型 ID、上下文窗口、最大输出、输入模态、推理能力与成本。&lt;/p&gt;
&lt;p&gt;这里要区分 Provider 和 API。Provider 是具体运行单元，拥有标识、认证方式、模型列表和流式入口；API 实现负责把统一 Context 翻译成 Anthropic Messages、OpenAI Responses、Google Generative AI 等协议。一个 Provider 可以按模型的 &lt;code&gt;model.api&lt;/code&gt; 分派到不同 API 实现。&lt;code&gt;Models&lt;/code&gt; 集合负责找到模型所属 Provider、解析认证并委托调用，而不是自己实现所有厂商协议。&lt;/p&gt;
/* 这是一个文本绘图，源码为：flowchart LR
  CTX[&quot;Model + Context&quot;] --&gt; MS[&quot;Models.streamSimple&quot;]
  MS --&gt; PR[&quot;Provider&quot;]
  PR --&gt; API[&quot;API translator&quot;]
  API --&gt; UP[&quot;Upstream model&quot;] --&gt; */
&lt;p&gt;&lt;img src=&quot;https://cdn.nlark.com/yuque/__mermaid_v3/a6a7f260730b82011c213c18d9cb82b2.svg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;认证在每次请求前解析，而不是在会话启动时永久冻结。这对会过期的 OAuth Token 很重要：长时间工具执行后，下一次 Generation 可以重新取得有效凭证。Coding Agent 还在这一层补充超时、重试、归因 Header 和 Provider 请求扩展 Hook。&lt;/p&gt;
&lt;h3&gt;3.2 模型输出为什么不是字符串&lt;/h3&gt;
&lt;p&gt;上游返回的是一个过程。Pi 将不同厂商的流式协议统一为 &lt;code&gt;AssistantMessageEventStream&lt;/code&gt;：先发出 &lt;code&gt;start&lt;/code&gt;，随后可能交错出现 &lt;code&gt;text_*&lt;/code&gt;、&lt;code&gt;thinking_*&lt;/code&gt; 和 &lt;code&gt;toolcall_*&lt;/code&gt;，最后以 &lt;code&gt;done&lt;/code&gt; 或 &lt;code&gt;error&lt;/code&gt; 收束。&lt;/p&gt;
&lt;p&gt;这些增量不断更新同一个 partial &lt;code&gt;AssistantMessage&lt;/code&gt;。最终消息包含内容块、实际 Provider/API/Model、usage、cost、&lt;code&gt;stopReason&lt;/code&gt;、时间戳，以及错误时的 &lt;code&gt;errorMessage&lt;/code&gt;。&lt;code&gt;stopReason&lt;/code&gt; 可能是 &lt;code&gt;stop&lt;/code&gt;、&lt;code&gt;length&lt;/code&gt;、&lt;code&gt;toolUse&lt;/code&gt;、&lt;code&gt;error&lt;/code&gt; 或 &lt;code&gt;aborted&lt;/code&gt;；流式阶段使用的 &lt;code&gt;pending&lt;/code&gt; 不应作为最终 Session 消息保存。&lt;/p&gt;
&lt;p&gt;以示例为例，第一次模型响应可能先产生少量 thinking，再逐步生成 &lt;code&gt;read&lt;/code&gt; 的工具名与 JSON 参数，最终以 &lt;code&gt;toolUse&lt;/code&gt; 结束。此时“一次模型生成”已经完整结束，但 Agent 还没有回答用户。Tool Call 只是结构化内容块，是否执行由上一层决定。&lt;/p&gt;
&lt;p&gt;Pi 的 &lt;code&gt;EventStream&lt;/code&gt; 同时支持异步迭代和 &lt;code&gt;result()&lt;/code&gt;：前者让 UI 实时消费事件，后者让 Runtime 获得聚合后的最终 &lt;code&gt;AssistantMessage&lt;/code&gt;。因此流式展示和最终状态不是两套互相独立的实现。&lt;/p&gt;
&lt;h3&gt;3.3 Error、Abort 与部分输出&lt;/h3&gt;
&lt;p&gt;模型请求失败不应只表现为一个脱离上下文的 Promise rejection。&lt;code&gt;pi-ai&lt;/code&gt; 的流式契约要求请求、模型或运行时失败通过 &lt;code&gt;error&lt;/code&gt; 事件和最终 &lt;code&gt;AssistantMessage&lt;/code&gt; 表达，&lt;code&gt;stopReason&lt;/code&gt; 为 &lt;code&gt;error&lt;/code&gt; 或 &lt;code&gt;aborted&lt;/code&gt;。这样 Runtime、Session 和 UI 都能观察到同一种结束语义。&lt;/p&gt;
&lt;p&gt;错误前已经产生的部分文本仍可存在于 partial message 中，但 Coding Agent 在跨请求重放历史时会跳过 &lt;code&gt;error&lt;/code&gt; 和 &lt;code&gt;aborted&lt;/code&gt; 的 AssistantMessage，因为它们可能包含不完整推理或残缺 Tool Call。保留错误事实用于展示和审计，不等于把残缺内容继续发给下一模型。&lt;/p&gt;
&lt;h3&gt;3.4 切换模型时，统一抽象的边界&lt;/h3&gt;
&lt;p&gt;会话切换 Provider 后，旧消息不会原样塞入新协议。&lt;code&gt;transformMessages()&lt;/code&gt; 会根据目标模型处理跨模型差异：不支持图像时用占位文本替换图片；跨模型时把可读 thinking 降级为普通文本，丢弃只对原模型有效的 redacted thinking；移除 Provider 专属的 thought signature；必要时归一化 Tool Call ID，并同步更新 Tool Result 的关联 ID。&lt;/p&gt;
&lt;p&gt;它还会为孤立 Tool Call 补合成错误结果，避免新 Provider 收到不完整工具对。错误或中止的 AssistantMessage 则不参与重放。&lt;/p&gt;
&lt;p&gt;这说明统一接口解决的是“共同协议和调用方式”，不是让所有模型完全等价。加密推理签名、Prompt Cache、上下文窗口、工具约束采样和图像能力仍然具有 Provider 语义。跨模型会话可以继续，但转换必然可能有损。&lt;/p&gt;
&lt;p&gt;本章对应的主要源码是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;packages/ai/src/types.ts&lt;/code&gt;、&lt;code&gt;packages/ai/src/models.ts&lt;/code&gt;、&lt;code&gt;packages/ai/src/utils/event-stream.ts&lt;/code&gt;、&lt;code&gt;packages/ai/src/api/transform-messages.ts&lt;/code&gt;，以及 &lt;code&gt;packages/ai/src/api/*&lt;/code&gt; 下的具体协议实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 4 章：Agent Loop 如何组织多次模型生成&lt;/h2&gt;
&lt;h3&gt;4.1 Prompt 如何启动一次运行&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Agent&lt;/code&gt; 是有状态的 Runtime 包装。它保存 System Prompt、当前 Model、Thinking Level、工具集合、完整 Agent Messages，以及 &lt;code&gt;isStreaming&lt;/code&gt;、partial message、pending tool calls 和最近错误等运行状态。&lt;/p&gt;
&lt;p&gt;调用 &lt;code&gt;prompt()&lt;/code&gt; 时，字符串先被标准化为 &lt;code&gt;UserMessage&lt;/code&gt;。如果 Agent 已在运行，新的 &lt;code&gt;prompt()&lt;/code&gt; 会被拒绝，调用方必须等待结束，或明确使用 steering / follow-up 入队。随后 &lt;code&gt;runAgentLoop()&lt;/code&gt; 发出 &lt;code&gt;agent_start&lt;/code&gt;、&lt;code&gt;turn_start&lt;/code&gt; 和用户消息事件，再开始第一次模型 Generation。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;continue()&lt;/code&gt; 不会自动添加新用户消息，而是从现有 transcript 的最后一条 user 或 tool result 继续，适合重试或 Session 恢复后的续跑。如果最后一条是 assistant，只有队列里已有 steering / follow-up 时才会用它们开启新运行，否则会拒绝继续。这一限制保证 Provider 不会收到语义不完整的上下文尾部。&lt;/p&gt;
&lt;h3&gt;4.2 Generation、Turn 与 Run&lt;/h3&gt;
&lt;p&gt;| 概念 | 起止范围 | 示例中的数量 |
| --- | --- | --- |
| Generation | 一次 Provider 请求到一个最终 &lt;code&gt;AssistantMessage&lt;/code&gt; | 两次：提出 &lt;code&gt;read&lt;/code&gt;，再生成答案 |
| Turn | 一个 AssistantMessage 加上它触发的整批 Tool Results | 两次 |
| Agent Run | 从一次 &lt;code&gt;prompt/continue&lt;/code&gt; 到 &lt;code&gt;agent_end&lt;/code&gt; | 一次 |&lt;/p&gt;
&lt;p&gt;Pi 的 Turn 不是传统聊天中“用户一句＋助手一句”的宽泛说法。在源码事件语义里，一个 Turn 以 assistant 生成和其工具批次为中心。Tool Result 会推动下一个 Turn；没有 Tool Call、steering 和 follow-up 时，整个 Run 才结束。&lt;/p&gt;
&lt;h3&gt;4.3 AgentMessage 如何投影成模型 Context&lt;/h3&gt;
&lt;p&gt;每次 Provider 调用前，Loop 都执行同一条边界转换：&lt;/p&gt;
/* 这是一个文本绘图，源码为：flowchart LR
  AM[&quot;AgentMessage[]&quot;] --&gt; TC[&quot;transformContext()&quot;]
  TC --&gt; CL[&quot;convertToLlm()&quot;]
  CL --&gt; MM[&quot;Message[]&quot;]
  MM --&gt; CX[&quot;Context&quot;] --&gt; */
&lt;p&gt;&lt;img src=&quot;https://cdn.nlark.com/yuque/__mermaid_v3/1b02027d754b5d2b7db535fa308e4c80.svg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AgentMessage&lt;/code&gt; 比模型 &lt;code&gt;Message&lt;/code&gt; 更丰富。Coding Agent 还需要保存直接 Bash 执行、Extension 自定义消息、Branch Summary 和 Compaction Summary。它们有 UI 或恢复语义，却不能全部按原类型发送给 Provider。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transformContext()&lt;/code&gt; 工作在 AgentMessage 层，适合让 Extension 动态注入、删除或修改上下文。&lt;code&gt;convertToLlm()&lt;/code&gt; 才完成模型协议投影：普通 user/assistant/toolResult 直接保留；可见 Bash 记录、Custom Message 和两类 Summary 转成 user message；标记为排除上下文的 Bash 记录被过滤。若设置禁止图片，还会把图片替换为说明文本。&lt;/p&gt;
&lt;p&gt;因此，Agent State 是运行事实，Model Context 是一次性的视图。先保留丰富语义，再在调用边界转换，能够同时服务 Session、UI、扩展和不同 Provider；代价是多了一条必须理解和测试的转换链。&lt;/p&gt;
&lt;h3&gt;4.4 Loop 为什么继续，又为什么停止&lt;/h3&gt;
&lt;p&gt;第一次 Generation 得到 &lt;code&gt;read&lt;/code&gt; Tool Call 后，AssistantMessage 先写入状态。Loop 找出所有 Tool Call，执行并追加 Tool Results，然后发出 &lt;code&gt;turn_end&lt;/code&gt;。因为存在工具结果，内层循环继续，下一次 Generation 能看到“模型提出了什么调用”以及“工具实际返回了什么”。&lt;/p&gt;
&lt;p&gt;第二次 Generation 没有 Tool Call。Loop 在当前 Turn 结束后检查 steering；没有则离开内层循环，再检查 follow-up；仍没有才发出 &lt;code&gt;agent_end&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果 AssistantMessage 以 &lt;code&gt;error&lt;/code&gt; 或 &lt;code&gt;aborted&lt;/code&gt; 结束，当前 Turn 和 Run 会直接收束。若输出因 token 上限以 &lt;code&gt;length&lt;/code&gt; 结束且其中出现 Tool Call，Pi 不会冒险执行可能被截断的参数，而是为整批调用生成错误 Tool Result，要求模型重新发出完整调用。&lt;/p&gt;
&lt;p&gt;Loop 还允许 &lt;code&gt;prepareNextTurn&lt;/code&gt; 在 Turn 之间替换下一轮 Context、Model 或 Thinking Level，也允许 &lt;code&gt;shouldStopAfterTurn&lt;/code&gt; 做优雅停止。它不是“固定循环 N 次”，而是由模型输出、工具结果、队列和 Runtime 状态共同决定下一步。&lt;/p&gt;
&lt;p&gt;本章对应 &lt;code&gt;packages/agent/src/agent.ts&lt;/code&gt;、&lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt;、&lt;code&gt;packages/agent/src/types.ts&lt;/code&gt;，Coding Agent 的消息转换位于 &lt;code&gt;packages/coding-agent/src/core/messages.ts&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 5 章：一次 Tool Call 如何真正变成工具结果&lt;/h2&gt;
&lt;h3&gt;5.1 Tool Call 只是执行请求&lt;/h3&gt;
&lt;p&gt;模型生成的 &lt;code&gt;ToolCall&lt;/code&gt; 只有 ID、名称和参数。Runtime 收到它后先在当前工具集合中按名称查找 &lt;code&gt;AgentTool&lt;/code&gt;。找不到工具、参数不符合 Schema、参数预处理失败或 &lt;code&gt;beforeToolCall&lt;/code&gt; 明确阻断时，都不会执行真实动作，而是生成 &lt;code&gt;isError: true&lt;/code&gt; 的 Tool Result。&lt;/p&gt;
&lt;p&gt;三层中的工具类型逐步增加职责。&lt;code&gt;pi-ai.Tool&lt;/code&gt; 只定义给模型看的名称、描述和参数 Schema；&lt;code&gt;pi-agent-core.AgentTool&lt;/code&gt; 增加 label、&lt;code&gt;execute()&lt;/code&gt;、进度回调和执行模式；Coding Agent 的 &lt;code&gt;ToolDefinition&lt;/code&gt; 再补充 Prompt 片段、运行上下文与 TUI 渲染能力，最后通过 wrapper 适配为 AgentTool。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    T1[&quot;pi-ai Tool&amp;#x3C;br/&gt;模型可见协议&quot;] --&gt; T2[&quot;AgentTool&amp;#x3C;br/&gt;可执行能力&quot;]
    T2 --&gt; T3[&quot;ToolDefinition&amp;#x3C;br/&gt;Coding UI / Context&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把“模型提出调用”和“系统执行动作”分开，才有位置实施 Schema 校验、权限检查、参数兼容和审计。如果两者等同，模型输出一段 JSON 就会直接成为副作用，Runtime 无法建立可靠边界。&lt;/p&gt;
&lt;h3&gt;5.2 &lt;code&gt;read&lt;/code&gt; 如何执行&lt;/h3&gt;
&lt;p&gt;示例中的 &lt;code&gt;read&lt;/code&gt; 参数先经过 TypeBox Schema 校验。随后工具解析相对或绝对路径，检查可读性，识别文本或图片，再执行实际读取。文本输出默认最多保留 2000 行或 50 KB，按先触达的限制截断，并在 details 中保留截断元数据；模型可根据提示继续使用 offset / limit 读取后续内容。&lt;/p&gt;
&lt;p&gt;这说明工具输出截断不是 UI 的省略显示，而是 Context 管理的一部分。若把任意大文件完整塞回模型，一次读取就可能耗尽上下文。工具既要完成外部动作，也要把结果整理成适合再次推理的形态。&lt;/p&gt;
&lt;p&gt;工具执行时会获得 &lt;code&gt;AbortSignal&lt;/code&gt; 和 &lt;code&gt;onUpdate&lt;/code&gt;。长任务可以报告 &lt;code&gt;tool_execution_update&lt;/code&gt;，也应主动响应中止。工具抛出的异常会被 Runtime 捕获并转换为错误 Tool Result，而不是让整个 Agent 立即丢失上下文。&lt;code&gt;afterToolCall&lt;/code&gt; 还可以在结果进入消息流前替换 content、details、usage、error 标志或终止提示。&lt;/p&gt;
&lt;h3&gt;5.3 Tool Result 怎样回到 Loop&lt;/h3&gt;
&lt;p&gt;执行完成后，Runtime 用原始 &lt;code&gt;toolCall.id&lt;/code&gt; 构造 &lt;code&gt;ToolResultMessage.toolCallId&lt;/code&gt;，同时记录工具名、内容、details、usage 和 &lt;code&gt;isError&lt;/code&gt;。这个 ID 是模型提出的请求与外部执行结果之间的关联键。只在终端打印文件内容并不够；结果必须作为消息重新进入 transcript，下一次模型生成才能依据真实数据完成回答。&lt;/p&gt;
&lt;p&gt;完整工具管道是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    C[&quot;Tool Call&quot;] --&gt; V[&quot;查找、预处理、Schema 校验&quot;]
    V --&gt; H1[&quot;beforeToolCall&quot;]
    H1 --&gt; EX[&quot;执行与进度更新&quot;]
    EX --&gt; H2[&quot;afterToolCall&quot;]
    H2 --&gt; TR[&quot;Tool Result Message&quot;]
    TR --&gt; N[&quot;下一次 Generation&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;失败也进入同一管道。未知工具、非法参数、Hook 阻断和执行异常的来源不同，但都会形成模型可读的错误结果。这样模型仍可修正参数、换用其他工具或向用户说明阻塞。&lt;/p&gt;
&lt;h3&gt;5.4 多工具的顺序、并行和 Abort&lt;/h3&gt;
&lt;p&gt;默认工具策略是 parallel，但准备阶段仍按模型声明顺序进行；如果全局设置为 sequential，或本批任一工具声明 &lt;code&gt;executionMode: &quot;sequential&quot;&lt;/code&gt;，整批按顺序执行。&lt;/p&gt;
&lt;p&gt;并行模式下，允许执行的工具并发运行，&lt;code&gt;tool_execution_end&lt;/code&gt; 按实际完成时间出现；全部结束后，Tool Result 消息仍按 AssistantMessage 中 Tool Call 的原始顺序回填。这样 UI 能及时显示谁先完成，模型上下文却保持稳定、可复现的顺序。&lt;/p&gt;
&lt;p&gt;AbortSignal 会传到 Hook 和工具。顺序模式在发现中止后不再启动后续调用；并行模式中已经启动的工具需要自行响应 Signal，尚未准备的调用不会继续启动。已经完成的结果仍可保留。中止不是数据库事务回滚，Runtime 无法自动撤销外部世界中已经发生的副作用。&lt;/p&gt;
&lt;p&gt;一个批次只有在每个最终 Tool Result 都设置 &lt;code&gt;terminate: true&lt;/code&gt; 时，才不会由本批工具继续触发下一次模型生成；如果另有 steering 或 follow-up，Run 仍可继续。单个工具无权在同批其他工具仍需处理时擅自终止整个 Run。&lt;/p&gt;
&lt;p&gt;本章主要对应 &lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt;、&lt;code&gt;packages/agent/src/types.ts&lt;/code&gt;、&lt;code&gt;packages/coding-agent/src/core/tools/*&lt;/code&gt; 和 &lt;code&gt;packages/coding-agent/src/core/tools/tool-definition-wrapper.ts&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 6 章：事件流如何让运行过程可观察、可干预&lt;/h2&gt;
&lt;h3&gt;6.1 四层生命周期事件&lt;/h3&gt;
&lt;p&gt;Agent Runtime 不直接调用终端 UI，而是输出事件。一次示例请求大致产生以下顺序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant UI as Event Consumer
    participant A as Agent Runtime
    participant M as Model
    participant T as read Tool
    A-&gt;&gt;UI: agent_start / turn_start
    A-&gt;&gt;M: Generation 1
    M--&gt;&gt;A: message_update ... toolCall
    A-&gt;&gt;T: tool_execution_start
    T--&gt;&gt;A: result
    A-&gt;&gt;UI: tool_execution_end / turn_end
    A-&gt;&gt;M: Generation 2
    M--&gt;&gt;A: final answer
    A-&gt;&gt;UI: turn_end / agent_end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agent 事件表示整次 Run，Turn 事件圈住一次 AssistantMessage 及其工具批次，Message 事件表达用户、assistant 和 tool result 的开始、更新与结束，Tool 事件表达外部执行生命周期。UI、JSON 输出、RPC 适配器和 SDK 调用方可以消费同一套事实，只选择不同呈现方式。&lt;/p&gt;
&lt;h3&gt;6.2 为什么监听器会影响运行时序&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Agent.subscribe()&lt;/code&gt; 的监听器按订阅顺序执行，返回的 Promise 会被等待。即使已经发出 &lt;code&gt;agent_end&lt;/code&gt;，Agent 也要等对应监听器完成后才真正进入 idle。这保证 Session 持久化、扩展处理和 UI 状态不会落后于“任务已经结束”的信号。&lt;/p&gt;
&lt;p&gt;代价是监听器不再只是旁路日志。缓慢监听器会增加延迟，抛错也可能影响运行。Pi 选择了强顺序与一致性，而不是把所有观察者都做成不可控的 fire-and-forget。工具的高频 update 由执行函数收集对应 Promise，并在工具结束前统一等待，避免工具已经宣布完成但进度事件仍在漂移。&lt;/p&gt;
&lt;p&gt;只返回最终答案无法支撑 Coding Agent：用户看不到模型是否仍在思考、正在读取哪个文件、某个命令是否卡住，也无法在运行中插入新意图。事件流把“执行过程”提升为公共输出。&lt;/p&gt;
&lt;h3&gt;6.3 steering 与 follow-up 为什么不能合并&lt;/h3&gt;
&lt;p&gt;steering 用于改变正在进行的 Run。消息入队后，要等当前 Assistant Turn 及其整批工具执行完成，再在下一次模型调用前注入。当前 Commit 不会因为 steering 跳过本批剩余 Tool Call；它改变的是后续推理，而不是撤销已经形成的当前工具计划。&lt;/p&gt;
&lt;p&gt;follow-up 则等 Agent 原本将要停止时才消费。例如当前问题完全回答后，再处理“顺便看看这些依赖是否过期”。它不会打断现有任务的收束。&lt;/p&gt;
&lt;p&gt;两种队列都支持 &lt;code&gt;all&lt;/code&gt; 与 &lt;code&gt;one-at-a-time&lt;/code&gt;。前者在消费点一次注入全部消息，后者每次只取最早一条。这不仅影响 UI 顺序，也影响模型看到的是一组同时约束，还是逐条形成新的 Turn。&lt;/p&gt;
&lt;p&gt;假设工具执行期间用户输入“也检查 scripts”：作为 steering，它会在本次 &lt;code&gt;read&lt;/code&gt; 结束后进入下一轮 Context，模型可据此继续读取；如果作为 follow-up，则要等原有依赖分析完成后才开始。相同文本因为消费时机不同，具有不同控制语义。&lt;/p&gt;
&lt;p&gt;到这里，Runtime 主链路已经闭环：用户消息触发 Generation，Tool Call 经过真实执行成为 Tool Result，结果推动下一次 Generation，事件流对外公开过程，队列允许用户在合适边界改变后续运行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 7 章：Coding Agent 如何组装一次运行&lt;/h2&gt;
&lt;h3&gt;7.1 不同入口如何汇合到 &lt;code&gt;AgentSession&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Interactive、Print/JSON、RPC 和 SDK 的输入输出方式不同，但都复用 &lt;code&gt;AgentSession&lt;/code&gt; 与底层 Agent。Interactive 使用 TUI 收集输入并呈现组件；Print/JSON 适合一次性命令和事件管道；RPC 通过 stdin/stdout 的 JSONL 让非 Node 程序控制会话；SDK 直接暴露 &lt;code&gt;createAgentSession()&lt;/code&gt; 和对象 API。&lt;/p&gt;
&lt;p&gt;这四种模式不是四套 Agent Loop。它们共享 Model Runtime、工具、Session 和事件，只在谁提供输入、谁消费事件、怎样返回结果上分叉。&lt;/p&gt;
&lt;h3&gt;7.2 请求执行前，运行环境怎样形成&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;createAgentSession()&lt;/code&gt; 是关键组装入口。它确定 cwd、Agent 配置目录、&lt;code&gt;ModelRuntime&lt;/code&gt;、&lt;code&gt;SettingsManager&lt;/code&gt;、&lt;code&gt;SessionManager&lt;/code&gt; 和 &lt;code&gt;ResourceLoader&lt;/code&gt;。若继续已有 Session，会从当前分支恢复模型、Thinking Level 和 Agent Messages；否则按显式参数、设置与 Provider 默认值选择初始模型。&lt;/p&gt;
&lt;p&gt;随后它创建 &lt;code&gt;Agent&lt;/code&gt;，注入 Coding Agent 的 &lt;code&gt;convertToLlm()&lt;/code&gt;、模型流式函数、动态认证、Provider 请求 Hook、Context 扩展 Hook、队列模式和传输设置。默认激活 &lt;code&gt;read&lt;/code&gt;、&lt;code&gt;bash&lt;/code&gt;、&lt;code&gt;edit&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt; 四个工具，也允许 SDK 调用方选择、排除或加入自定义工具。&lt;/p&gt;
&lt;p&gt;真正的 System Prompt 在 &lt;code&gt;AgentSession&lt;/code&gt; 初始化资源后构造。默认内容保持简短：身份、当前可用工具、少量行为规则、Pi 文档位置和工作目录。&lt;code&gt;AGENTS.md&lt;/code&gt;、&lt;code&gt;CLAUDE.md&lt;/code&gt; 等项目上下文会作为 &lt;code&gt;&amp;#x3C;project_context&gt;&lt;/code&gt; 附加；&lt;code&gt;.pi/SYSTEM.md&lt;/code&gt; 可替换默认 Prompt，&lt;code&gt;.pi/APPEND_SYSTEM.md&lt;/code&gt; 用于追加。Skills 只把名称、描述和文件位置加入 System Prompt，完整 &lt;code&gt;SKILL.md&lt;/code&gt; 等到匹配任务时再通过 &lt;code&gt;read&lt;/code&gt; 加载。&lt;/p&gt;
&lt;p&gt;因此，模型启动前看到的不是“所有项目资料”，而是一组分层供给：稳定规则放 System Prompt，项目约束放上下文文件，能力目录只放 Skill 元数据，大体积内容由工具按需读取。&lt;/p&gt;
&lt;h3&gt;7.3 Prompt Template、Skill 与 Extension 的进入位置&lt;/h3&gt;
&lt;p&gt;Prompt Template 在 &lt;code&gt;AgentSession.prompt()&lt;/code&gt; 前展开，把 &lt;code&gt;/review&lt;/code&gt; 一类短命令转为完整用户 Prompt；它不改变 Runtime，也不执行代码。&lt;/p&gt;
&lt;p&gt;Skill 是供模型按需读取的能力说明，可同时携带脚本、参考资料和资产。Pi 启动时扫描并验证元数据，模型真正需要时才读取全文。这是渐进式披露，而不是把每个 Skill 永久塞进 Context。&lt;/p&gt;
&lt;p&gt;Extension 是可信 TypeScript 代码，可以注册工具、命令、事件处理器、Provider 和 UI，并在 &lt;code&gt;before_agent_start&lt;/code&gt;、context、tool call、Provider request 等位置改变运行行为。它的能力远高于 Skill，也拥有与 Pi 进程相同的系统权限。&lt;/p&gt;
&lt;h3&gt;7.4 &lt;code&gt;AgentSession&lt;/code&gt; 为什么是上层枢纽&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AgentSession&lt;/code&gt; 不只是聊天记录类。它连接 Agent、SessionManager、SettingsManager、ResourceLoader、ExtensionRunner 与 ModelRuntime，并处理 Prompt 展开、Skill 调用、消息入队、事件转发、持久化、自动重试、自动 Compaction 和 Session 切换。&lt;/p&gt;
&lt;p&gt;Agent Core 只保证通用运行语义，AgentSession 才知道一条消息何时写入 JSONL、Context Overflow 后是否压缩并重试、Extension 事件怎样影响 System Prompt，以及工具定义怎样包装 Coding Agent 的 cwd 和 UI 上下文。&lt;/p&gt;
&lt;h3&gt;7.5 Project Trust 不等于 Sandbox&lt;/h3&gt;
&lt;p&gt;Project Trust 控制的是是否加载项目本地设置、&lt;code&gt;.pi&lt;/code&gt; 资源、Packages 和 Extensions。拒绝信任会跳过这些可能在启动时改变 Pi 行为的资源；&lt;code&gt;AGENTS.md&lt;/code&gt; 和 &lt;code&gt;CLAUDE.md&lt;/code&gt; 仍按当前规则加载，除非关闭上下文文件加载。&lt;/p&gt;
&lt;p&gt;它不限制模型启动后可以请求工具做什么。Pi 没有内置 Sandbox，内置工具和 Extension 都继承 Pi 进程的文件、网络、命令与凭证权限。真正的隔离必须来自容器、虚拟机、微虚拟机或操作系统策略，并只挂载任务需要的目录和凭证。&lt;/p&gt;
&lt;p&gt;把 Trust 当成“资源加载准入”，把 Sandbox 当成“运行权限边界”，才能避免产生虚假的安全感。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 8 章：一次请求如何写入 Session Tree&lt;/h2&gt;
&lt;h3&gt;8.1 从事件到 append-only JSONL&lt;/h3&gt;
&lt;p&gt;Session 文件第一行是 Header，记录版本、Session ID、创建时间和 cwd。后续每行是一个 Entry。示例请求执行时，用户消息、第一次 AssistantMessage、&lt;code&gt;read&lt;/code&gt; Tool Result 和最终 AssistantMessage 都以 &lt;code&gt;message&lt;/code&gt; Entry 追加；模型与 Thinking Level 的变化使用独立 Entry 保存。&lt;/p&gt;
&lt;p&gt;正常运行中，SessionManager 只追加，不原地修改或删除旧 Entry；旧格式迁移等维护操作可以重写文件。每个 Entry 有短 ID 和 &lt;code&gt;parentId&lt;/code&gt;，当前 &lt;code&gt;leafId&lt;/code&gt; 表示活跃位置。新 Entry 总是成为当前 Leaf 的孩子，然后自身成为新 Leaf。&lt;/p&gt;
&lt;p&gt;JSONL 的好处不是“比数据库简单”这么笼统。逐行追加使写入和调试直接，单个文件可流式读取；&lt;code&gt;id/parentId&lt;/code&gt; 又让同一文件保留多个分支，不必为了改写早期消息复制整段会话。代价是树遍历、迁移、损坏行处理和并发写入需要额外管理。&lt;/p&gt;
&lt;h3&gt;8.2 保存的是树，模型看到的是路径&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    U1[&quot;U1: 原始请求&quot;] --&gt; A1[&quot;A1: 初次回答&quot;]
    A1 --&gt; U2[&quot;U2: 方案 A&quot;]
    A1 --&gt; U3[&quot;U3: 方案 B&quot;]
    U3 --&gt; L[&quot;当前 Leaf&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/tree&lt;/code&gt; 选择早期 Entry 时，SessionManager 只移动 Leaf。下一次追加会从该节点产生新孩子，旧路径仍在文件中。当前模型 Context 则通过从 Leaf 沿 &lt;code&gt;parentId&lt;/code&gt; 回溯到根节点，只投影一条活跃路径。&lt;/p&gt;
&lt;p&gt;这解释了为什么 Session History 不等于 Model Context：History 包含所有分支、标签、设置变化和 Extension 状态；模型通常只看当前路径中经过 Compaction 处理、再转换成 Message 的部分。&lt;/p&gt;
&lt;h3&gt;8.3 为什么模型变化也是 Entry&lt;/h3&gt;
&lt;p&gt;如果模型选择只保存在进程内存，恢复 Session 时无法知道上次使用哪个 Provider 和模型。Pi 将 &lt;code&gt;model_change&lt;/code&gt;、&lt;code&gt;thinking_level_change&lt;/code&gt; 作为树节点保存，&lt;code&gt;buildSessionContext()&lt;/code&gt; 沿当前路径扫描它们，并以最后一个有效变化恢复设置。AssistantMessage 本身也能提供最近模型信息。&lt;/p&gt;
&lt;p&gt;设置变化属于会话演进的一部分，而且不同分支可以拥有不同模型状态。把它们放入树，比在 Header 中维护一个可变“当前模型”更符合 append-only 设计。&lt;/p&gt;
&lt;h3&gt;8.4 Session 恢复究竟恢复什么&lt;/h3&gt;
&lt;p&gt;恢复时，SessionManager 解析 JSONL、迁移旧版本、重建 Entry 索引与 Leaf，再调用 &lt;code&gt;buildSessionContext()&lt;/code&gt; 得到当前分支的 Agent Messages、Thinking Level 和 Model 标识。&lt;code&gt;createAgentSession()&lt;/code&gt; 用当前 ModelRuntime 重新解析模型与认证，并重新创建工具、Extension、事件监听器和 System Prompt。&lt;/p&gt;
&lt;p&gt;也就是说，Session 保存可恢复事实，不序列化整个运行时对象。文件句柄、AbortController、网络连接和 Extension 实例必须重新构造。这种边界避免把瞬时资源错误地当成可持久化状态。&lt;/p&gt;
&lt;p&gt;本章主要对应 &lt;code&gt;packages/coding-agent/src/core/session-manager.ts&lt;/code&gt;、&lt;code&gt;packages/coding-agent/src/core/messages.ts&lt;/code&gt; 和 &lt;code&gt;packages/coding-agent/src/core/sdk.ts&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 9 章：Compaction 如何改变模型看到的历史&lt;/h2&gt;
&lt;h3&gt;9.1 为什么需要压缩&lt;/h3&gt;
&lt;p&gt;长会话不断积累文件内容、命令输出和多轮修改，最终会接近模型上下文窗口。Pi 在 &lt;code&gt;contextTokens &gt; contextWindow - reserveTokens&lt;/code&gt; 时触发自动 Compaction；默认 &lt;code&gt;reserveTokens&lt;/code&gt; 为 16384，为下一次输出留出空间，&lt;code&gt;keepRecentTokens&lt;/code&gt; 默认为 20000，用于保留近期原文。&lt;/p&gt;
&lt;p&gt;Context Token 不总能精确计算。Pi 优先利用最近 AssistantMessage 的 usage，并对之后新增消息做估算；缺少可靠 usage 时再估算全部内容。压缩只需要一个足以判断阈值和选择边界的近似值，追求逐 token 完全一致反而会绑定某个 tokenizer 和 Provider。&lt;/p&gt;
&lt;h3&gt;9.2 怎样选择切割点&lt;/h3&gt;
&lt;p&gt;Pi 从最新消息向前累计，寻找大约能保留 &lt;code&gt;keepRecentTokens&lt;/code&gt; 的切割位置。合法切点包括 user、assistant、直接 Bash 和 Custom Message，不会从 Tool Result 开始，因为 Tool Result 必须与提出它的 Tool Call 保持可理解的配对。&lt;/p&gt;
&lt;p&gt;通常切点落在一个新 Turn 开始处，旧的完整 Turn 进入摘要，近期 Turn 保留原文。如果单个 Turn 本身已经超过保留预算，Pi 允许从 Turn 中间的 assistant 边界切开，并单独总结这个巨大 Turn 的前缀，再与历史摘要合并。&lt;/p&gt;
&lt;p&gt;摘要生成前，消息被序列化为带角色标签的文本；Tool Result 在摘要请求中最多保留 2000 字符，避免“为了压缩而再次塞入巨量工具输出”。默认摘要采用结构化格式，保留目标、约束、进度、关键决策、下一步和读写文件。&lt;/p&gt;
&lt;h3&gt;9.3 压缩后什么变了，什么没变&lt;/h3&gt;
&lt;p&gt;当前 Commit 的 &lt;code&gt;CompactionEntry&lt;/code&gt; 保存 &lt;code&gt;summary&lt;/code&gt;、&lt;code&gt;firstKeptEntryId&lt;/code&gt;、&lt;code&gt;tokensBefore&lt;/code&gt;、可选 details 和摘要调用 usage。它不包含某些后续设计材料中出现的 &lt;code&gt;retainedTail&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;buildContextEntries()&lt;/code&gt; 在当前分支上找到最新 Compaction 后，构造：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-latex&quot;&gt;Compaction Summary
+ 压缩前位于 firstKeptEntryId 之后的近期 Entry
+ Compaction 之后新增的 Entry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更早的被摘要 Entry 仍在原始 JSONL 树中，只是不再进入当前模型 Context。Compaction 改变的是 Session → Agent Messages 的投影，不是覆盖或删除原始历史。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    H[&quot;完整 Session History&quot;] --&gt; C[&quot;Compaction Entry&quot;]
    H --&gt; K[&quot;近期原文&quot;]
    C --&gt; MC[&quot;模型 Context&quot;]
    K --&gt; MC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多次压缩时，Pi 把上一份摘要作为 previous summary，与新增长历史合并，并重新选择新的保留边界。摘要语义会延续，但细节不可避免地损失，所以关键决定、文件状态和下一步需要用稳定结构表达。&lt;/p&gt;
&lt;h3&gt;9.4 Compaction 与 Branch Summary&lt;/h3&gt;
&lt;p&gt;| 机制 | 触发场景 | 总结对象 | 目的 |
| --- | --- | --- | --- |
| Compaction | 接近上下文上限或 &lt;code&gt;/compact&lt;/code&gt; | 当前活跃路径的旧历史 | 释放 Context 空间 |
| Branch Summary | &lt;code&gt;/tree&lt;/code&gt; 离开旧分支 | 从共同祖先到旧 Leaf 的被放弃路径 | 把另一条探索路径的重要信息带到新分支 |&lt;/p&gt;
&lt;p&gt;两者最终都会转换为模型可读的 user message，但语义不同。Compaction 说“更早的同一条历史被压缩了”，Branch Summary 说“用户曾在另一条分支探索过这些内容”。混用会让模型误判事件顺序和当前工作状态。&lt;/p&gt;
&lt;p&gt;本章主要对应 &lt;code&gt;packages/coding-agent/src/core/compaction/compaction.ts&lt;/code&gt;、&lt;code&gt;branch-summarization.ts&lt;/code&gt;、&lt;code&gt;utils.ts&lt;/code&gt; 和 &lt;code&gt;session-manager.ts&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第 10 章：Pi 为什么把更多能力留在内核之外&lt;/h2&gt;
&lt;h3&gt;10.1 四类扩展资源怎样分工&lt;/h3&gt;
&lt;p&gt;| 资源 | 改变什么 | 是否执行代码 | 典型用途 |
| --- | --- | --- | --- |
| Extension | Runtime、工具、事件、Provider、命令和 UI | 是 | 权限门、定制工具、外部服务、Plan Mode |
| Skill | 模型按需加载的工作方法与配套资源 | 可指导模型运行脚本 | PDF 流程、部署 SOP、领域能力 |
| Prompt Template | 用户输入的可复用展开 | 否 | &lt;code&gt;/review&lt;/code&gt;、&lt;code&gt;/release&lt;/code&gt; 等固定提示 |
| Pi Package | 组合、安装和分发以上资源及主题 | 取决于内容 | 团队工作流或第三方能力包 |&lt;/p&gt;
&lt;p&gt;选择标准不是“哪个功能更强”，而是能力位于哪一层。需要拦截 Tool Call 或新增 Provider，就必须使用 Extension；只是告诉模型处理某类任务的步骤，Skill 更轻；只想复用一段输入，用 Prompt Template 即可；需要整体交付时再打成 Package。&lt;/p&gt;
&lt;p&gt;Skills 的渐进式披露尤其重要。启动时只把名称、描述和路径放入 Prompt，匹配任务后再读取完整 &lt;code&gt;SKILL.md&lt;/code&gt;，其引用的参考资料和脚本继续按需加载。这样能力规模可以增长，而基础 Context 不必线性膨胀。&lt;/p&gt;
&lt;h3&gt;10.2 不同复用深度对应不同入口&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-latex&quot;&gt;只需要统一多模型调用        → pi-ai
需要通用 Agent Runtime       → pi-agent-core
需要完整 Coding Harness      → pi-coding-agent SDK
需要非 Node 进程远程控制     → RPC
需要现成终端交互             → Interactive CLI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同一核心支持多入口的价值在于，能力不会被 TUI 锁死。服务端可以消费 JSONL 事件，桌面应用可以通过 SDK 自己渲染 UI，自动化程序可以使用 Print/JSON，而模型与工具语义保持一致。&lt;/p&gt;
&lt;h3&gt;10.3 极简内核的收益与成本&lt;/h3&gt;
&lt;p&gt;不内置 MCP、子 Agent 和 Plan Mode，使核心无需选择唯一的连接协议、任务分解方式或审批交互。使用者可以按场景组合，也更容易理解和替换单个机制。Pi 自己的实现因此能把重点放在模型、Loop、工具、Context 和 Session 这些共同基础上。&lt;/p&gt;
&lt;p&gt;成本同样具体。团队若需要权限确认、后台任务、企业 MCP、子 Agent 编排和任务看板，就要自己选择 Extension 或 Package，并承担兼容、测试与维护。不同安装之间也可能形成截然不同的行为，不能仅凭“都在使用 Pi”推断相同能力和安全策略。&lt;/p&gt;
&lt;h3&gt;10.4 扩展性不会自动带来安全&lt;/h3&gt;
&lt;p&gt;Extension 是与 Pi 同权限运行的 TypeScript，第三方 Package 也可能包含可执行代码；Skill 虽不是 Runtime 插件，却能指导模型运行脚本和外部命令。扩展越强，信任面越大。&lt;/p&gt;
&lt;p&gt;Pi 的安全边界很诚实：Project Trust 只防止未确认项目在启动时加载本地资源，不能约束模型和工具之后的行为；内核没有 Sandbox；Prompt Injection 也不会因为存在 Trust 开关而消失。高风险或无人值守任务应在操作系统级隔离环境中运行，并最小化文件挂载、网络与凭证。&lt;/p&gt;
&lt;p&gt;Pi 负责提供 Harness，不负责替上层应用完成业务权限、操作审批、数据质量和验收标准。理解“没有构建什么”，和理解已经构建的代码同样重要。&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;第三部分：我对 Pi 的理解&lt;/h1&gt;
&lt;h2&gt;1. Pi 最值得学习的不是某个 API，而是边界&lt;/h2&gt;
&lt;p&gt;Pi 最有价值的设计不是“支持很多模型”或“能调用工具”，而是持续区分容易混淆的对象：Provider 与 Agent、Tool Call 与工具执行、Agent State 与 Model Context、Session History 与当前分支、可扩展性与安全隔离。&lt;/p&gt;
&lt;p&gt;这些边界让系统中的事实归属更清晰。模型只负责产生候选内容和工具请求，Runtime 负责执行语义，Coding Harness 负责场景组装，Session 负责历史事实，UI 负责呈现。每一层都可以影响最终行为，但不会假装自己拥有全部责任。&lt;/p&gt;
&lt;h2&gt;2. 三个尤其值得迁移的设计&lt;/h2&gt;
&lt;p&gt;第一，AgentMessage 到 Model Message 的晚转换。应用可以保留完整运行语义，在每次调用边界才决定模型看什么。这比一开始就把所有状态压成厂商消息更适合长期演进。&lt;/p&gt;
&lt;p&gt;第二，Tool Result 必须重新进入模型。工具不是旁路脚本，也不是 UI 展示；它是一次外部世界反馈。成功与失败都结构化回填，Agent 才能基于证据继续。&lt;/p&gt;
&lt;p&gt;第三，append-only Session Tree 把“历史事实”和“当前视图”分开。分支、压缩和恢复都不要求改写过去，而是通过 Leaf 和投影选择当前路径。这一思想不仅适用于 Coding Agent，也适用于需要审计和人工回退的 AI 工作流。&lt;/p&gt;
&lt;h2&gt;3. 这些设计付出的真实代价&lt;/h2&gt;
&lt;p&gt;分层带来类型与转换链路的理解成本；事件生命周期使时序调试更复杂；跨 Provider 转换可能丢失签名和推理语义；Compaction 与 Branch Summary 会损失细节；Extension 提供强能力的同时扩大信任面；极简内核把权限、审批、后台任务和企业集成等责任交给使用者。&lt;/p&gt;
&lt;p&gt;因此不能只用“解耦”“扩展性强”评价 Pi。更准确的说法是：它把耦合从内核移到显式装配与转换边界，换来了可替换性，也要求使用者真正理解这些边界。&lt;/p&gt;
&lt;h2&gt;4. 仍值得继续观察的问题&lt;/h2&gt;
&lt;p&gt;Tool Abort 与外部副作用的部分完成语义，仍需要工具作者和上层应用共同约定。监听器被顺序等待保证一致性，但慢监听器或异常监听器对主流程的影响也值得持续评估。跨 Provider 转换能避免协议错误，却很难自动判断语义损失有多大。Compaction 摘要可延长会话，但如何量化信息漂移、让 Session → Context 投影更透明，仍是 Agent 工程的重要问题。&lt;/p&gt;
&lt;p&gt;这些问题不说明 Pi 设计错误，恰恰说明 Agent Harness 的复杂性不在一个循环，而在模型、状态、外部动作和长期历史的交界处。&lt;/p&gt;
&lt;h2&gt;5. Pi 适合做什么，不负责什么&lt;/h2&gt;
&lt;p&gt;Pi 适合用作本地 Coding Agent、可嵌入的编码 Harness、通用 Agent Runtime，以及统一多模型调用层。它提供可观察、可恢复、可扩展的工程基础。&lt;/p&gt;
&lt;p&gt;它不会自动替上层应用完成业务权限、数据可信度、成功标准、人工审核、事务一致性或完整 Sandbox。使用 Pi 构建生产系统时，这些能力仍必须由具体业务和基础设施补齐。&lt;/p&gt;
&lt;h2&gt;6. 学完 Pi 后，我真正理解了什么&lt;/h2&gt;
&lt;p&gt;Agent 不只是一个循环，而是模型生成、工具反馈、上下文投影、事件和持久化共同组成的 Runtime。Tool Call 不等于动作已经发生；Agent State 不等于模型此刻看到的 Context；Session History 不等于当前记忆；扩展能力不必全部塞进核心；没有默认构建什么，同样是一项需要承担收益与代价的架构选择。&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;复习附录&lt;/h1&gt;
&lt;h2&gt;附录 A：完整请求链路&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant U as User
    participant S as AgentSession
    participant A as Agent Core
    participant P as pi-ai / Provider
    participant R as read Tool
    participant J as Session JSONL
    U-&gt;&gt;S: 读取 package.json 并分析依赖
    S-&gt;&gt;A: prompt(UserMessage)
    S-&gt;&gt;J: append user message
    A-&gt;&gt;P: Context + tools
    P--&gt;&gt;A: AssistantMessage(read Tool Call)
    S-&gt;&gt;J: append assistant message
    A-&gt;&gt;R: validate + execute
    R--&gt;&gt;A: ToolResultMessage
    S-&gt;&gt;J: append tool result
    A-&gt;&gt;P: updated Context
    P--&gt;&gt;A: final AssistantMessage
    S-&gt;&gt;J: append final answer
    A--&gt;&gt;S: agent_end
    S--&gt;&gt;U: 最终回答
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;附录 B：概念—类型—函数—源码文件对照&lt;/h2&gt;
&lt;p&gt;| 概念 | 关键类型或函数 | 主要源码 |
| --- | --- | --- |
| 模型描述 | &lt;code&gt;Model&lt;/code&gt;、&lt;code&gt;Context&lt;/code&gt;、&lt;code&gt;Message&lt;/code&gt; | &lt;code&gt;packages/ai/src/types.ts&lt;/code&gt; |
| Provider 集合 | &lt;code&gt;Provider&lt;/code&gt;、&lt;code&gt;Models&lt;/code&gt;、&lt;code&gt;createProvider()&lt;/code&gt; | &lt;code&gt;packages/ai/src/models.ts&lt;/code&gt; |
| 模型事件流 | &lt;code&gt;AssistantMessageEventStream&lt;/code&gt; | &lt;code&gt;packages/ai/src/utils/event-stream.ts&lt;/code&gt; |
| 跨模型历史转换 | &lt;code&gt;transformMessages()&lt;/code&gt; | &lt;code&gt;packages/ai/src/api/transform-messages.ts&lt;/code&gt; |
| Agent 状态 | &lt;code&gt;AgentState&lt;/code&gt;、&lt;code&gt;AgentMessage&lt;/code&gt; | &lt;code&gt;packages/agent/src/types.ts&lt;/code&gt; |
| Stateful Runtime | &lt;code&gt;Agent&lt;/code&gt; | &lt;code&gt;packages/agent/src/agent.ts&lt;/code&gt; |
| Agent Loop | &lt;code&gt;runLoop()&lt;/code&gt; | &lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt; |
| 工具执行 | &lt;code&gt;prepareToolCall()&lt;/code&gt;、&lt;code&gt;executePreparedToolCall()&lt;/code&gt; | &lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt; |
| Coding 消息转换 | &lt;code&gt;convertToLlm()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/messages.ts&lt;/code&gt; |
| 运行环境组装 | &lt;code&gt;createAgentSession()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/sdk.ts&lt;/code&gt; |
| 会话枢纽 | &lt;code&gt;AgentSession&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/agent-session.ts&lt;/code&gt; |
| System Prompt | &lt;code&gt;buildSystemPrompt()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/system-prompt.ts&lt;/code&gt; |
| 资源加载 | &lt;code&gt;DefaultResourceLoader&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/resource-loader.ts&lt;/code&gt; |
| Session Tree | &lt;code&gt;SessionManager&lt;/code&gt;、&lt;code&gt;buildSessionContext()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/session-manager.ts&lt;/code&gt; |
| Compaction | &lt;code&gt;prepareCompaction()&lt;/code&gt;、&lt;code&gt;compact()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/compaction/compaction.ts&lt;/code&gt; |
| Branch Summary | &lt;code&gt;generateBranchSummary()&lt;/code&gt; | &lt;code&gt;packages/coding-agent/src/core/compaction/branch-summarization.ts&lt;/code&gt; |&lt;/p&gt;
&lt;h2&gt;附录 C：事件生命周期速查&lt;/h2&gt;
&lt;p&gt;| 层级 | 开始 | 增量 | 结束 | 表达的事实 |
| --- | --- | --- | --- | --- |
| Agent Run | &lt;code&gt;agent_start&lt;/code&gt; | — | &lt;code&gt;agent_end&lt;/code&gt; | 一次 prompt / continue 的完整运行 |
| Turn | &lt;code&gt;turn_start&lt;/code&gt; | — | &lt;code&gt;turn_end&lt;/code&gt; | 一个 AssistantMessage 及其工具批次 |
| Message | &lt;code&gt;message_start&lt;/code&gt; | &lt;code&gt;message_update&lt;/code&gt; | &lt;code&gt;message_end&lt;/code&gt; | 用户、Assistant 或 Tool Result 消息 |
| Tool | &lt;code&gt;tool_execution_start&lt;/code&gt; | &lt;code&gt;tool_execution_update&lt;/code&gt; | &lt;code&gt;tool_execution_end&lt;/code&gt; | 一次真实工具执行 |&lt;/p&gt;
&lt;p&gt;&lt;code&gt;message_update&lt;/code&gt; 只用于流式 AssistantMessage。&lt;code&gt;agent_end&lt;/code&gt; 是最后一个 Loop 事件，但 Agent 要等对应监听器完成后才真正 idle。&lt;/p&gt;
&lt;h2&gt;附录 D：15 道面试速答卡&lt;/h2&gt;
&lt;p&gt;| 题目 | 一句话结论 | 三个关键词 | 主要代价 |
| --- | --- | --- | --- |
| Q1 分层 | AI、Runtime、Harness 按复用深度分离 | Provider、Loop、Session | 类型转换增加 |
| Q2 依赖 | 底层不知道上层场景 | 单向依赖、复用、适配 | 装配更显式 |
| Q3 多模型 | Provider 负责运行，API 负责协议翻译 | Model、Auth、Dispatch | 差异仍存在 |
| Q4 流式 | 模型输出是事件过程和最终消息 | Delta、stopReason、usage | 消费逻辑复杂 |
| Q5 切模型 | 历史需要按目标模型有损转换 | Thinking、Tool ID、Image | 私有语义丢失 |
| Q6 Loop | Tool Result 和队列驱动后续 Turn | Generation、Turn、Run | 停止条件较多 |
| Q7 消息 | Agent State 通过两阶段投影成为 Context | transform、convert、filter | 可见性排查更难 |
| Q8 工具 | Tool Call 要经过准入、执行和回填 | Schema、Hook、toolCallId | 工具契约更重 |
| Q9 调度 | 完成事件可乱序，结果上下文保持原序 | parallel、Abort、order | 部分完成需定义 |
| Q10 交互 | 事件管观察，双队列管时机 | lifecycle、steering、follow-up | 时序更复杂 |
| Q11 组装 | AgentSession 连接 Runtime 与 Coding 资源 | SDK、ResourceLoader、Extension | 枢纽职责较多 |
| Q12 Context | 稳定规则常驻，大内容按需，旧历史压缩 | Prompt、Skill、Compaction | 来源分散 |
| Q13 Session | append-only 树保存所有路径，Leaf 选当前路径 | JSONL、parentId、branch | 需树遍历 |
| Q14 压缩 | 摘要替换旧 Context 投影，不删除历史 | threshold、cut point、summary | 信息会损失 |
| Q15 极简 | 内核给 primitives，工作流由扩展组合 | Extension、Skill、Trust | 责任转给使用者 |&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;源码阅读建议&lt;/h1&gt;
&lt;p&gt;如果要亲自复现本文，不必从 3000 多行的 &lt;code&gt;AgentSession&lt;/code&gt; 开始。先读 &lt;code&gt;packages/ai/src/types.ts&lt;/code&gt; 中的消息和流式事件，再完整阅读 &lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt;，随后看 &lt;code&gt;packages/coding-agent/src/core/messages.ts&lt;/code&gt; 与 &lt;code&gt;sdk.ts&lt;/code&gt; 的组装。理解主链路后，再进入 &lt;code&gt;session-manager.ts&lt;/code&gt;、&lt;code&gt;compaction.ts&lt;/code&gt; 和 Extensions。&lt;/p&gt;
&lt;p&gt;阅读时始终用同一请求做断点：当前对象属于哪一层，何时从一种消息变成另一种消息，外部动作是否已经发生，结果何时重新进入模型，当前保存的是完整历史还是调用视图。能稳定回答这五件事，就不再只是“看过 Pi 源码”，而是已经建立了 Agent Runtime 的实现层认识。&lt;/p&gt;</content:encoded></item><item><title>从 Hacker 到 Founder：在选择里对整件事负责</title><link>https://ygrowly.github.io/blog/20260729---hacker-to-founder/post</link><guid isPermaLink="true">https://ygrowly.github.io/blog/20260729---hacker-to-founder/post</guid><description>AI 小酒馆「从 Hacker 到 Founder」的一场夜聊：初心决定往哪走，判断决定怎么下注，责任决定选择以后怎么扛。</description><pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;《凌晨两点以后，我开始理解 Hackathon》写的是一次比赛如何改变了我看待协作、交付和准备的方式。这篇想写的不是重复复盘，而是比赛结束后，那场 AI 小酒馆的夜聊留给我的另一层问题：如果不只想把分配到的功能做好，而开始在意一件事为什么要做、该怎么选、出了问题谁来扛，那么人要慢慢长出什么能力？&lt;/p&gt;
&lt;p&gt;那晚听到的并不只是创业、融资或 AI 的趋势。最后留在我脑中的，是一句很朴素的话：初心决定往哪里走，判断决定怎么下注，责任决定选择以后怎样把事情扛下来。&lt;/p&gt;
&lt;p&gt;我想，这或许就是从 Hacker 到 Founder 更真实的变化。它不一定意味着立刻创业，也不是某天忽然换了一个身份；而是在一次次具体的选择里，开始对整件事负责。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ygrowly.github.io/_astro/01-night-talk.CPIzGBB8_184FPH.webp&quot; alt=&quot;夜聊&quot;&gt;&lt;/p&gt;
&lt;h2&gt;初心：不急着离开新手村，但不能把新手村当作世界&lt;/h2&gt;
&lt;p&gt;赵维奇老师讲过早期创业的一段经历：在国外时，团队只能点最便宜的麦当劳套餐，却还是尽量把大部分钱投向研发。这个故事启发我的地方，不是&quot;吃苦&quot;本身。而是在资源最紧张的时候，人更要看清自己究竟愿意为哪件事牺牲、为哪件事坚持。那是一种很具体的长期取舍。&lt;/p&gt;
&lt;p&gt;初心也许不是&quot;我这一生只能做某个职业&quot;，而是当短期回报、外界评价与心里的愿望发生冲突时，仍知道哪些东西不想轻易背叛。耐心不是等着成功出现，而是不因为一次失败、一条弯路就否定长期积累。信心也不是确信自己一定会赢，而是即使判断错了，仍相信自己能修正、继续走下去。&lt;/p&gt;
&lt;p&gt;我很喜欢&quot;新手村&quot;这个比喻。学校、学历、公司和平台都像新手村：它们给我们资源、规则、初始信任和同行的人，但不能代替我们走完后面的路。小马过河时，松鼠说水深、老牛说水浅，可能都是真的；只是每个人的能力、目标和风险承受度不同,最后还是要自己试探。&lt;/p&gt;
&lt;p&gt;还有袁老板分享的&quot;米缸里的老鼠&quot;也提醒我：米缸里有足够的粮食，甚至会让人过得很舒服；危险在于，待得越久，越容易把缸壁当作世界的边界，慢慢失去出去的能力、好奇心和心气。&lt;/p&gt;
&lt;p&gt;我现在要做的不是急着爬出去，而是在离开之前，逐渐获得独立过河的能力。&lt;/p&gt;
&lt;h2&gt;判断：领先十年，不等于已经走完十年&lt;/h2&gt;
&lt;p&gt;John Li 老师分享自己的经历时很有锋芒。直博三年后，他发现自己想做的事与当时的学术环境并不相容，于是离开学校创业。十年前，他已经在答辩里讲过许多 AI 的设想；后来做的 &lt;code&gt;mathematician&lt;/code&gt;，也是一个自动解数学题、证明数学定理的框架。以今天的词来理解，它缺少的是能把框架真正串起来、持续行动的 Agent 能力。&lt;/p&gt;
&lt;p&gt;他相信自己当时看到的方向领先了很久。今天回看，其中的一部分技术判断确实很早；但创业本身并没有因为&quot;方向是对的&quot;而自动成功。产品、时机、融资、叙事、团队和经验，没有一个会因为远见而自然补齐。&lt;/p&gt;
&lt;p&gt;这让我记住一句比&quot;我看对了&quot;更重要的话：看见未来是一种能力，让未来在当下成立，是另一种能力。&lt;/p&gt;
&lt;p&gt;我们做重大决定时，常常只问&quot;这件事值不值得做&quot;。但真正的判断还要多问几层：它依赖哪些条件？这些条件现在是否存在？我手里的资源能补上什么、缺什么？它适合直接下注，还是先做一个低成本实验？即使方向没有错，我是否能承受它暂时不被理解、也暂时没有回报的阶段？&lt;/p&gt;
&lt;p&gt;&quot;正确的直觉，是尚未被解释的推理。&quot;我越来越认同这句话。直觉不是玄学，它往往来自过去的经验、先验信息和眼前线索的快速碰撞。熟悉领域里，它可以先给方向；但在陌生、代价高、不可逆的选择上，直觉只能作为假设的起点，之后仍要调查、建模和验证。&lt;/p&gt;
&lt;p&gt;那晚还有朋友问：如果 AI 能大规模参与内容生成和修订，知识的权威性如何保证？真理程度能不能量化？我觉得 AI 不该变成唯一的权威，更不能替人跳过判断。比起给&quot;真理&quot;粗暴打一个百分比，更有意义的是让它把一条具体主张的来源、版本、证据、冲突与时效呈现出来。&lt;/p&gt;
&lt;p&gt;来源与治理机制是否可信，是一种 root trust；在特定领域，哪些人和机构更值得参考、边界在哪里，是另一层 domain trust；而某一句具体的话，又有多少证据支持、是否已经过时，则是另一层置信度。AI 可以帮助整理这些线索，但最后如何理解、采纳和行动，仍然是人的责任。&lt;/p&gt;
&lt;p&gt;这也是我想继续学数理信息、读历史和看科幻的原因。它们不只是工具或谈资，而是在训练一种判断力：从现象里归纳规律，再从规律推演后果；既看见一项技术能做什么，也追问它进入社会后会改变什么。科幻尤其提醒我，技术最值得警惕的往往不是第一层便利，而是它带来的第二层、第三层后果。&lt;/p&gt;
&lt;h2&gt;责任：不靠英雄救火，而是让事情不必发展成事故&lt;/h2&gt;
&lt;p&gt;袁老板讲的支付事故，把&quot;责任&quot;从一个很抽象的词拉回了现实。用户信用卡被重复扣款，问题并不会随着一句&quot;系统出了 Bug&quot;结束。它同时涉及用户、资金、合作方、团队信任与公司的信誉。事前有没有做幂等、对账、监控和告警；事中能不能快速发现、止损、解释、协调；事后能不能补偿、复盘，并把教训沉淀为机制——这些才是一件事真正的后半程。&lt;/p&gt;
&lt;p&gt;John Li 也分享《孙子兵法》里说：&quot;故善战者之胜也，无智名，无勇功。&quot;最好的系统，不靠每次出事后出现一个力挽狂澜的人。很多真正重要的工作，在平时几乎看不见：一个边界条件、一条监控、一份对账、一场演练。但正是它们，让事故没有扩大，甚至根本没有发生。&lt;/p&gt;
&lt;p&gt;这也重新解释了我在 Hackathon 现场看到的一些产品与技术冲突。产品不断加需求，技术担心重构代价，表面是沟通问题，深层却是团队没有共同定义问题：这次 Demo 到底要证明什么？哪些必须做，哪些明确不做？有新想法进来时，要替换掉什么？谁来做最终取舍，并承担结果？&lt;/p&gt;
&lt;p&gt;产品不是不断增加点子，技术也不只是守住已有架构。两者共同的任务，是把复杂问题压缩成一个所有人都能理解、能取舍、能执行的模型。&lt;/p&gt;
&lt;p&gt;所以 Builder 与 Founder 的差别，也许不在于谁更会写代码、谁更会讲故事。Builder 为实现负责；Founder 还要为方向、取舍、协作关系和最终后果负责。我当然还不是 Founder，也不需要急着扮演这个角色。但我可以从每一次项目开始，练习不只盯着自己手里的那一小块，而是试着看清整件事能否成立。&lt;/p&gt;
&lt;h2&gt;那晚之后，我准备做几件小事&lt;/h2&gt;
&lt;p&gt;这些想法不该只停在文章里。我想先给自己加几个轻量、但能长期运行的动作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遇到重要选择，写一页决策日志：我真正想要什么、依据是什么、有哪些反证、最坏结果能否承受、下一步能做什么最小实验。让直觉先提出方向，再由证据检查它。&lt;/li&gt;
&lt;li&gt;下一次项目或 Hackathon，先写清 Demo Story，再排功能优先级；有争议的技术方案，用 30 到 120 分钟的 Spike 验证，而不是靠想象反复争论。&lt;/li&gt;
&lt;li&gt;每月认真看一部科幻作品或读一本相关的书，不为打卡，只留下几个问题：它假设了什么技术？人怎样使用它？制度和权力如何反应？现实里已经有哪些苗头？&lt;/li&gt;
&lt;li&gt;继续把能力沉淀成能复用的原子：开发底座、技术 Spike、项目表达、可靠交付、复盘模板，以及愿意持续交流的人。以后做新事时，不必总从零开始。&lt;/li&gt;
&lt;li&gt;用项目、测试、开源贡献、文章和一次次可靠交付建立自己的领域信任，而不只依赖学历或一句&quot;我很懂&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我不急着离开新手村，也不急着把自己包装成 Founder。但我不想只做一个把分配任务完成的人。&lt;/p&gt;
&lt;p&gt;保留初心，训练判断，认真承担。把每一次项目、一次失误、一次相遇，都变成下一次更稳的选择。也许，&quot;从 Hacker 到 Founder&quot;不是某一天突然完成的身份转换，而是在每一次选择里，开始愿意对整件事负责。&lt;/p&gt;</content:encoded></item><item><title>凌晨两点以后，我开始理解 Hackathon</title><link>https://ygrowly.github.io/blog/20260729---hackathon-builder-reflection/post</link><guid isPermaLink="true">https://ygrowly.github.io/blog/20260729---hackathon-builder-reflection/post</guid><description>一次 AdventureX 黑客松复盘：从选题、组队和开发，到 Demo、路演与产品判断，我重新理解了 Builder 的责任。</description><pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;7 月 25 日凌晨两点，我们还在准备 Demo、部署和报名材料。&lt;/p&gt;
&lt;p&gt;产品已经能够运行，但完成度不算高。报名信息还没有全部整理好，也没有录制演示视频，更没有准备断网后的备用方案。&lt;/p&gt;
&lt;p&gt;当时没有特别强烈的情绪，只是觉得：这次获奖的概率可能不大了。&lt;/p&gt;
&lt;p&gt;第二天，场馆网络不太稳定，原本准备好的在线功能无法正常演示。我们只能拿出手机，向观众展示之前跑通的页面和结果。&lt;/p&gt;
&lt;p&gt;也是到了这时，我才意识到，Hackathon 并不是在几天内把代码写出来就结束了。&lt;/p&gt;
&lt;p&gt;选题、组队、开发、提交和路演，其实是同一个过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ygrowly.github.io/_astro/01-adventurex-site.C8suY7qy_Z24nFDU.webp&quot; alt=&quot;AdventureX 黑客松现场&quot;&gt;&lt;/p&gt;
&lt;h2&gt;我原本把自己放在 Builder 的位置&lt;/h2&gt;
&lt;p&gt;参加 AdventureX 之前，我主要想体验一次大型 Hackathon，看看大家怎样使用 AI，也认识一些新朋友。&lt;/p&gt;
&lt;p&gt;我最担心的是组队。我的性格偏内向，不太擅长在现场快速介绍自己，也没有提前准备成熟的 Idea。对自己的定位，我想得比较简单：等团队确定产品方向，我负责把 AI 模块做出来。&lt;/p&gt;
&lt;p&gt;当时我觉得，有 AI Coding 的帮助，几天内完成一个 MVP 应该不算太难。&lt;/p&gt;
&lt;p&gt;真正开始后才发现，即使写代码变快了，工程框架、环境依赖、跨平台适配和部署仍然会消耗很多时间。更重要的是，AI Coding 可以帮助我们实现一个想法，却不能替我们判断应该做什么。&lt;/p&gt;
&lt;h2&gt;在时间压力下确定选题&lt;/h2&gt;
&lt;p&gt;组队后，我们讨论过几个方向。&lt;/p&gt;
&lt;p&gt;创业大哥提出过智能耳机：在用户就医、约会或者商务沟通时，通过耳机提供实时提醒。这个方向很有想象力，但大家担心语音采集、模型处理和语音回复的整条链路延迟太高，最后没有继续。&lt;/p&gt;
&lt;p&gt;现在回头看，放弃它未必是错的。问题在于，我们没有先花三十分钟做一个最小实验，而是主要根据感觉判断可行性。&lt;/p&gt;
&lt;p&gt;后来，产品成员提出了闲置物品管理。我们在此基础上加入心愿单、克制消费和 AI 购物建议，逐渐形成了“星币六”。&lt;/p&gt;
&lt;p&gt;它希望帮助用户了解自己已经拥有什么，在购买新物品前重新判断需求，也可以通过出售闲置物品推进自己的心愿。&lt;/p&gt;
&lt;p&gt;这个方向有真实需求，开发范围也相对可控。但确定得比较仓促。我们没有系统比较需求强度、创新性、可演示性、团队匹配和赛道契合，更多是几个人觉得可以做，于是就开始做了。&lt;/p&gt;
&lt;p&gt;我后来还提出加入 Web3，让闲置物品进一步流转和交易。但当时没有时间回答一个关键问题：这件事为什么一定需要上链？最后它只停留在设想里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ygrowly.github.io/_astro/02-topic-discussion.DYzZwXbM_zsOYh.webp&quot; alt=&quot;团队讨论选题方向&quot;&gt;&lt;/p&gt;
&lt;h2&gt;能力互补，不等于方向匹配&lt;/h2&gt;
&lt;p&gt;方向确定后，创业大哥觉得自己在这个项目中难以发挥，因此没有继续参与开发。&lt;/p&gt;
&lt;p&gt;我们之间没有因此产生矛盾。路演前，他还回来和我们一起讨论，并提出了从年轻人的冲动消费延伸到不同人生阶段财商管理的故事。&lt;/p&gt;
&lt;p&gt;这个故事增强了路演的吸引力，但也比当前 Demo 能支撑的范围更大。我们真正做出来的，还是闲置物品、心愿单和 AI 消费决策。&lt;/p&gt;
&lt;p&gt;这让我意识到，组队不能只看成员的能力是否互补，还要看选题能否让每个人真正参与进来。团队配置看起来完整，不代表方向一定匹配。&lt;/p&gt;
&lt;h2&gt;功能做出来了，但体验不够鲜明&lt;/h2&gt;
&lt;p&gt;开发过程中，前端队友负责 Expo App 和大部分功能，产品成员负责产品设计、视觉和报名材料，我主要负责 AI 决策模块，也参与了后期部署与演示。&lt;/p&gt;
&lt;p&gt;由于前端队友使用 iOS，而我使用 Windows，Expo 的环境和启动适配消耗了大约两个小时。平时两小时不算长，但在 Hackathon 里，已经会明显挤压后面的调试时间。&lt;/p&gt;
&lt;p&gt;我们最终完成了闲置物品录入、心愿单和 AI 消费建议。AI 会读取用户已有的物品与心愿，输出“建议结论、理由和行动方案”。&lt;/p&gt;
&lt;p&gt;所以，AI 确实进入了产品的核心链路。但从观众视角看，它仍然很容易被理解成：&lt;/p&gt;
&lt;p&gt;AI 读取了我的信息，然后告诉我应不应该买。&lt;/p&gt;
&lt;p&gt;问题不是没有 AI，而是 AI 的作用没有转化成足够直观的产品体验。&lt;/p&gt;
&lt;p&gt;更完整的链路应该是：用户录入一件闲置物品，准备购买心愿单中的新物品；AI 结合已有资产和预算，给出“暂缓购买”或者“先卖后买”的建议；闲置物品出售后，获得的资金再进入心愿进度。&lt;/p&gt;
&lt;p&gt;这样观众看到的就不只是一次对话，而是一次真实发生变化的消费决策。&lt;/p&gt;
&lt;p&gt;遗憾的是，我们没有在开发前先确定这条 Demo Story。功能逐渐增加，但如何用一个具体场景把它们串起来，是到后期才开始考虑的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ygrowly.github.io/_astro/03-app-demo.Cn6mp2Rk_1kcbzj.webp&quot; alt=&quot;星币六 App 演示画面&quot;&gt;&lt;/p&gt;
&lt;h2&gt;凌晨两点以后&lt;/h2&gt;
&lt;p&gt;最后一晚，我们仍在处理部署、演示和报名信息。材料集中由一人负责，也没有安排另一个人逐项复核赛道要求。&lt;/p&gt;
&lt;p&gt;第二天网络出现问题时，前面的准备不足被一起放大了。&lt;/p&gt;
&lt;p&gt;现场有人问，为什么不用闲鱼或者记账软件；也有人认为，如果需求成立，很容易被大公司复制。这些问题都指向同一点：我们还没有完全讲清楚“星币六”为什么应该成为一个独立产品。&lt;/p&gt;
&lt;p&gt;也有人对产品感兴趣，我们临时建立了一个十人左右的内测群。但活动结束后，大家回到原本的生活节奏，群里也逐渐安静下来。它只能说明现场存在兴趣，还不能证明长期需求。&lt;/p&gt;
&lt;p&gt;最终没有获奖时，我还是有些难过。不过比起反复猜测评委的想法，我更想把自己能够看见的问题整理清楚。&lt;/p&gt;
&lt;p&gt;我认为主要有三个：&lt;/p&gt;
&lt;p&gt;第一，选题判断、产品差异化和团队匹配不足。我们没有统一的评估方法，也没有完全想清楚与闲鱼、记账软件的区别。&lt;/p&gt;
&lt;p&gt;第二，Demo 和路演准备不足。没有提前确定完整的演示故事，也没有准备离线视频和弱网方案。&lt;/p&gt;
&lt;p&gt;第三，报名材料缺少复核。最后一晚还在集中填写，没有安排第二个人对照赛道要求逐项检查。&lt;/p&gt;
&lt;p&gt;它们其实是一条连续的链路：选题依据不够清楚，产品主线就容易模糊；主线模糊，功能优先级和 Demo Story 就难以确定；到了最后，只能同时补功能、部署、材料和路演。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ygrowly.github.io/_astro/04-final-night.9DdKmhDs_1zFAYj.webp&quot; alt=&quot;最后一晚的准备现场&quot;&gt;&lt;/p&gt;
&lt;h2&gt;下一次会有什么不同&lt;/h2&gt;
&lt;p&gt;下一次参加 Hackathon，我会先准备一份简短的个人介绍，把自己能够快速提供的能力拆成几个原子模块，例如 AI 结构化输出、上下文读取、简单的 Agent 工作流和后端接口。&lt;/p&gt;
&lt;p&gt;选题时，团队需要从需求强度、创新性、可演示性、团队匹配、商业空间和赛道契合几个方面进行比较。对于存在争议的方向，先做三十到六十分钟的最小实验。&lt;/p&gt;
&lt;p&gt;正式开发前，先写出一条完整的 Demo Story，再反推必须完成的功能。提交前安排一人独立复核材料，路演前准备离线视频，以及三十秒、两分钟和五分钟三个版本的介绍。&lt;/p&gt;
&lt;p&gt;这些流程并不复杂，但能避免最后一晚同时补所有东西。&lt;/p&gt;
&lt;h2&gt;我想成为怎样的 Builder&lt;/h2&gt;
&lt;p&gt;这次经历并没有改变我当前的定位。我还是希望以 AI 应用开发为主，先做好一个 Builder。&lt;/p&gt;
&lt;p&gt;但我也不想把 Builder 理解成等待需求、完成开发的人。&lt;/p&gt;
&lt;p&gt;一个好的 Builder 需要参与问题判断，验证想法，理解用户，也要知道哪些功能应该放弃。代码跑通只是完成了一部分，产品最终能否被理解、被使用，同样需要负责。&lt;/p&gt;
&lt;p&gt;我也希望自己不只是工程能力更强。以后无论是尝试轻创业，还是逐渐参与更多产品工作，都需要创意、产品判断和对真实需求的理解。&lt;/p&gt;
&lt;p&gt;这次 Hackathon 让我第一次比较完整地看到，一个想法如何被提出、被选择、被实现，又如何在展示时暴露出前面没有解决的问题。&lt;/p&gt;
&lt;p&gt;凌晨两点以后，我开始理解 Hackathon，也开始重新理解自己想成为怎样的 Builder。&lt;/p&gt;</content:encoded></item></channel></rss>