Execution Engine
每個請求在 exec.Execute() 中執行主迴圈,最多 128 次迭代。每次迭代:
- 組裝 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、更新去重表、發出 EventToolResult |
標記為 concurrent 的 tool:read_files、list_files、glob_files、search_files、fetch_page、search_web、search_google_news、send_http_request、download_file、transcribe_media、calculate、invoke_subagent、list_subagent_sessions、search_chat_history、search_error_history、read_error、remember_error、list_rag、search_rag、list_chatbot、list_tools、search_tools、list_schedule、list_revisions、reasoning_guide、write_skill、write_tool。write 類檔案 tool、run_command、互動類 tool、api_* / script_* / ext_* 以及 MCP tool 一律序列執行。
去重是「每次執行」而非「每個 session」:重複的 tool|args 組合直接回傳先前結果而不再執行,且任何 write tool 都會使涵蓋其寫入路徑的 read_files 項目失效。另有一層 30 分鐘的結果 cache 存於 ToriiDB,僅涵蓋 fetch_page、search_web 與 search_google_news。
Pending registry
internal/runtime/pending.go 是由 prefix 路由的 confirm/ask listener registry,由主 agent 與任何 in-process subagent 共用。Producer(toolCall confirm、ask_user handler、store_secret handler)呼叫 Ask(ctx, req) 並阻塞於 per-entry buffered=1 的 reply channel;每個 runtime 透過 pending.RegisterListener(prefix) 註冊一個 listener(TUI/CLI 用 "" 匹配全部,Telegram daemon listener 用 "tg-"),並只透過 PickNextFor(prefix) 認領匹配的 entry。ctx 取消會移除該 entry,使過期的 producer 絕不浪費一次人為互動。
Gate pending.HasListener(sessionID) 檢查是否有匹配 prefix 的 listener 為該 session 註冊。這取代了舊的全域 pending.Active atomic.Bool,讓 Telegram、Discord、CLI 的 confirm 流程能並行運作而不互相阻塞。
Send 失敗處理
Agent.Send() 失敗採逐級升高處理,而非盲目重試:
| 失敗類型 | 行為 |
|---|---|
| Timeout | 對同一模型重試至 MaxSendTimeoutRetries(3 次),間隔 SendTimeoutRetryInterval |
| Rate limit / cooldown 規則辨識的上游狀態 | 對該模型註冊 30 分鐘 cooldown,接著切換到下一個 fallback |
| Context 長度超出 | 裁掉最舊的一組對話(compact.TrimFallback)後重送;只有在無可裁剪時才中止 |
| 其他錯誤 | 切換到下一個 fallback 模型;無可用 fallback 時中止 |
切換模型會清空 ToolHistories(或套用 compact.RawToolFallback)、重置去重表並歸零計數器——新模型不會繼承失敗模型的殘留狀態。
跨 turn workdir 重置
每則新的 user message 會重建 Executor,並透過 os.Getwd() 將 data.WorkDir 重置為 process cwd — 被 cd 改動的 workdir 不會跨 turn 保留。兩道護欄防止 LLM 從歷史推斷出過期的 workdir:
- L1(system prompt) —
Work directory: {{.WorkPath}}行,加上明確提醒:先前的cd文字來自較早的 turn - L2(per-message) — 每則 user message 都被包上含當前時間戳與工作目錄的 metadata header;workDir 行是最強的錨點,覆蓋任何歷史近因偏誤
TUI 透過 stripUserMetaHeader 在視覺上剝除該 wrapper;LLM 仍原樣收到。
[!NOTE] 本文件由 Claude 讀取完整原始碼後自動生成。