Execution Engine
所有進入點(TUI、/v1/send、/v1/chat/completions、Telegram、Discord、scheduler)走同一套啟動流程:exec.Prepare 從輸入比對 skill,接著 exec.Start 解析呼叫端指名的 skill、發出 skill 與模型選擇事件、透過 ResolveAgent 挑選模型與 fallback,再交給 exec.Execute()。exec.Run 已移除,改用 exec.Prepare + exec.Start。
exec.Execute() 執行主迴圈,最多 128 次迭代(limits.max_tool_iterations)。每次迭代:
- 組裝 messages:
SystemPrompts+OldHistories+UserInput+ToolHistories - 對所選 provider 呼叫
Agent.Send() - 從 response 解析
tool_calls - 透過
toolCall.go分派 tool call(three-pass concurrency,見下) - 將結果 append 至
ToolHistories - 當無
tool_calls剩餘或撞到迭代上限時停止
無 inter-round delay — rate-limit 保護來自 provider round-trip 延遲、per-model cooldown 以及各 tool 的 timeout。
Three-pass tool concurrency
toolCall.go 將每一輪的 tool call 拆成三個序列 pass;只有 Pass 2 會 fan out:
| Pass | 模式 | 工作 |
|---|---|---|
| 1 — pre-flight | 序列 | 重複呼叫去重(tool|args hash)、stub-tool 短路、confirm gate、JSON-schema 驗證 |
| 2 — execute | 對標記 IsConcurrent 的 tool 並行;其餘序列 |
tools.Execute |
| 3 — commit | 序列 | 落地 sessionData.Tools 與 ToolHistories、更新去重表、寫入結果 cache、發出 EventToolResult |
標記為 concurrent 的內建 tool(17 個):read_files、find_files、find_tools、find_note、file_history、chat_history、error_history、fetch_page、search_web、http_request、download_file、test_tool、calculate、subagents、list_chatbot、send_to_chatbot、reasoning_guide。generate_audio 與 generate_image 不帶此旗標。api_* / script_* / ext_* tool 僅在自身定義宣告時才並行。edit_file、run_command、互動類 tool 以及 MCP tool 一律序列執行。
去重是「每次執行」而非「每個 session」:重複的 tool|args 組合直接回傳先前結果而不再執行,且 edit_file 會使涵蓋其寫入路徑的 read_files 項目失效。另有一層 30 分鐘的結果 cache 存於 ToriiDB(db_0),僅涵蓋 fetch_page、search_web 與 http_request 的 GET 呼叫。
Pending registry
internal/runtime/pending.go 是由 prefix 路由的 confirm/ask registry,由主 agent 與任何 in-process subagent 共用。Producer(toolCall confirm、ask_user handler、store_secret handler)呼叫 Ask(ctx, req) 並阻塞於 per-entry buffered=1 的 reply channel。每個前端透過 RegisterListener(prefix) 註冊 listener —— TUI 用 "cli-"、web 儀表板用 "chat-"、Telegram / Discord listener 用 "tg-" / "dc-"(空 prefix 匹配全部)—— 並只透過 PickNext(prefix) 或 PickNextMatch(prefix, accept) 認領匹配的 entry。PickNextFor 已移除,改用 PickNext / PickNextMatch。ctx 取消會移除該 entry,使過期的 producer 絕不浪費一次人為互動。
Gate HasListener(origin) 檢查是否有 prefix 符合該 session 來源(OriginOf 辨識 cli-、chat-、tg-、dc-)的 listener 已註冊。這取代了舊的全域 pending.Active atomic.Bool,讓 TUI、web、Telegram、Discord 的 confirm 流程能並行運作而不互相阻塞。
Send 失敗處理
Agent.Send() 失敗採逐級升高處理,而非盲目重試:
| 失敗類型 | 行為 |
|---|---|
| Timeout | 於同一模型最多嘗試 MaxSendTimeoutRetries(3)次,間隔 SendTimeoutRetryInterval(15 秒) |
| Rate limit(HTTP 429 或 rate-limit 訊息) | 註冊 30 分鐘 cooldown,於同一模型在 5 秒、10 秒、15 秒後重試;之後切換到下一個 fallback |
| 額度耗盡(HTTP 402 / 403 或額度 / 帳務訊息) | 註冊 30 分鐘 cooldown,立即切換到下一個 fallback |
| Context 長度超出 | 裁掉最舊的一組對話(compact.TrimFallback)後重送;只有在無可裁剪時才中止 |
| 串流中無回應 | 每 30 秒 health probe(UnresponsiveProbeInterval),探測失敗後每 10 秒重試,失敗 3 次即切換模型 |
| 其他錯誤 | 切換到下一個 fallback 模型;無可用 fallback 時中止 |
Fallback 候選來自 ResolveAgent 排好的清單。nextAgent 會略過與失敗模型同供應商的模型、以及 context window 小於目前輸入的模型,對每個候選做 health check(HealthCheckTimeout,10 秒),並最多重建清單 3 輪(maxFallbackRounds)。綁定特定模型(auto 以外)的 session 不會 fallback。
切換模型會清空 ToolHistories(或套用 compact.RawToolFallback)、重置去重表並歸零計數器——新模型不會繼承失敗模型的殘留狀態。
跨 turn workdir 重置
Workdir 由每次請求提供,而非從歷史沿用:TUI 傳入自身的工作目錄、/v1/send 解析 work_dir 欄位、subagent 沿用或自行解析。GetSession 每次執行都以該目錄重建 system prompt,因此先前 turn 的 cd 不會保留。
- System prompt —
Work directory: {{.WorkPath}}行,加上明確規則:{{.WorkPath}}為本輪權威基準、忽略歷史中過期的提及,且每次run_command都已從該目錄開始
逐則訊息的 metadata header(包在每則 user message 外的時間戳與工作目錄)及其 TUI 剝除函式 stripUserMetaHeader 已移除;system prompt 的 {{.WorkPath}} 行是唯一的 workdir 錨點。
[!NOTE] 本文件由 Claude 讀取完整原始碼後自動生成。