一個 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 是房間里平等的一等成員;
- 所有交互都是異步消息,走唯一一條消息總線入隊(不存在並行扇出的私有路徑)。
指揮官的"調度"不是寫在散文里的 @somebody——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 一寸未讓;
- 以及預設關閉、有界、可觀測的記憶與自進化。
功能可以一個個加,但地基只值得認真翻一次。翻完之後,上面蓋什麼都更快了——這正是這次重構想要的結果。