有一段时间我只要听到同事说“能不能让 Pi Agent 把这批日志看一遍”心里就会一紧。倒不是不想帮忙而是单机版的 Pi Agent 用来做临时分析确实顺手一旦变成团队共用的小助手问题立刻暴露任务状态不知道在哪、并发一高就超时、工具权限边界模糊、跑完一次没有审计记录。后来我们花了大概三个月把 Pi Agent 的引擎能力重新封装成了内部代号叫 AIRUN 的企业级运行时现在回头看这条路走得挺值。这篇文章就把我们从“一个脚本型 Agent”到“可托管的 Agent 运行时”的完整过程拆开讲讲包括架构取舍、模块设计、部署运维和迁移中的坑希望能给正在做同类事情的人一些参考。1. 为什么要从 Agent 脚本走向运行时1.1 最初 Pi Agent 只是个人效率工具最早接触 Pi Agent 的时候它更像一个“带工具集的编码助手”。我把它跑在本地开发机上通过命令行对话触发让它读仓库代码、改测试、跑 lint甚至让它自己写提交信息。那时候的需求很朴素我不想手动敲重复命令希望有一个能理解上下文的自动流程。Pi Agent 的桌面端和 CLI 支持了这种用法配置好模型 API Key 和本地工具权限之后确实能把我从很多机械操作里解放出来。但个人工具和团队平台之间隔着一道很深的沟。个人使用时我清楚自己给了 Agent 什么权限、跑在什么目录、模型上下文里塞了什么内容出了问题我可以直接看终端输出、翻进程、重启进程。可当其他同事也想用的时候要求就完全不同了他们不关心你怎么装的只关心“能不能给我一个地址我发个请求就能跑任务”。于是“安装一个 Pi Agent 在自己电脑上”的模式天然不适合团队化。1.2 团队使用后暴露的四类问题我们把 Pi Agent 开放给组内七八个人试用后问题集中爆出来。第一是状态管理缺失。Pi Agent 的设计重心是单次会话里的多轮交互它把对话状态放在本地换一台机器、换一个终端就没有了。团队场景需要任务中心化谁发起的、跑到哪一步、结果在哪、失败原因是什么这些问题必须能回答。第二是并发能力太弱。本地跑 Agent 一般一次只跑一个任务我在一台 16GB 内存的开发机上最多开两三个实例内存和 CPU 就吃紧。多人同时触发任务时本地进程的调度完全是操作系统随机给的没有任何优先级和资源隔离概念。第三是权限不可控。单机版能用的工具集如果直接开放给团队基本等于裸奔。文件读写、执行 Shell 命令、调用外部 API这些能力在个人桌面端没问题但要作为企业服务提供必须做完整的身份认证、操作授权和敏感操作审批。第四是没有可观测性。Agent 推理过程是一个黑盒只有输入输出中间调了什么工具、消耗了多少 token、成功还是失败都没有结构化记录。做实验可以生产环境没法接受。这四个问题指向同一个结论我们需要的不只是把 Pi Agent 变快一点而是需要一个能承载 Agent 生命周期管理的运行时。于是 AIRUN 立项了。2. AIRUN 的总体设计把一个 Agent 引擎改造成可托管服务2.1 运行时与引擎的边界划分关于“运行时”这个概念不同项目理解不同。我们定义的 AIRUN 并不是重写 Agent而是提供一个托管环境Agent 引擎只负责“思考并决策调用工具”AIRUN 负责“让这个决策能安全、可重复、可观测地执行”。所以架构上我们做了一次严格分层。最底层是模型接入层统一封装 OpenAI 兼容协议、本地模型服务等不让上层关心具体模型厂商。中间是 Agent 引擎层也就是 Pi Agent 的核心调度逻辑我们把它当库来引用去掉原先绑定的终端交互界面只保留 plan / act / observe 循环。再往上是 AIRUN 运行时层负责任务接收、队列调度、会话持久化、工具注册、权限校验、审计日志。最顶层是接入层提供 REST API、WebSocket 推送和内部管理面板。实践中我们最开始犯过一个错想改 Agent 引擎内部的规划逻辑以为这样可以更好地适配运行时。后来发现方向反了。引擎和运行时的边界应该像 CPU 和操作系统CPU 负责算指令操作系统负责分配进程、管理内存、调度优先级。让 CPU 去管进程调度既慢又乱。Agent 引擎只要把“下一步打算调用哪个工具、参数是什么、为什么”暴露出来运行时就能做控制。因此我们保留了 Pi Agent 原始的 planning 核心只增加了一个“决策钩子”在每个动作执行前把计划发给运行时校验。2.2 核心抽象任务、会话、工具、策略AIRUN 的领域模型围绕四个抽象展开。任务是用户提交的一次完整运行请求包含触发参数、目标描述、约束条件如超时时间、资源配额。任务会有唯一 ID从 pending 到 running、succeeded、failed、canceled整个生命周期都写数据库。我们没沿用 Agent 内部“对话轮次”的概念因为多轮对话只是任务过程中的交流不是业务单位。你需要为业务计费、审计和追踪的只有任务。会话代表一次交互上下文它可能跨多个任务。比如用户让 Agent “每周分析一次日志”每次分析是一个任务但模型需要知道之前几周的结论这时会话就发挥作用了。AIRUN 把会话数据独立存储按需注入到 Agent 的上下文中避免每次任务都从头积累大量历史。工具是 Agent 能调用的外部能力AIRUN 把它做成了可插拔注册表。每个工具声明自己的输入 schema、权限级别、是否允许携带机密参数、调用超时和限流规则。Pi Agent 自带的本地工具我们保留了一部分但大多数对接内部系统的工具都是重新用 SDK 写的比如查询工单、读取监控指标、触发 CI。策略是对 Agent 行为的约束包括禁止调用某些工具、限制读取某些路径、必须经过审批才能执行变更类操作。策略引擎在工作流的不同阶段执行检查规划后、工具调用前、工具返回后都有对应钩子。这四个抽象配合起来让上层使用方不用关心 Agent 内部细节。一个业务部门想用 AIRUN 做个数据助理只需要创建任务、定义允许访问的数据源工具、设定输出格式剩下的都由运行时处理。2.3 为什么选择了“异步事件驱动 状态外置”而不是直接调用最初做 AIRUN 第一版时我图省事直接在 Flask 接口里调用了 Agent 引擎的同步方法。请求来了就同步跑到 Agent 给出最终结果再返回。本地测试看起来完美上线后很快发现一个稍复杂的任务要跑五分钟HTTP 连接早被网关断掉了数据库连接池也被长事务占满更麻烦的是如果进程重启正在跑的任务直接消失。后来我们重构为异步事件驱动。API 层接收请求后只做校验和落库返回任务 ID 和“已受理”状态后台通过消息队列把任务分发给 WorkerWorker 拉起 Agent 引擎实例执行执行过程中的状态变化、工具调用记录、token 消耗等全部通过事件流写入数据库和日志系统。用户可以通过 API 轮询任务状态也可以订阅 WebSocket 事件推送。这里的关键词是“状态外置”。Agent 引擎内部不再保存任何必须依赖进程内存的东西。每次工具调用前引擎把必要的上下文从存储里拉取出来组装成 Prompt每次工具调用后结果立即持久化。这样一来 Worker 崩溃并不可怕重启后可以从最后一个持久化事件继续或者至少能定位到失败节点。事件驱动带来的另一个好处是背压控制。当任务洪峰过来队列会自动堆积Worker 按消费能力处理不会把 Agent 引擎压垮。而同步调用模式只能靠限流丢请求用户体验很差。我们后来给队列设置了优先级交互式任务优先于批量任务这样既保证实时性又能充分利用空闲资源。3. 关键模块落地细节3.1 API 网关与认证授权AIRUN 的 API 层一开始只做了简单的 Token 校验后来发现不够。企业内部服务调用场景更常见的是服务间认证我们尝试让 AIRUN 对接公司的统一 SSO但 Agent 任务往往是长时间运行的用户的登录态可能已经失效。最后我们采用了双 Token 机制用户通过 SSO 获取短时访问 Token用于提交任务、查状态同时为每个长期任务签发一个运行时 Token用于 Agent 调用内部系统 API 时的身份标识。每个运行时 Token 会绑定任务 ID、允许访问的工具列表、到期时间。这样内部系统看到的身份不是“某个用户”而是“某个任务”审计时能直接定位。工具层面我们也实现了一套作用域声明机制比如“只读日志系统”工具会自动带上只读角色任何写操作在网关层就被拒绝。这条路径上踩过的坑是不要把用户权限直接透传给工具用户可能有一个很宽的角色但某个具体 Agent 流程只需要最小权限。运行时应当重新计算“最小必要权限”避免权限扩散。3.2 任务调度与并发控制调度模块是 AIRUN 里业务逻辑最复杂的一块。我们需要防止两个问题一是同质任务挤占资源二是某个失控 Agent 一直循环调用工具把 token 烧光。调度器按任务类型做了队列分组代码生成类、数据分析类、运维操作类各自分开每组有自己的 Worker 池。Worker 数量不是拍脑袋定的我们根据 Agent 引擎单实例的峰值内存来算。假设每实例峰值 1.5GB一台 8GB 内存的虚拟机扣掉系统占用后留给运行时 6GB最多跑 4 个 Worker。实际使用中还要留一半内存给突发所以安全并发数一般取 2。公式很简单但很多人忽略Agent 引擎的峰值内存不是启动内存而是上下文拉满、工具返回大量结果时的内存需要压测统计。我们还加了两个控制旋钮每个任务的“最大工具调用次数”和“最大 token 消耗”。初始化任务时如果不显式指定就套用团队模板里的默认值。比如数据分析任务默认最多调用 60 次工具超出后 Agent 会被强制终止并标记为 failed。这么做看似粗暴但确实能挡住那些因为 Prompt 写得不清楚而在错误的循环里出不来任务。3.3 上下文管理与可观测性Agent 的上下文窗口是有限资源企业级运行时比个人使用更在意上下文利用率。个人用 Pi Agent 时塞很多冗余历史问题不大多人共用同一个模型服务时冗余上下文直接变成成本。我们在 AIRUN 里实现了一个简单的上下文压缩策略每轮对话结束把历史摘要交给一个小模型生成 summary再拼接当前轮的关键数据。执行工具调用时只保留工具返回结果的结构化摘要原始大响应放到对象存储需要用细节时再按 ID 召回。可观测性方面AIRUN 强制要求每个工具调用记录一个事件事件至少包含任务 ID、工具名、输入参数摘要、输出结果状态、耗时、token 增量、调用次数序号。把这些事件接入 Prometheus Loki 之后我们终于能回答“这个 Agent 到底在干什么”了。很多团队忽略这点觉得 Agent 是 AI不是中间件不用做 tracing。实际上 Agent 天生就是异步和不确定的如果没有完整事件链出问题后只能靠猜。3.4 工具生态与沙箱隔离工具安全是运行时最不能省的部分。Pi Agent 桌面端可以信任本地进程因为用户自己负责安全检查企业级环境里Agent 执行代码、写文件、调 API 都必须在受控沙箱内完成。我们对不同工具设了三种隔离级别。第一类是纯 API 调用基本只做网络出站限制允许访问企业内网白名单第二类是脚本执行放在容器里跑容器内只挂载必要的只读数据卷写操作走另外的网关注册第三类是交互式命令比如需要登录服务器查询时我们不会把 SSH 权限直接给 Agent而是提供一个“命令审批代理”Agent 生成命令后由审批代理执行执行结果返回给 Agent。这样最危险的操作始终有人工审核环节Agent 只是提出建议决定权还是控制在人手里。沙箱镜像我们定期重建避免 Agent 在运行时通过工具下载额外依赖改变镜像内容。镜像里不安装任何机密配置文件所有密钥都通过环境变量在启动 Worker 时注入并且只注入到具体任务需要的工具进程中。4. 部署与 SRE 实践4.1 多环境配置拆分AIRUN 部署到生产环境时我们做的第一件事就是把配置从代码和镜像里剥离。配置文件按环境拆成四块基础配置各环境一样、环境配置不同环境的数据库连接、队列地址、机密配置密钥走密钥管理服务、功能开关灰度中的新工具、新策略。我们用 YAML 文件管前三类功能开关放到配置中心实时修改避免每次调整都重启。这里有个教训不要图省事把所有配置塞在一个 JSON 里。最开始我们就是单个 config.json结果为了在生产环境临时开一个开关必须重新出包风险高还慢。后来改成配置中心之后开关变更都是秒级生效而且能按任务类型、用户组做百分比灰度。4.2 内存、超时与重试参数调优运行时稳定性的很多细节都隐藏在一堆看似不起眼的参数里。我们把这些参数整理成了标准模板供新环境部署时参考。超时方面Agent 引擎里有两个超时单次工具调用超时和任务总超时。单次工具调用我建议设置在 60 到 120 秒短了容易误伤长时间查询长了会让队列堵住。任务总超时按类型区分交互式任务 15 分钟批量分析任务 2 小时。重试方面我们不重试 Agent 引擎的整个任务因为重跑可能产生不同结果幂等性不可控。正确的做法是把重试粒度缩小到工具调用如果一个只读工具调用因为网络抖动超时可以自动重试最多两次如果是写操作绝不自动重试必须返回状态让上层人工决策。内存调优上我们给每个 Worker 设置了硬上限和软上限。软上限超过后拒绝新任务但不杀进程硬上限超过后直接重启 Worker 并标记任务失败。GC 参数也需要根据上下文场景调整Pi Agent 的上下文通常会产生大量短期对象默认的新生代比例在长时间运行中会导致频繁 Full GC我们改成更激进的新生代大小停顿明显变少。4.3 灰度发布与回滚经验AIRUN 的每次引擎升级都伴随不确定性因为 Agent 的行为既受代码影响也受模型 Prompt 影响。我们灰度发布分三层。第一层是内部环境只允许自己团队跑第二层是外部试用组开放给 5% 用户第三层全量。每层观察的指标不是“服务没挂”而是三个核心指标任务成功率、工具调用误差率、平均 token 消耗。用户说“效果变了”但成功率没降这类问题就体现在 token 消耗波动上。回滚方案我们特意设计成“版本镜像 数据库兼容”。每次发布新引擎时旧的 Worker 镜像保留至少三天。数据库 schema 变更必须向前兼容不允许发布后立刻删旧字段。这样一旦发现新版本有问题直接切换 Worker 镜像即可回滚不用动数据。这套机制让我们在一次引擎升级导致工具参数解析异常时五分钟内回到了旧版本影响面很小。5. 从 Pi Agent 迁移到 AIRUN 的避坑清单5.1 Prompt 和工具定义的不兼容点将现有 Pi Agent 工作流迁移到 AIRUN最花时间的不是代码而是 Prompt 和工具的描述。个人使用时工具描述写得比较随意例如“读取仓库文件”因为使用者就是作者本人隐含了大量约定。到了 AIRUN 这种平台化环境模型对同一个工具名的理解完全取决于描述文本。我们把所有工具描述重新按统一模板改写名称、用途、输入参数、参数约束、返回值结构、错误语义、示例。改写完发现很多工具成功率明显上升因为模型能更准确地选择工具并生成参数。另一个坑是历史对话格式。Pi Agent 的本地会话记录是偏人类阅读习惯的AIRUN 需要的是结构化事件序列。迁移时不能直接丢给新运行时需要做格式转换。我们写了一个迁移脚本把历史会话拆成 messages tool_calls observations 三段式结构再导入新存储。5.2 状态同步与幂等性企业级运行时必须假定网络可能断、服务可能重启因此所有写操作都要幂等。AIRUN 的 API 层要求客户端提交任务时带上幂等键。比如同一个自动化流程要重复执行客户端可以生成同一个 UUID服务端发现相同幂等键且任务已完成时直接返回之前的结果而不是再建新任务。这个机制在我们对接内部工单系统时特别重要否则 Agent 重试会把同一个变更工单提交两次。任务内部的工具调用同样有幂等问题。我们允许工具注册“幂等模式”声明哪些操作是天然幂等的如查询类、Set 类型操作哪些不是如创建资源、发送消息。对后者AIRUN 会在工具调用前向目标系统发送一个预检查请求或者在参数里注入一个操作令牌目标系统重复执行时可以通过令牌拒绝二次执行。5.3 成本控制与配额管理Agent 运行时的成本和普通 API 服务完全不同普通服务成本取决于请求量Agent 成本取决于模型的推理次数和上下文长度。我们发现迁移后成本暴涨的一个原因是原本本地会话里很多历史被重复塞进上下文。AIRUN 的上下文压缩虽然能压但最好在源头上控制每个任务创建时设定目标预算超出预算就触发降级策略比如改用更小模型重跑。配额管理我们也做了三层。用户级别每人每天最多提交多少任务、消耗多少 token。团队级别每个团队有月度预算池用完自动发通知。系统级别全局并发配额避免所有团队同时提交大量任务导致模型服务超载。刚开始我们觉得没必要这么复杂直到某天一个自动化测试脚本因为循环 bug一个下午消耗了相当于半个月的预算从那以后配额管理就成了默认配置。6. 最后再说点实在话从 Pi Agent 到 AIRUN 的改造技术上最累的不是写代码而是切换思维。个人工具关心“能不能干”运行时关心“能不能安全、可控、可观测地干”。如果你也准备走这条路我的建议是先把 Agent 引擎的边界划清楚别急着加功能先把任务状态、工具权限、审计日志这三件事做成硬约束再做别的都好说。另外一个小技巧工具调用的审计事件记得额外记录一个“触发用户”字段不要只记任务 ID。刚开始我们只记了任务 ID后来查问题时发现一个任务可能是另一个 Agent 子任务发起的没有用户信息就很难理清责任链。加上之后整个审计体系才真正闭环。希望这些经验能帮你少走点我们走过的弯路。