1. 为什么一定要动地基旧架构的账本先交代一下背景。Orkas 是我从三年前就开始维护的一个 Agent 开发框架定位是帮团队快速搭建带记忆、会调用工具、能编排多步骤任务的智能体。早期版本核心就是一套 ReAct 循环模型推理出下一步动作执行工具观察结果再推理直到产出最终答案。这套逻辑在 demo 阶段非常好用十行代码就能跑通一个 Agent大家都很兴奋。但等我们真正把它塞进生产环境接到第二十个需求时问题开始像藤蔓一样缠上来。旧架构最大的问题是“什么都在循环里”。循环里既要解析模型输出又要做工具路由还要管记忆读写甚至情绪化地塞进一些业务校验。结果就是核心循环越来越胖每次加功能都像往一个已经装满的行李箱里再塞东西——能塞进去但拉链迟早会崩开。我们经常遇到的情况是一个团队改了工具返回格式另一个团队调了模型 prompt两个 PR 合在一起Agent 的行为就变得不可预测。代码评审变成考古git blame 翻遍五层调用栈才能定位到改动来源。真正让我下决心重构的是一次线上事故。某个客户跑了一个多小时的金融报告生成任务Agent 在执行到第 27 步时因为一个工具接口超时整个循环直接终止前面所有历史、缓存、中间结论全部丢失。用户拿到一条干巴巴的错误提示只能从头再跑。复盘时我们发现旧架构把“执行步骤”和“状态持久化”完全耦合在一次内存调用里进程一退出什么都没了。这已经不是优化能解决的问题而是地基层面的缺陷。那次事故之后我花了整整两周梳理需求最终决定不修修补补直接重写 Orkas 的底层。这次重构的核心原则可以浓缩成一句话让 Agent 框架更像一个操作系统而不是一条写死的流水线。操作系统管进程、管内存、管调度应用程序只需要关心自己的业务逻辑。Orkas 的目标也应该一样——运行时负责生命周期和资源记忆系统负责状态存取工具层负责能力接入编排层负责策略各层之间通过清晰接口通信谁也不碰谁的内部实现。下面我会把整个重构过程中的设计思路、踩过的坑、以及最终落地的细节都摊开讲希望给同样在做 Agent 框架或打算重构 Agent 基础组件的朋友一些参考。2. 重构目标与顶层设计下沉核心上浮业务2.1 四条设计原则重构启动前我先把团队拉到一起定了几条不允许妥协的原则。没有这些原则重构很容易在过程中失去方向最终变成一个加了更多补丁的新版旧系统。第一条原则是“运行时与策略彻底分离”。运行时只负责任务编排的底层机制步骤调度、异常捕获、上下文传递、生命周期管理。策略则属于上层业务下一步调用哪个工具、什么时候该结束循环、如何规划多步任务。旧架构里这两件事全揉在 agent 主类里重构后策略应该由外部注入运行时通过标准接口去调用策略让框架本身不绑定任何固定思维模式。这样才能同时支持简单的 ReAct、复杂的 Plan-and-Solve、甚至未来可能出现的全新范式。第二条原则是“一切状态皆可持久化”。Agent 执行过程中的每一步中间状态包括当前步骤编号、工具调用参数、返回结果摘要、工作记忆内容都必须能序列化到一个快照里。这样就算进程崩溃也可以从最近的快照恢复而不是从头再来。基于这个原则我们顺带设计了一套“断点续训”能力——不对应该叫断点续跑能力对长时间运行的任务特别重要。第三条原则是“记忆要有层次不能一个列表打天下”。旧系统里记忆就是一个 List[Message]把系统提示、历史对话、工具结果全塞在一起。随着对话变长或任务变复杂这个列表很快就会被无关信息淹没模型既记不住重点也容易串场。重构后我们做了三层记忆短期工作记忆、长期事实记忆、永久结构化记忆各有各的读写协议和清理策略。第四条原则是“工具接入必须标准化MCP 是一等公民”。业界对工具调用的协议已经有不少共识MCP 就是其中比较有代表性的一个。我们不打算发明新协议而是把 MCP 作为工具层的首选接入方式同时保留一层适配器让内部已有的 HTTP 工具、Python 函数也能被包成统一接口。2.2 分层架构总览经过几轮讨论最终 Orkas 的底层分成了五个主要模块从下往上依次是运行时内核、记忆系统、工具总线、编排策略层、接口层。我画了一张架构图记在设计文档里这里用文字描述一下各层职责。运行时内核是核心的心脏它不关心你的 Agent 是干什么的只负责按指令一步步执行。每个“步骤”是一个不可变的事件对象内核维护一个执行队列把当前步骤交给策略层决定怎么处理再把结果写回状态存储。这一层还包含重试机制、超时控制、错误分类和生命周期钩子。记忆系统是独立的存储模块内部区分短期工作记忆、长期事实记忆和永久记忆。短期工作记忆类似进程内的缓存容量有限每次执行只保留最近的关键信息长期事实记忆类似数据库按实体和关系组织定期从对话与工具结果中提炼永久记忆则是用户主动写入的配置、偏好和不可遗忘的约束优先级最高。工具总线负责统一管理所有可被 Agent 调用的能力。每个工具在总线上注册为一个描述符包含名称、参数 Schema、校验规则、执行函数。工具总线不直接调用工具而是把调用请求和返回值封装成事件这样一来可以方便地实现日志审计、权限控制、并发限制和 mock 测试。编排策略层是唯一允许写业务逻辑的地方。你可以实现一个策略类比如 ReActStrategy、PlanAndSolveStrategy或者一个完全自定义的 GraphStrategy。策略类只做决策不自己执行工具而是向运行时提交一个“下一步动作请求”。这个设计让框架可以随时换策略而不需要修改底层机制。接口层提供面向不同使用场景的 API同步调用、异步流式输出、Webhook 回调、以及一个 Python 装饰器风格的高层接口。我始终认为框架不能被 API 绑架但好用的 API 能决定开发者愿不愿意用你。2.3 关键取舍不用 Workflow DSL改用事件总线在设计编排层的过程中我们遇到了一个分叉路口是采用当时很流行的 Workflow DSL用 JSON/YAML 定义节点和连线还是做成事件总线加策略注入。一开始团队里有声音支持 DSL因为可视化好、业务人员也能看懂。但深入评估后我发现 DSL 有个致命问题它把图的结构当成静态配置但 Agent 的运行本身就是动态的。模型下一步会说什么、工具返回什么结果这些都无法提前写进一个静态图里。如果强行用 DSL 表达动态分支只能写出大量条件节点最终和写代码无异还比代码难调试。所以我们选择了事件总线模式。运行时内核只发布事件和响应事件策略层监听这些事件决定下一步发布什么新事件。整个过程像一个状态机在驱动自己而不是中央控制器在指挥。这样做的好处是极其灵活坏处是新手可能觉得“框架没有形状”。为缓解这一点我们后来封装了一个高层 API把最常见的事件序列藏起来让普通用户仍然可以像写 ReAct 一样快速上手。但底层绝对是事件驱动的。3. 核心细节从 ReAct 循环到运行时内核3.1 运行时内核Step、ExecContext 与 Agent Loop下面直接进入代码层面的重构细节。旧版 Orkas 的核心循环大概长这样简化版# 旧版示意所有逻辑堆在循环里 class OldAgent: async def run(self, task: str): messages [{role: user, content: task}] while True: response await self.llm.chat(messages) action parse_action(response) if action.type finish: return action.output result await call_tool(action.name, action.args) messages.append({role: tool, content: result})这段代码的问题在于没有回归上限、没有状态持久化、没有记忆管理、没有中间事件、无法单独测试任何环节。重构后的运行时内核变成了这个结构dataclass class Step: step_id: str # 全局唯一 kind: str # observe, decide, act, finish payload: dict parent_step_id: str | None created_at: float class ExecContext: def __init__(self, state_store: StateStore): self.state_store state_store self.current_step: Step | None None self.hooks: list[StepHook] [] async def emit(self, step: Step): # 持久化当前步骤然后交给事件总线 await self.state_store.append_step(step) await self.event_bus.publish(step) class OrkasRuntime: def __init__(self, strategy: Strategy, executor: Executor, memory: MemorySystem): self.strategy strategy self.executor executor self.memory memory async def run(self, task: str, context: ExecContext): init_step Step(step_idnew_id(), kindobserve, payload{task: task}, parent_step_idNone) await context.emit(init_step) while not context.finished: step await self.event_bus.next_step() decision await self.strategy.decide(step, context) next_steps await self.executor.execute(decision, context) for ns in next_steps: await context.emit(ns) return context.final_output这里的关键点在于OrkasRuntime本身不包含任何业务决策逻辑它只负责“把一个步骤放进去从事件总线里取出下一步再放进去”。Strategy.decide负责思考Executor.execute负责执行两者完全解耦。我特意保留了ExecContext这个对象它就像是线程中的 TLS贯穿整个执行过程。所有状态快照、步骤历史、临时变量都挂在 context 上而不是散落在各个模块里。这样做的好处是如果要实现断点恢复只需序列化ExecContext如果要调试只要打印context.steps就能看到完整执行轨迹。3.2 记忆体系短期、长期与永久记忆的实现方案记忆是我们重构时投入最多精力的一部分。Agent 如果没有记忆就像一个人每句话都要重新解释背景根本没法完成复杂任务。旧版用一个 messages 数组打天下的方案已经被证明不可持续我们在新版里实现了三层记忆系统。短期工作记忆本质上是“当前任务相关的上下文窗口”。它存储最近 N 轮交互、当前步骤结果摘要、临时推理草稿。实现上我用了类似缓存淘汰的思路每条记忆有一个recency分数和一个importance分数当总长度超过阈值时按加权分数淘汰最不重要的条目。这样既能防止上下文无限膨胀又能让关键信息比如用户反复强调的需求保留下来。class ShortTermMemory: def __init__(self, max_items: int 50): self.items [] self.max_items max_items def add(self, content: str, importance: float): self.items.append({ content: content, importance: importance, recency: time.time() }) self._evict() def _evict(self): if len(self.items) self.max_items: return # 综合分数 0.7 * 新近度 0.3 * 重要性 self.items.sort(keylambda x: x[recency] * 0.7 x[importance] * 0.3) self.items self.items[-self.max_items:]长期事实记忆解决的是“跨任务的知识沉淀”。比如 Agent 之前发现用户喜欢简洁风格或者某个 API 返回的字段含义这些信息应该被提取出来并长期保存。我们实现了一个提炼器每隔几轮交互用一个轻量模型把短期记忆中符合“事实”的句子抽取出来经过去重和置信度打分后写入长期存储。长期存储用的是向量数据库加结构化表混合方式实体和关系放结构化表描述性知识放向量库。查询时先解析当前问题里的实体再向量召回相关背景合成为增强上下文。永久记忆则是最顶层的约束比如用户明确设置的偏好、合规要求、安全规则。永久记忆的读写权限被严格管控普通 Agent 不能自动写入只有用户或管理员通过专门接口才能修改。执行时永久记忆会以最高优先级注入提示词保证 Agent 不越界。这三层记忆互相配合的工作流程是这样的任务开始先加载永久记忆和与任务相关的长期记忆执行过程中短期记忆实时读写任务结束后异步运行提炼器把新的长期记忆入库。整个过程对上层策略透明策略只管调用memory.search()和memory.remember()。3.3 工具层与 MCP让 Agent 自己决定用哪个工具箱工具层在旧版里只是一个简单的注册表一个字典key 是字符串工具名value 是函数。新版将工具抽象成“工具箱 总线”一个工具不仅是可调用函数还自带描述、参数 Schema、权限要求、适用场景标签。最关键的变化是我们把 MCP 作为标准接入协议。MCP 的好处在于它把“工具的定义”和“工具的执行”分开模型看到的是标准化的工具描述执行时通过 MCP 客户端与远端服务通信。这样即使工具背后的实现从 Python 函数换成了独立微服务Agent 侧完全无感。在 Orkas 里我设计了一个ToolDescriptordataclass class ToolDescriptor: name: str description: str input_schema: dict tags: list[str] # 如 [finance, read_only, low_latency] permission: str # public / restricted / admin invoke: Callable[..., Any] mcp_endpoint: str | None None工具总线在每次决策前会生成一张“候选工具清单”。这张清单不是把所有工具都丢给模型而是根据当前步骤的上下文做预筛选。比如当前任务是查天气就没必要把支付工具加进来当前工具标签包含read_only就不能调用写数据库的工具。预筛选大大减少了模型的决策噪音也降低了幻觉概率。关于并行调用我们在工具层实现了简单的gather语义。当策略明确表示“这几个工具互不依赖”时工具总线可以并发执行并把结果合并成一个聚合步骤。这在旧版里需要手写并发逻辑现在只要在策略决策里声明依赖关系运行时自动处理。3.4 Skill 与 Agent 的边界划分这是一个让很多人混淆的概念我在这轮重构里也把它彻底理清了。简单说Skill 是“能力单元”Agent 是“拥有目标和策略的主体”。一个 Agent 可以拥有多个 Skill但 Skill 是不具备自主决策能力的它只是被 Agent 调用的工具或流程模板。在我们的架构里Skill 被实现为一个“带预设步骤序列的超级工具”。普通的 Tool 是一次函数调用而 Skill 可以包含多个 Tool 调用、内嵌的条件判断、甚至调用子 Agent。比如“生成周报”这个 Skill内部可能涉及读取数据工具、汇总模型调用、生成图表工具最后格式化输出。对 Agent 来说它只需要决定“用不用周报 Skill”而 Skill 内部的流程由 Skill 自己的小型状态机管理。为了支持 Skill 和 Agent 的良性互动我们给 Skill 接口加了一个feedback通道。Skill 执行到一半如果发现前置条件不满足可以主动返回一个“需要更多信息”的信号Agent 看到这个信号后会调整自己的策略比如先去问用户补充数据再重新执行 Skill。这个设计让 Agent 不再是生硬地按顺序调用技能而是像人一样根据执行情况动态调整方案。4. 实操过程一场持续三周的迁移手术4.1 第一步先给旧系统打上“观测补丁”重构的第一天我没有写任何新代码而是先在旧系统里加上了全链路日志和指标采集。这一招是从一次架构评审会学到的不把旧系统的真实运行数据摸清楚就动手重写等于蒙眼换发动机。我们需要采集的数据包括每次执行的总时长、模型推理耗时、工具调用耗时、循环次数、错误类型分布、上下文 token 消耗量。其中最核心的是“步骤级耗时拆解”。旧系统没有这样的细粒度日志我不得不临时在循环的每个关键节点加了埋点。这些埋点丑但至关重要。因为有了这些数据我们才能在重构后做一比一的性能对比。比如我们发现旧系统平均 70% 的耗时来自模型推理工具调用只占 15%这告诉我们新架构应该重点优化推理相关的缓存和并行而不是纠结于工具函数的 IO。另一个发现是约有 12% 的执行是因为 JSON 解析错误导致循环重启这个数据直接推动了我们在新系统中引入“自适应结构化输出”机制而不是让模型裸输出 JSON。4.2 第二步用适配器模式双跑新旧内核重构最怕的是“一把梭”旧代码删掉新代码重写结果新架构半年跑不通业务全部停摆。我们采取的过渡方案是双跑模式——新旧两套内核同时存在通过配置开关切换并且让它们跑同一组测试用例和同一批线上流量镜像。具体做法是定义了三个接口LegacyRunner、NewRunner、CompareRunner。CompareRunner接收同样的输入同时调用新旧两个内核然后对比输出和中间轨迹。对于输出不同的案例我们一件件分析原因有的是旧系统 bug有的是新系统行为改进有的是两边对工具结果的理解不同。这个过程非常耗时但每一份差异报告都让新系统更可靠。双跑期间我们特意挑了一些长尾场景比如超长上下文对话、多语音输入语音转文字后的长文本、嵌套工具调用、模型突然返回格式错误等。这些场景在单元测试里很难构造但在真实流量里随处可见。我记得有个案例是真事旧系统在处理“计算上月同比”时会把“上月”理解成上一个自然月而新系统结合长期记忆后能理解业务上下文中的“上月”。这种差异只有对比测试才能发现。4.3 第三步分批切换流量与回归清单双跑稳定后我们开始分批切换生产流量。切换顺序遵循“从低风险到高风险”的原则第一批是只读查询类的轻量 Agent 任务比如信息检索、文档摘要第二批加入工具调用但工具只限于只读 API第三批开放写操作任务比如发送邮件、更新数据库最后一批才是涉及支付、合约的高风险任务。每一批切换都配套一张回归清单。清单中除了功能点还有几个固定项目执行成功率、平均耗时、p95 延迟、上下文 token 消耗、工具调用失败率。我们设定了红线指标任何一项劣化超过 10% 就立即回滚该批次。这里特别想说一下“回滚预案”的重要性。很多团队重构不做回滚演练真出问题时手忙脚乱。我们在切换前已经演练了三次从新系统切回旧系统的操作整个流程压缩到不到五分钟。结果真正用到这个预案的次数是零但演练带来的安心感和操作肌肉记忆让团队在发布当天心态非常稳。回归清单里还包含了一项非常容易被忽视的指标用户体感语义一致性。我们每一批随机抽出 20 个案例让业务同事盲评新旧系统的输出看是否出现“改坏了”的迹象。这个指标无法自动化但能捕捉到自动化测试发现不了的行为偏移。曾经就发生过新系统把一个口语化的助理改成了机械式回复模型分数没变但用户明显觉得别扭。靠着人工盲评我们在上线前及时调回来。4.4 第四步压测与性能回退预防压测不是简单地把并发打到多高而是找出新架构的瓶颈分布。我们使用 Locust 模拟了不同规模的任务单轮对话型 Agent、多工具调用型 Agent、长时间训练型 Agent。压测结果有几个比较有意思的发现。第一新内核的事件总线在低并发下比旧循环多了约 3% 的延迟因为毕竟多了一层事件拆包和分发。但到了高并发500 并发以上新内核的优势出来了由于步骤持久化是异步写的我们可以在内存里批量聚合快照磁盘 IO 的摊销效应让整体吞吐比旧系统高了近 40%。第二工具并行调用的收益非常明显。旧系统里如果策略决定调用三个独立工具必须串行等待新系统自动并行在模型推理占大头的前提下工具阶段耗时几乎可以忽略不计。第三短期记忆的淘汰算法在高频写入场景下会出现一点竞争锁解决办法是把内存队列改成分片结构每个片段一个锁冲突概率大幅降低。性能回归防护方面我们在 CI 里加入了一个基准测试套件每次 PR 合并前都会跑一遍标准任务集记录耗时和资源占用。一旦发现比基线劣化超过 5%就会拦截 PR 并要求给出解释。这套机制在重构后帮我们抓到了不少因为日志库误用导致的性能回退可以说非常值。5. 常见问题与排查技巧实录5.1 循环终止失控Agent Execution Terminated Due to Error这可能是 Agent 开发里最经典的一类报错了。旧版系统里循环一旦遇到异常就可能直接终止整个进程导致用户工作全部丢失。新版虽然做了断点快照但我们还是收到过几次这个报错排查后发现大多是策略层主动抛出终止异常而不是运行时的问题。我们的做法是把“错误”分成两类可恢复错误和致命错误。可恢复错误比如工具超时、模型输出格式不对会被运行时捕获并生成一个“重试步骤”策略可以选择再次尝试或换一种工具。致命错误比如找不到依赖的配置、权限被拒绝才会触发用户可见的AgentExecutionTerminated。为了让策略层能区分这两类错误我定义了异常层级class RecoverableAgentError(Exception): 可重试的错误例如临时性网络故障、工具超时 retry_after: float 1.0 class FatalAgentError(Exception): 无法继续执行的错误必须终止 user_message: str 并且为致命错误附带了完整的ExecContext快照。这样用户崩溃后可以通过oras.resume(snapshot_path)恢复而不是从头再来。如果你在自己的框架里也遇到这类报错建议先看日志里错误的父类再决定是要增加重试策略还是真正修复底层状态存储。5.2 记忆错乱短期记忆串场我遇到过几个典型的记忆错乱案例。最典型的是Agent 在完成一个多步骤任务后把上一步的工具输出错误地当成了当前步骤的输入。排查后发现是短期记忆在读取时没有区分“当前步骤上下文”和“历史步骤摘要”导致模型把旧数据当成新数据。解决方法是给短期记忆的每个条目强制附加一个scope字段取值是current或history。策略层构造 prompt 时先放current范围的条目再放history摘要。同时我在记忆写入入口做了校验凡是工具返回结果都自动归类为current凡是对话历史提炼的内容归类为history。这个小小的字段改动让记忆混乱的场景几乎归零。还有一个容易踩坑的地方长期记忆的写入很可能和用户隐私相关。我们在写入前会做一次实体脱敏把手机号、身份证、地址等敏感字段替换成占位符。如果你做的是面向 C 端的 Agent这一步必须要有否则一旦长期记忆被坏人以提示注入的方式提取出来就是严重事故。5.3 工具调用顺序错乱并行与依赖如何协调引入并行工具调用后出现了一个新问题策略层有时会错误地声明“工具 A 和工具 B 无依赖”但实际上 B 的输入要等 A 的输出。如果运行时盲目并行就会拿到空值或脏数据。我们的解法是在策略层的决策对象里增加dependencies字段。每个动作声明它依赖哪些上游步骤 ID。运行时看到一个动作的依赖未完成时会把它优先调度到依赖完成后而不是直接执行。换句话说事件总线内置了一个轻量的 DAG 调度器。但这里的坑在于如果策略写错了依赖运行时无法自行判断。所以我在工具总线里加了一个运行时校验根据工具的输入 Schema检查上游步骤中是否有匹配的变量名。如果没有即使策略声称不依赖总线也会拒绝执行并提示错误。这个“双重保险”让工具调用顺序问题减少了 80%。5.4 安全加固防注入与权限边界Agent 框架的安全问题在重构中必须前置考虑。旧版系统里工具返回内容被直接拼进上下文如果某个工具返回的是用户可控的文本攻击者就能通过构造恶意字符串诱发提示注入诱导 Agent 执行危险操作。我们的加固分三层。第一层所有外部返回内容在进入记忆前都经过内容清洗剥离掉看起来像是指令的片段但保留信息本身。这个清洗不是简单地删掉“ignore previous instructions”之类的话而是用专门的小模型对文本做指令意图检测命中则进行隔离。第二层工具调用的权限边界统一由permission字段控制Agent 不能动态提权。也就是说即使模型被注入诱导去调用“删除用户”这个工具只要该工具标记为restricted运行时就会直接拒绝并记录审计日志。第三层对于高风险操作默认启用“人工确认”钩子Agent 必须返回一个等待用户确认的步骤而不是直接执行。这三层加在一起不能说绝对安全但至少能挡住绝大多数已知的注入手段。安全是 Agent 进入生产环境的第一道门槛建议大家在架构设计阶段就留好这些钩子不要等到出事了再补。6. 一些没有写进文档的经验6.1 重构不是重写兼容性是一面镜子这是我在整个项目里最深的体会。很多人一重构就想着推翻一切“旧 API 反正没人用直接换掉”。但实际上的教训是兼容性不仅仅是对用户的承诺更是对自己的约束。正是因为我们保留了大量旧版接口的兼容层迁移才没有变成一场大爆炸。旧接口可以在一段时间内作为一个适配器内部调用新内核这不仅让老用户代码不用改也让我们能渐进式地验证新内核的正确性。兼容性还有一个好处它会暴露出你对业务理解的盲区。每当你发现某个旧接口难以映射到新架构时就应该停下来想是不是新架构缺失了某种能力而不是粗暴地回答“这是旧设计不合理该改”。有时候确实是旧设计不合理但更多时候旧接口代表着某类真实需求新架构需要有自己的承接方式。6.2 测试要跟着架构走旧版 Orkas 的测试全都建立在“端到端调用 agent.run()”这一个维度上。重构后我们发现这种测试太粗一旦某个中间环节出错定位成本极高。于是我们把测试分成了四层单元测试覆盖每个工具函数和记忆策略组件测试覆盖运行时内核的事件分发逻辑契约测试覆盖工具描述符与真实工具的 Schema 一致性端到端测试数量最少只保留真正跨层的关键路径。这套分层测试最直接的效果是每次提交后能在五分钟内知道新改动到底影响了哪一层。旧系统经常出现那种“跑十遍偶然挂一次”的怪问题现在因为每层都有自己可控的输入重复概率大大降低。我建议所有做 Agent 框架的人都应该认真设计分层测试不要迷信全链路的 golden 测试。全链路测试更适合作为发布前的安全网而不是日常开发的主要验证手段。6.3 最后再分享一个小技巧给每一步都留个名字新版 Orkas 里我给每一步骤都加了step_id和parent_step_id一开始只是为了实现断点恢复。但在实际使用中我发现这组标识符对调试的帮助远超预期。每当用户报告一个奇怪行为我只要把整个执行轨迹导出按 parent 关系构建一棵树肉眼就能看到 Agent 的决策路径找到哪一步出现了逻辑偏离。如果你也在做自己的 Agent 框架哪怕不重构我也强烈建议从今天开始给每一次 Agent 决策打上结构化的 trace 标识。这几乎零成本但未来省下的调试时间会非常可观。Orkas 的底层重构到现在已经稳定运行了半年这半年里我们又在这个地基上加了长任务调度、多 Agent 协作、以及更细粒度的权限策略。回头看这次重构最值得的其实不是技术选型本身而是我们想清楚了一件事Agent 框架的本质不是“帮模型做决定”而是“给模型提供做决定的稳定环境”。地基稳了上面长什么草都好办。