internal/llmloop/loop.go — per-file 評審迴圈

工具呼叫對話、記憶壓縮、退出條件
套件:internal/llmloop/(loop.go + pool.go + compression.go)

大方向

internal/llmloop 是「單一檔案的評審引擎」。每個 Runner 管理一個檔案的 plan+main 迴圈:呼叫 LLM、執行工具、收集 code_comment、記憶超過預算時觸發三分區壓縮。它與 internal/agent 的分工:agent 管編排(哪些檔、何時),llmloop 管執行(怎麼跟模型對話)。


1. 主迴圈

Runner.RunPerFile() loop.go單一檔案的 main 迴圈:LLM 往返 → 工具執行 → 收集評論。

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),讓上層知道為何結束。

2. 工具執行

executeToolCall() · lookupTool() · parseToolArgs() loop.go執行單一工具呼叫;查 registry;解析參數 JSON。

parseToolArgs 把模型回傳的原始 JSON 參數解析成 map[string]anylookupTooltool.Registry 找到 provider;executeToolCall 呼叫 Provider.Execute 並記錄 TaskCheckpoint(寫進 session JSONL)。code_comment 特別派發到 CommentWorkerPool——不阻塞主迴圈。

runGraceRound() · graceRoundToolDefs() loop.go讓迴圈「優雅收尾」的最後一輪。

當模型在最後階段可能漏掉已產出的評論時,跑一個「寬限輪」:graceRoundToolDefs 過濾掉可能誤導的工具定義,給模型最後一次整理 code_comment 的機會——提升評論完整性而不延長太多。

3. 記憶管理

addNextMessage() loop.go把一輪往返加入訊息緩衝;依 token 閾值觸發非同步/同步壓縮。

每次加訊息前計算總 token(CountMessagesTokens):超過 60% 閾值啟動非同步背景壓縮(runCompression 在 goroutine),超過 80% 在送下一個請求前同步壓縮。回傳 false 表示壓縮無法壓回閾值以下——這是五個退出條件之一。

4. 計數器

RecordUsage() · RecordWarning() · recordToolCall() loop.go原子記錄 token、警告、工具呼叫計數。

全部用 atomic 計數器(多子任務並行安全)。TotalInputTokens/TotalOutputTokens/TotalCacheReadTokens 等由 agent 層彙整成總摘要。

看完這頁你應該能說出RunPerFile 的主迴圈結構與五個退出條件、CommentWorkerPool 為何讓主迴圈不阻塞、寬限輪的目的、以及 60%/80% 閾值的壓縮觸發時機。

下一步:internal/diff 或回程式碼對照