pi agent 扩展与 hook 机制浅析

一、前言

最近在重新翻 Pi Agent 的几个包时,我开始注意到一个之前没单独拆开看的东西:Extension。

一个很直接的原因是,前面两篇把架构、Session、Context、Compaction 都讲了一圈,但凡是讲到 extension state 的地方,都只是一笔带过。而我自己在实际折腾 Agent 时发现,真正决定「这个 Agent 能不能贴着我的工作流跑」的,往往不是核心 Loop 本身,而是 Loop 之外那些能悄悄插手的扩展点。

所以这篇文章不想再去重复 Agent Loop 怎么转,而是想回到一个更具体的问题:

Pi Agent 里的扩展,到底是怎么被加载、在什么位置介入、又通过什么方式影响 Agent 的运行?

本文会以 Pi Agent 为例,看看 Extension 在分层架构里处在什么位置、它的生命周期有哪些阶段、以及最关键的 hook 机制是怎么把「外部逻辑」缝进 Agent 执行过程中的。

二、扩展在分层中的位置

2.1 扩展不是第五个核心包

回顾一下 Pi Agent 的四个核心模块:

  1. pi-ai:管模型、Provider 接入。

  2. pi-agent-core:管执行流程、状态、工具调用循环。

  3. pi-tui:管终端 UI、输入框、消息渲染。

  4. pi-coding-agent:管 CLI / Harness、会话、配置、工具、上下文、安全信任,依赖上面三个包。

扩展并不属于这四个包里的任何一个。

更准确地说,Extension 是一层「外部介入面」------它由 resourceLoader 从 cwd 相关的资源里加载进来,然后被注入到 AgentSessionextension state 中。它不替 Agent 做决策,只在某些关键节点上改变 Agent 看到的上下文、拦截一次工具调用、或者接管一部分 UI。

所以扩展和四个核心包的关系可以概括成:

复制代码
pi-coding-agent
    │  依赖
    ├─ pi-agent-core   (执行循环 / 状态 / 工具调用)
    ├─ pi-ai           (模型 / Provider)
    └─ pi-tui          (终端 UI)

Extension(不在上述四个包内)
    │  由 resourceLoader 加载
    ├─ 元信息  (name / description / location)
    ├─ hook 定义 (before_agent_start / beforeToolCall / ...)
    ├─ UI 扩展  (命令 / 面板)
    └─ 运行态  (ExtensionRunner / UI context / handlers)

2.2 为什么扩展必须跟着 Session 走

前面讲 Services 时提到过一个关键点:Session 往往与当前 cwd 强绑定,而 cwd 会决定项目配置、AGENTS.md、Skills、还有扩展本身。

因此扩展并不是全局挂载一次就完事。当一个 Session 被新建、恢复、Fork 或切换时,它依赖的那套运行环境会先被重建,扩展也会随之重新加载和绑定。

也就是说,扩展的生命周期,是挂在 Session 生命周期下面的。这一点后面讲 rebind 时会再回来。

三、扩展的加载与生命周期

3.1 加载时机

扩展的加载,发生在 resourceLoader 初始化当前 cwd 相关资源的过程中。它会把当前项目(以及全局)下声明的扩展读进来,交给 AgentSession 登记到 extension state

这里最关键的一点是:

扩展在进入 Agent Loop 之前就已经就位,但它本身并不进入 messages 数组------它只是静静地等着被各个 hook 点位触发。

3.2 生命周期阶段

一个扩展从加载到退场,大致会经历这样几个阶段:

复制代码
加载 Extension(resourceLoader 读入)
        ↓
before_agent_start
        │  (可临时改写 system prompt / 注入上下文)
        ↓
Agent Loop 运行
        │  ├─ beforeToolCall   (工具调用前)
        │  ├─ afterToolCall    (工具调用后)
        │  └─ ... 其他 hook 点位
        ↓
agent_end
        ↓
shutdown / abort / error handler(收尾与清理)

简单来说,扩展的「存在感」主要来自两头:一头是 Loop 启动前的 before_agent_start,一头是 Loop 运行过程中散布的各个 hook。中间那段 Loop,扩展并不主动参与,只在被 hook 命中的瞬间插一句话。

四、hook 机制

4.1 什么是 hook

hook 是扩展介入 Agent 运行的唯一通道。

它本质上是一个「在某件事发生前 / 发生后,允许外部逻辑先跑一段」的约定。Pi Agent 不把扩展的逻辑硬编码进核心 Loop,而是把若干关键节点暴露成 hook 点位,扩展按需挂载。

因此,对核心 Loop 来说,它看到的不是「某个扩展在做某事」,而是「在这个节点上,如果注册了 hook,就先执行它」。

4.2 常见的 hook 点位

从前面提到的 extension state 字段,可以梳理出几个典型的 hook 点位:

  • before_agent_start:Agent 真正开始跑之前。适合做上下文准备,比如临时改写 system prompt、注入项目级约束。

  • beforeToolCall:每次工具调用执行前。适合做校验、审计、改写参数。

  • afterToolCall:工具执行之后、结果写回 Context 之前。适合做结果转换、脱敏、追加日志。

  • shutdown / abort / error handler:Agent 结束、被中止或出错时的收尾。适合释放资源、保存状态。

例:

/demo 帮我在这个项目里加一条「每次写文件前先备份」的规则

这条规则最终不是写进核心 Loop,而是被实现成一个挂载在 beforeToolCall 上的扩展。

4.3 hook 改写上下文,但不留脏状态

before_agent_start 有个很实用的用法:临时改 system prompt。

但这里有个容易踩的坑------如果扩展在启动阶段改了 system prompt,而跑完之后不还原,那么后续每一轮 Loop 都会带着这份被改过的提示词,污染整个会话。

所以 Pi Agent 的处理方式是:扩展在 before_agent_start 里写的是「本轮临时 override」,等这一轮执行结束,再把它清掉,恢复成基础 system prompt。

复制代码
before_agent_start
        ↓
改写 system prompt(本轮临时 override)
        ↓
Agent Loop 执行
        ↓
执行结束
        ↓
清掉 override,恢复原 system prompt

核心可以一句话概括:

扩展对上下文的改写是「会话级临时生效」,不是「永久持久化」。

五、扩展与 UI / 宿主的交互

5.1 Extension UI context

扩展不只是逻辑,它还能带一部分 UI。

extension state 里能看到 ExtensionRunner、扩展 UI context、扩展命令上下文这些字段。也就是说,扩展可以注册自己的命令、面板,甚至在某些节点上接管终端里的展示。

对 Agent 来说,这部分和核心 Loop 是解耦的:Loop 只负责推理和工具,UI 的呈现交给 pi-tui 与扩展各自协商。

5.2 切换 Session 时的 rebind

前面埋的那个点,这里收回。

当用户切换 Session 时,旧 Session 被销毁、新 Session 创建,这套运行环境会被重建。此时扩展既然是挂在 Session 下的,它的 UI 绑定也必须跟着重新接上。

这一步由 AgentSessionRuntime 的 rebind hooks 完成:

复制代码
切换 Session
        ↓
AgentSessionRuntime.rebind hooks
        ↓
通知 UI / Host 重新绑定当前 session
        ↓
Extension UI context 重建

因此,扩展的 UI 不是「全局常驻」,而是「随当前 Session 动态绑定」。这保证了不同项目下的扩展不会串台。

六、扩展的权限边界

6.1 扩展能做什么,不能做什么

扩展能做的事,严格受它挂载的 hook 点位约束:

  • 它能在 before_agent_start 改写上下文,但不能凭空创造一轮新的 Agent Loop;

  • 它能在 beforeToolCall 拦截和改写工具参数,但不能绕过工具直接改 messages;

  • 它能在收尾阶段释放资源,但不能阻止 Agent 已经做出的决策。

一句话:

扩展是 Agent 运行过程中的「介入面」,不是「替代品」。

它改变的是 Agent 在某个节点上「基于哪些信息、以什么约束」去做决策,而不是替 Agent 把决策做了。

七、整体来看

如果把扩展机制压成一条链,就是:

复制代码
Extension 被 resourceLoader 加载
        ↓
注入 AgentSession.extension state
        ↓
before_agent_start 改写上下文
        ↓
Loop 中 hook 介入工具 / 消息
        ↓
agent_end / shutdown 收尾清理
        ↓
Extension 本身不进入消息流,
只通过 hook 与上下文影响 Agent

所以 Extension 本质上不是 Agent 内部的「功能模块」,而是一种运行过程中的「外部介入面」:它不替 Agent 思考,只在关键节点悄悄改写上下文、拦截工具、接管 UI,然后把自己清理干净。

如果说 Agent Loop 负责的是「决策怎么发生」,那么 Extension 负责的就是「决策发生的环境与约束长什么样」------它让一套固定的核心 Loop,能够贴着不同项目、不同工作流去「个性化」地运行。

相关推荐
土星云SaturnCloud1 小时前
ResNet-50 图像分类算法在边缘微服务器上的部署与性能评测
服务器·人工智能·算法·边缘计算·resnet-50
QYRdata1 小时前
基于ANPR数据分析平台市场增长预测(2026-2032年复合增长率4.3%)
大数据·人工智能
果粒蹬i1 小时前
AI 不只回答问题:用 Hermes Agent 把微信指令接到 Windows 电脑上
人工智能·ai
workflower1 小时前
AI 转型正从工具部署转向生产关系重构
人工智能·安全·机器学习·机器人·无人机
“AI国潮设计-小江”1 小时前
【Python/SDXL实战】潮汕国潮IP视觉落地:普宁美食猫IP & 创意甜品设计(附ComfyUI工作流与商用授权思路)
开发语言·人工智能·python·prompt·aigc
2601_962380761 小时前
挑战者杯成果视频的素材匹配路径:从文案到成片的实操拆解
人工智能
RisunJan1 小时前
AI 每日要闻总结(2026-09-14)
人工智能
陈嘿萌1 小时前
ACM MM 2026 华北电力|连续融合空间实现实例级细粒度的可控红外与可见光图像融合
人工智能·计算机视觉·图像融合·学术研究与理论基础
Java后端的Ai之路1 小时前
【AI大模型Harness】-Codex源码深度解读
人工智能·源码·ai大模型·codex·harness