Skip to content
GitHub

理解设计扩展点

v0.1.0

为什么区分 Hook、Middleware 和 observer ​

给 Agent 增加日志、反馈、调用计时或项目规则时,最容易把扩展写成一个无所不能的回调:既改请求,又触发工具,又保存状态,最后所有组件都依赖它。Iris 按介入时刻与修改权限划分扩展点,使扩展不会代替运行、工具或存储的 owner。

要做的事合适的入口可以影响什么
某个事件发生时执行处理器Hook工具前拒绝,工具后追加反馈;run 事件用于通知逻辑
包装一次普通工具调用ToolMiddleware短路或执行一次下游,返回替代结果
消费已经提交的运行事实RunEventObserver外部展示、记录、转发;不回滚提交
判断哪个延迟工具匹配意图DecisionEvaluator 接点在允许候选中选择,保持同一发现契约

Hook 只在明确事件上工作 ​

Hook 接收事件快照,不接收任意可变 runtime。run.started 对应新 logical run,run.finished 对应本次新终态;等待与恢复不会伪装成新的开始或结束。

工具事件的位置有意靠近真实执行:参数与权限检查完成后触发 before;真实 body 已产生已知结果,且普通后续处理仍可继续时触发 after。before 返回拒绝原因,after 返回模型反馈,二者都不直接改写 lifecycle store。

以文件写入为例,after Hook 可以提醒模型“请说明修改的影响”,这段反馈跟随工具结果进入后续请求。它不把真实写入结果改成另一种业务事实。事件中的结果快照与 body_status 也区分包装后的可见结果和真实 body 成败。

普通异常的后果由事件语义决定:before 失败会阻止当前调用,因为它尚未发生;after 或 run Hook 的普通失败记录日志并继续,不回滚已经发生的副作用。取消、结果未知和资源清理失败保留其运行控制语义,不能吞掉后报成功。

Middleware 是一次调用的包装层 ​

Middleware 用 wrap_tool_call(call, call_next) 形成嵌套链,首个注册项最外层。它可以直接返回缓存等替代结果,也可以调用一次下游再修改结果。调用参数是独立快照;结果的修改通过返回新对象完成。

“下游至多一次”把重复副作用的控制放在一个明确边界。Middleware 不能随手调用两次 call_next 实现重试;它也不能在下游仍运行时返回、把真实执行遗留为无人管理的任务。框架会收拢已启动的下游,取消与未知结果优先于包装器提供的普通返回值。

这种约束使计时、缓存等常见能力足够直接,同时防止它们悄悄改变运行生命周期。真实 body 的结果已知后,包装器自己的后置失败保留下游结果;只有包装器根本没执行 body,才归为它自己的失败。熔断统计同样依据真实 body,不能因缓存命中或包装器改文案而虚构一次执行成功。

observer 看的是提交后的事实 ​

Hook 和 Middleware 在执行过程中工作,observer 接收 store 已经提交的 RunEvent。同一个 observer 的调用按顺序串行,不同 observer 独立;普通异常或超时记录日志并继续。它适合连接宿主展示或外部记录系统,不适合做工具执行前的决定。

由于它在 settlement 后投递,不能承诺逐 token 实时性。即时流式显示由流式输出与观测接口处理;最终结果与恢复以 lifecycle/store 为准。把事件“展示到了面板”不能当成“状态已经正确持久化”的证明,反过来也一样。

装配声明与运行行为分开 ​

YAML 声明 Python 工厂路径和 options,构造 Agent 时调用同步工厂一次,获得 handler 或 Middleware 实例;执行时只使用已装配对象。配置项按声明顺序保留,SDK 注入项追加在 YAML 项之后。child 使用自己的配置装配扩展,父扩展不自动继承。

命令 Hook 用固定脚本接受事件 JSON、输出单个 JSON 对象,借用现有命令环境而不绕路经过模型工具。它的期限归 Hook 自己,资源停止仍遵循命令服务与 harness 的分工。框架没有另外创建一套脚本调度器或权限交互界面。

Decision 是另一个窄接口:输入共享状态和一组结构化问题,输出按问题 ID 定位的答案与独立用量。工具发现可以借用它做候选选择,但执行过程不依赖具体服务商。默认本地搜索继续可用,启用远程判断后失败也不会静默改变为另一个后端,从而让行为和配置一致。

按扩展 Cookbook接入实际代码;精确返回值、顺序和错误规则见参考手册。源码入口:扩展装配、事件模型、包装链、observer 协议。