一个 AI agent 产品做到后面,最贵的不是功能,而是地基。 这篇文章讲 Orkas 在 1.0 这条发布线上做的一次底层重构——把模型调用、Agent 循环、多智能体编排和工具生态整体翻新——以及每个决定背后的取舍。
为什么要动地基
Orkas 是一个 local-first 的桌面 AI agent 工作台:所有 agent 工作都在用户本机的进程内跑,数据落在本地,再按需做端到端的云端同步。早期版本功能堆得很快——技能库、知识库、连接器、群聊式多 agent——但越往后越能感觉到:真正的瓶颈不在某个功能,而在三件"地基级"的事情上。
模型调用层若沿用对话式的老路,会被一串错误假设绑住。 把大模型当成"一问一答的聊天"来调用,背后藏着一堆对聊天合理、对 agent 不合理的默认:固定的输出 token 上限、串行的工具调用、藏起来的超时、写死的单一 provider。Agent 是一个要连续跑几十轮、动辄触达上下文窗口、需要并行读文件、还要随时被用户打断的长流程——这些默认每一条都会在生产里咬你一口。更要命的是,桌面端 agent 最值钱的那部分能力——细致的文件操作、本地检索、跑 shell、并行多 worker、长程任务求解——恰好都被这层假设挡在门外。
编排是"静态计划"的。 早期是一套 plan/DAG 引擎:先让模型把任务拆成一张计划图,再由执行器按图调度。听起来很工整,但 agent 的现实是高度动态的——读一个文件发现要改方向、一个子任务的结果决定下一步派给谁。把决策固化在一张预先生成的图里,意味着每次"计划赶不上变化"都要在执行器里打补丁。
生态是封闭目录。 技能只能来自官方 marketplace,连接器是硬编码的目录,用户机器上已有的外部 agent 工具对 Orkas 完全是黑盒。用户想接入一个第三方项目、自己的 MCP server、或者让本机已有的 agent 反过来调用 Orkas 的技能和知识库——架构上全都走不通。
这次重构的论点很简单:把 Agent 的地基重新握在自己手里。 具体落成四条相互交织的主线——自建进程内运行时、provider 无关的模型层、动态群聊编排、以及从封闭目录到开放宿主。下面逐条讲。
一、把一套完整的 coding agent 能力搬进桌面端
桌面端是 agent 的主场——这里有真实的文件系统、真实的 shell、真实的本地工具链。一个只会聊天的助手在这里是浪费环境;真正能发挥桌面优势的,是一套完整的 coding agent 能力:细致到字符范围的文件读写、跨文件的检索、跑 bash 与系统工具、并行开多个 worker、以及连续跑几十轮把一个复杂任务真正做完的长程求解能力。
重构的核心产物,就是为了把这套能力原生地搬进 Orkas 自己的进程——一个独立的、可被主进程动态加载的进程内 agent 运行时(代码里叫 core-agent)。它不是又一个对话封装,而是 Orkas 自己掌控的 agent 引擎。
关键的架构决定是把它切成两层:
- 引擎层(独立包):纯粹的 agent 机制——tool-calling 循环、流式事件、上下文压缩、错误分类与重试、provider 抽象、沙箱、技能扫描、记忆、自进化。它不知道 Orkas 的任何业务:不读业务数据目录、不认识会话文件格式、不碰 IPC。
- 适配层(主进程内):把引擎接进 Orkas——会话持久化、provider 轮换、工具权限、技能注册表、连接器、知识库、各类生成工具。它把引擎产出的原生事件翻译成 Orkas 自己的事件形状,业务层只看到稳定的接口。
这条 engine/adapter 的分界,是后面一切灵活性的根。引擎可以独立测试、独立演进;适配层可以放心地塞进 Orkas 特有的复杂度(轮换、冷却、沙箱、权限)而不污染引擎。团队的架构评审给它的结论是一句话:这是赚来的复杂度,别去合并它。
这套能力具体是什么
把运行时握在自己手里,目的不是炫技,而是让 agent 在桌面端真正"动手做事"。能力集大致分四组:
- 细致的文件操作与本地检索。
read_file支持按字符范围读,PDF / Office 文档自动抽取文本;edit_file做精确的"旧串 → 新串"替换、写前必须先读;write_file落地产物并记账;stat_file探测体量;search_files按名字/通配定位、grep_files跨文件搜内容。这一组让 agent 能像工程师一样在真实工作区里"翻代码、改文件",而不是只能整段吞吐。 - bash 与系统工具。 一个受控的 shell 执行器,带后台执行模式(长任务脱离当轮、日志落文件),危险操作经风险分级把关。桌面端 agent 的杠杆,很大一部分就来自能直接调动系统工具链。
- 并行多 worker。 一轮里互不依赖的只读工具会被并发执行;任务层面,指挥官还能把独立子任务并发扇出给多个 worker(详见第三节)。该并行的地方并行,是把"长程任务"压到可接受墙钟时间的关键。
- 长程推理与任务求解。 一个能连续跑几十轮、自己管理上下文、能从错误里恢复、不会卡死空转的循环——这是"把一件复杂事做完"和"答一个问题"之间的分水岭。
工程上怎么把它做到生产级
"自己写循环"听起来是给自己找麻烦,确实有维护成本。但它换来的是对 agent 完整生命周期的细粒度控制。这些控制不是抽象的,而是一组具体改进,每一个都对应上面某项能力在生产里"扛不扛得住":
真实上下文窗口 + 80% 才压缩。 引擎读到每个模型的真实上下文窗口(包括百万级窗口的模型),在用到 80% 时才触发压缩——而不是保守地在 60% 就开始丢掉四成有用上下文。配套还有一个"无收益压缩"的护栏:如果保留的尾部本身就占满了窗口(比如尾部躺着一个很大的文件读取结果),压缩释放不出空间,就只记一条告警跳过,绝不空转烧一次 summary 调用。长程任务能不能"记得住前面",全看这里。
相邻只读工具并行。 模型一轮里点了读文件、搜文件、联网查这几个互不依赖的只读工具,引擎会把相邻的可并行工具分到同一批并发执行;写工具是天然屏障,保持声明顺序。工具调用与结果的配对严格按声明顺序提交,所以并发不会破坏协议。最常见的只读工具一下子从串行变并行,整批的墙钟时间显著下降。
读后写 + 乐观并发控制。 编辑文件之前必须先读,引擎记录被读文件的基线;编辑时校验基线没漂移。并行 worker 同时改一个文件时,败者拿到一个明确的"已过期"错误,而不是把彼此的修改悄悄覆盖掉。多 worker 并行动同一份工作区,这道保险不可少。
中途打断即时折叠。 用户在 agent 跑到一半时又补了一句话,引擎会在工具循环的边界把这条排队消息折叠进当前这一轮的输入,而不是等它单独再起一轮。这让"边跑边纠偏"变成自然交互。
循环检测。 同一个工具调用连续重复,引擎会先轻推(第 3 次)再硬停(第 5 次),任何不同签名都会重置计数——分页/轮询这类合法的变参不会误伤。模型卡死时不再无声地烧 token。
去掉主轮输出的硬上限。 主轮输出不再被写死在一个很小的上限,长报告、大段编辑不会被无声截断;辅助调用(压缩、反思)仍保守地用小上限。
还有一个很"本地"的细节值得一提:中英文混排的 token 估算。纯中文会话用通用估算会被低估两三倍,引擎按字符类别区别对待中英文,压缩阈值因此才靠谱。这种东西,通用 SDK 不会替你想。
这些改进合起来回答了"为什么不直接用现成 SDK":因为桌面端 agent 最强的那部分能力,恰好都藏在 SDK 不暴露的那一层;要把它们做到生产级,循环必须握在自己手里。
二、让模型永远在线:Provider 的多层包装
模型层的目标是一句话:无论某个 key、某个 provider、某条网络出什么事,用户的这一轮对话都要尽量活下来。 为此适配层在引擎的 provider 抽象之上叠了几层包装——轮换、冷却、注册、外部适配。
最关键的设计是轮换器坐在 runner 的下方。引擎在调用 provider 之前就把用户消息写进了持久会话;如果在引擎层做重试/轮换,要么重复提交用户消息,要么得写一套会话回滚。把轮换器放在引擎下面,用户消息只写一次,"换一个候选重试"对会话状态完全透明。
轮换器的判断也很克制,核心是首个内容事件这条线:
- 在模型吐出任何实质内容(文本/工具调用)之前失败——可以安全地切到下一个候选;
- 一旦吐出了首个内容事件——停止轮换,错误向上抛,因为模型可能已经跑过了完整一轮,重来会重复副作用。
错误分类决定了"换、不换、还是重试"。鉴权失败、余额不足、限流、订阅过期这类账号级失败,标记冷却并轮换;连接被重置这类瞬时网络失败,不冷却、原地无状态重试几次再说;而畸形请求、内容策略、服务端 5xx——换个 key 也一样失败,直接放行不轮换。冷却是进程内、不持久的十分钟提示:它只是个短期信号,不值得为每次失败写盘,进程重启正好是重新探测的合理时机。
在 provider 的"花名册"这一端,重构把三类来源拉平成一个统一抽象:
- Orkas 托管 LLM:服务端代理,登录即用,服务端在文本/图像模型间路由;
- 用户自带 key:标准的主流大模型 provider;
- 外部直连适配器:一批需要直连或自带计费的模型,手工适配成同一个 provider 接口。
对上层来说,这一切只表现为一个稳定的 (provider, model) 对——轮换、冷却、外部适配全都藏在适配层里。
三、群聊即编排:从静态 plan-DAG 到 commander-in-the-loop
这是这次重构里最"换脑子"的一块。
旧模型是静态计划:模型先生成一张 plan/DAG,执行器按图跑。新模型把这张图整个拆掉,换成一套动态的、指挥官在环(commander-in-the-loop)的群聊编排。
它的隐喻是一个群聊房间:
- Commander(指挥官) 是房间的主持人,不是一个隐形的中间件;
- Agent workers 是房间里平等的一等成员;
- 所有交互都是异步消息,走唯一一条消息总线入队(不存在并行扇出的私有路径)。
指挥官的"调度"不是写在散文里的 @某某——LLM 在正文里写 @AgentA 只是训练数据里的 markdown,不可信。真正的调度信号是结构化的工具调用,而且重构后收敛成三个语义清晰的动作:
dispatch_to—— 派一个 agent 跑完、把结果交回,由指挥官综合。多个互不依赖的任务可以并发扇出。run_worker—— 指挥官自己拥有的子任务,同步拿回结果;匿名 worker 是指挥官的"手"(对用户不可见),具名 worker 是可见的专家。hand_off_to—— 把对话交给 agent,指挥官退场,agent 直接回用户、这一轮不再综合。
为什么是群聊,而不是 orchestrator 或子代理树
把多 agent 做成群聊,带来几个传统编排器/子代理树拿不到的好处:
- 可见性切片。 每条消息只追加到"能看到它的人"的切片里。Agent worker 启动时只重放自己的切片,别的 agent 的大段输出不会污染它的上下文。指挥官看得见全部。
- 状态极简。 整个编排的核心状态只有"当前发言权归谁"和一个轻量的任务台账。没有 DAG,没有复杂状态机。
- 天然可重放、可同步。 消息按时间戳自然排序,重新加载、跨设备同步都直接落在消息流上。这套架构曾用于移动端远程控制原型:所有 agent 计算都在桌面端跑,移动端只做镜像呈现。当前公开桌面版本未启用 iOS 远程控制中继。
团队的架构评审对这一点的判断也很直接:群聊式的总线加上指挥官在环,就是 Orkas 的多 agent 形态;在进程内再叠一套并行子代理派发,反而会违反"只有一条群聊派发路径"的不变量。
这一版的新东西:可交互的 hand-off
重构在这条线上最新的一块拼图,是可交互的 agent 交接。
痛点很具体:一个"家教"型 agent 教了用户一轮,用户想接着追问,系统却强制把发言权收回给指挥官,用户被迫每句话都重新 @ 一遍这个 agent。
解决方案是服务端权威的发言权(floor)+ 模型决定的接收者:
- 发言权落成一个持久化的状态字段,跨重载保存,并搭着既有的状态变更事件自动同步到各端——不需要新事件类型。
- 指挥官用
hand_off_to把发言权交给一个可交互 agent 后,用户后续的"无 @"消息直达该 agent,直到 agent 主动交还或用户重新点名指挥官。 - Agent 用一个
<handback />标记交还控制权;解析时严格校验真匹配(避免把散文里偶然出现的<handback误判成交接)。 - 交还时若有未完成的任务台账,指挥官会从台账接着往下做。
配套还有一个体验上的修正——指挥官循环气泡。指挥官一轮里"派活 → 读结果 → 再派活"的循环,过去被压成一个气泡,重载时还会乱序跳到底部。重构把一轮在每次可见派发的边界处切成多个段(segment),每段是独立消息、时间戳递增——用户第一次能看见指挥官在"循环编排",重载顺序也对了。
最后,两条贯穿始终的安全网:group abort 是所有 actor 唯一的停止路径(用户一按 Stop,所有 worker 的中止信号都被掐掉,连匿名子 worker 都有兜底匹配);以及前面提到的 interrupt-steer,把用户的中途插话折进当前轮。
四、从封闭目录到开放宿主
如果说前三条是把地基打牢,这一条是把房子的门窗全部打开——把 Orkas 从一个封闭目录变成一个开放宿主——同时一寸不让地守住安全边界。
重构系统性地拆掉了几处"封闭"的卡点:
外部包(External packages)。 用户给一个仓库地址,Orkas 把它原样克隆成一个文件夹托管在本地,绝不规范化、不重写、不同步到云端(因为里面有第三方依赖目录)。一个独立的命令行工具负责安装/更新/启停的全生命周期,扫描出它是"技能形"(带技能描述文件)还是"CLI 形"(带可执行入口),把元数据写进一个包目录之外的注册表(这样后续拉取更新永远不会冲突)。依赖安装走"问一次、记住"的二步确认;可执行入口生成 shim 注入到 bash 工具的 PATH,模型于是能直接调用这些第三方 CLI。
多根技能加载。 技能执行的唯一入口从只认两个根,扩成 自定义 / marketplace / 外部包 / 全局 四层,按优先级解析;外部包里的脚本优先用包内自带的依赖环境。这是回归风险最高的一处 choke point,配了完整的 fixture 矩阵。
全局技能互操作。 直接读取用户机器上其它 agent 工具已有的全局技能目录,做到技能层面的互通——用户在一处沉淀的技能,在 Orkas 里也能用。用户把技能放进这些目录的动作本身就是授权,所以默认启用,但留了总开关。这些第三方技能描述是不可信的 prompt 注入面,所以它们走"开放层"加载器、只对指挥官可见,结构上不可能进入 agent 的技能白名单。
用户自配置 MCP。 连接器不再是硬编码目录。用户能加任意 MCP server——远程 HTTP 形态(低风险)或本地子进程形态(高风险)。表单本身就是同意面(用户亲手敲进去的命令逐字展示),传输配置(含密钥)整体进加密存储,自定义实例一律带固定前缀,永远不可能冒充目录里的官方连接器。
反向桥接:让本机的外部 agent 反过来感知 Orkas。 这是最有意思的一块。用户机器上已有的外部 agent 工具,过去对 Orkas 是黑盒;现在 Orkas 在派发它们时注入一个桥接通道,让它们能反过来列举/读取/运行 Orkas 的技能、调用连接器、检索知识库。桥接走本地进程间通道(不开网络端口),用每次运行独立、随运行结束即销毁的一次性凭据鉴权。每一次有外部副作用的连接器调用都过用户确认对话框——不是按工具名启发式判断读写(那会失之于过松),而是每个 (agent, 连接器) 一次确认、可选"始终允许"。
长尾编码姿态。 指挥官的决策树新增一条分支:没有匹配的 agent/技能/连接器时,评估直接用 bash + 一段脚本解决,这一轮做掉、验证输出,并可提议把它沉淀成一个自定义技能。配套有 bash 后台执行(长任务脱离当轮、日志落文件)和用户授权目录。
开放,但不松手
把门窗打开最怕的是漏风。这次重构的纪律是:所有"危险动作"的 spawn choke point 一个都不动。 MCP 只从一个地方启动,技能执行只走一个 runner,bash 只过一个沙箱执行器。在此之上叠了几层纵深:
- 文件操作一律过路径沙箱(工作区 + 当前附件 + 用户显式授权的目录),凭据目录、系统目录、Orkas 自身目录不可被授予;
- 危险 bash(外泄、破坏性删除、提权、敏感路径)触发权限确认,决策分"仅此一次 / 本次运行 / 拒绝",日志只记类别和长度、绝不记命令文本;
- 外部包安装对软链成员 fail-closed 直接拒装(防止借软链把沙箱外的敏感文件读进来),克隆来源限定协议白名单;
- 所有携带凭据的传输/密钥一律加密存储,桥接凭据随运行隔离;
- 开源/托管发行版按裁剪规则去掉宿主专属能力。
一句话:用户的每一个显式动作(安装 / 授权 / 提交表单 / 点确认)就是同意的凭据,而每一个同意都被限制在它该有的边界里。
五、跨会话变聪明:记忆与自进化
地基翻新顺带把两个"让 agent 越用越聪明"的子系统也重做了,并且都遵循同一条工程纪律——默认关闭、上限有界、可观测。
跨会话记忆用混合检索:向量语义检索 + 关键词检索(BM25),用 RRF(倒数排名融合)合并,避免单一通道失效;落在本地存储里(带全文索引)。记忆分两类——agent 自己的笔记和用户偏好画像,都有字符上限、写入前过注入威胁扫描,并在每轮开始冻结注入进系统提示。整套记忆只服务于让 agent 更懂当前用户,数据始终在本地,用户在设置里可随时查看、编辑、导出。
自进化是 agent 私有的技能库(和平台共享技能库分开存放)加一套元认知反思。引擎按一组加权信号判断要不要反思:用户纠正(权重最高)、从非平凡错误中恢复、任务复杂度、已知弱点被触发或被克服、技能失效……信号加权超过阈值才反思。反思本身是一个后台周期任务(约 12 小时一轮、数小时冷却、多日兜底),用便宜的小模型读最近的活动摘要,决定要不要创建/修补一条技能、更新 agent 的"能力画像"。
安全上最重要的一条:只有显式绑定了 agent 的会话才开启自进化——默认的指挥官会话不进化。反思有 token 双重上限(条数 + 总量)、单 agent 失败不阻塞其他、单次成本被压到极低。让 agent 变聪明,但不让它失控。
工程心法:赚来的复杂度,别去简化它
重构期间团队做了多轮架构评审,有一条结论反复出现,值得单独拎出来:区分"组织肥大"和"赚来的复杂度",只动前者。
- 同步引擎的多种合并策略、自建的 agent 循环、provider 的多层包装、移动端远程控制的边界——这些看着复杂,但每一层都在挣自己的钱(多设备最终一致性、深度集成、多 key 轮换、产品决定的端边界)。强行"简化"只会把数据弄丢、把分层弄脏。
- 真正该动的是"上帝模块"和局部重复:把臃肿的群聊总线里那些无状态纯函数(prompt 组装、指挥官工具、CLI 轮)拆出去,把重复了多遍的"确认对话框"模式收成一个通用件。
支撑这种判断力的,是一套写进项目约束文档的硬纪律:boundary(单进程、IPC 唯一通路、运行时只能动态加载)、layering(各层的依赖方向)、单一真相源(类目、遥测口径、域名)、以及面向 prompt 的提交必须带"prompt audit"。地基能翻新而不塌,靠的不是某个聪明的设计,而是这些不变量被持续守住。
结语
把这四条线接起来看,这次底层重构给 Orkas 换上的,是一套自己掌控、provider 无关、动态编排、对外开放、还能自我进化的 agent 地基:
- 一个 engine/adapter 两层的进程内运行时,把一整套 coding agent 强项能力——文件操作、本地检索、系统工具、并行多 worker、长程求解——原生搬进桌面端,并逐条做到生产级;
- 一个多层包装的模型层,让对话在 key/provider/网络抖动下尽量活下来;
- 一套群聊式、指挥官在环的多 agent 编排,把"静态计划"换成"动态决策",并第一次让 agent 之间的交接变得自然;
- 一个从封闭目录走向开放宿主的生态,外部包、全局技能、自定义 MCP、反向桥接全部打通,而 spawn choke point 一寸未让;
- 以及默认关闭、有界、可观测的记忆与自进化。
功能可以一个个加,但地基只值得认真翻一次。翻完之后,上面盖什么都更快了——这正是这次重构想要的结果。