internal/llmloop/(loop.go + pool.go + compression.go)internal/llmloop 是「單一檔案的評審引擎」。每個 Runner 管理一個檔案的 plan+main 迴圈:呼叫 LLM、執行工具、收集 code_comment、記憶超過預算時觸發三分區壓縮。它與 internal/agent 的分工:agent 管編排(哪些檔、何時),llmloop 管執行(怎麼跟模型對話)。
RunPerFile 實作架構文件裡的 main 迴圈:
loop up to MAX_TOOL_REQUEST_TIMES (30):
response = llm.complete(messages, tools)
無 tool calls → nudge 模型再試
執行每個 tool call → 收集結果
看到 task_done → break
addNextMessage(...) # 可能觸發壓縮
回傳 MainLoopStop 列舉(task_done / max_tools / empty_rounds / cancelled / compression_failed),讓上層知道為何結束。
parseToolArgs 把模型回傳的原始 JSON 參數解析成 map[string]any;lookupTool 從 tool.Registry 找到 provider;executeToolCall 呼叫 Provider.Execute 並記錄 TaskCheckpoint(寫進 session JSONL)。code_comment 特別派發到 CommentWorkerPool——不阻塞主迴圈。
當模型在最後階段可能漏掉已產出的評論時,跑一個「寬限輪」:graceRoundToolDefs 過濾掉可能誤導的工具定義,給模型最後一次整理 code_comment 的機會——提升評論完整性而不延長太多。
每次加訊息前計算總 token(CountMessagesTokens):超過 60% 閾值啟動非同步背景壓縮(runCompression 在 goroutine),超過 80% 在送下一個請求前同步壓縮。回傳 false 表示壓縮無法壓回閾值以下——這是五個退出條件之一。
全部用 atomic 計數器(多子任務並行安全)。TotalInputTokens/TotalOutputTokens/TotalCacheReadTokens 等由 agent 層彙整成總摘要。
RunPerFile 的主迴圈結構與五個退出條件、CommentWorkerPool 為何讓主迴圈不阻塞、寬限輪的目的、以及 60%/80% 閾值的壓縮觸發時機。下一步:internal/diff 或回程式碼對照。