Session Viewer

localhost:5483 瀏覽歷史評審——五條泳道看每一輪 LLM 往返

是什麼

ocr viewer 是一個小型內嵌 HTTP server,以瀏覽器友好的 UI 渲染歷史評審會話。無外部依賴——直接讀 OCR 每次評審寫到磁碟的 JSONL 檔案。

啟動

ocr viewer                    # binds localhost:5483
ocr viewer --addr :3000       # bind all interfaces on port 3000
ocr viewer --addr 0.0.0.0:8080

Server 在前台跑——Ctrl+C 停止。會話每次請求時惰性掃描,另一終端裡跑的評審一旦 JSONL 出現就顯示。

DNS-rebinding 防護:viewer 對照 loopback 白名單檢查 Host 頭。通配綁定(:3000)需設 OCR_VIEWER_ALLOWED_HOSTS 才能從 LAN 存取。

三個頁面

URL內容
/磁碟上有會話的所有 repo 清單
/r/{repo}單一 repo 的會話清單,最新在前
/r/{repo}/{sessionID}單一會話完整詳情

會話詳情頁

最有用的頁面,顯示:

  1. 頭部——diff 範圍、模型、分支、總 token、執行時長。
  2. 檔案分組——每個被評審檔案一個區塊,內含五條「任務類型」泳道:
任務類型何時出現
plan_task跑了 plan 階段(檔案 ≥ 50 行)
main_task每個檔案。主評審迴圈。
review_filter_task跑了評審後評論過濾流程
memory_compression_taskactive+compress 區超過 60%/80% 預算
re_location_taskcode_comment 無法錨定,回退重新定位

每條泳道是任務卡片的水平條帶——每個 LLM 往返一張,按任務型別著色。

任務卡片

使用場景

「模型為什麼這麼說?」

在終端輸出打開一條評論,在 viewer 定位該檔,沿 main_task 泳道往下找包含該 code_comment 的卡片。卡片的 Response 顯示模型推理。

「這個檔案為什麼靜默?」

無評論的檔案只有當模型主動呼叫 task_done 才是成功評審。泳道以錯誤卡片結束 = 偽裝成靜默的失敗。

「壓縮保留/丟棄了什麼?」

memory_compression_task 泳道顯示每次壓縮輪。Response 窗格有摘要;被壓縮的 XML 在該輪 JSONL 的 messages 中。

磁碟儲存佈局

~/.opencodereview/sessions/
└── <path-encoded-repo-path>/
    └── <session-id>.jsonl

JSONL 每行一個事件:llm_request、llm_response、tool_call 等。行是 append-only——不完整的 JSONL 表示會話被中斷,viewer 渲染已寫入的部分。

隱私

JSONL 轉錄包含發給 / 從 LLM 收到的一切(含 diff 中任何程式碼),完全存在你的 ~/.opencodereview/ 內。OCR 不會上傳到任何地方。

📖 教學解說:Session Viewer 深入

五條泳道的實務解讀

每條泳道對應一種任務型別,出現在不同時機:

  • plan_task——diff ≥ 50 行才出現。看它產出的指引是否合理。
  • main_task——每個檔案都有。主要評審迴圈,最常看的。
  • review_filter_task——評審後過濾。如果評論數大幅減少,就是它在工作。
  • memory_compression_task——超過 60%/80% 預算時出現。看摘要保留/丟棄了什麼。
  • re_location_task——錨定失敗的回退。如果頻繁出現,可能是 diff 格式異常。

任務卡片的除錯價值

每個任務卡片是一次 LLM 往返的完整記錄:請求號、模型、token、時長、Response(含 thinking)、Tool calls + 結果。除錯時看:

  • Response 中的 thinking → 模型在想什麼
  • Tool calls → 模型用了哪些工具、參數是什麼
  • 錯誤徽章 → 哪一步失敗了

JSONL 的 append-only 性質

JSONL 每行一個事件,append-only——不完整的 JSONL 表示會話被中斷。Viewer 會渲染已寫入的部分。實務意義:如果看到不完整的會話,不要慌——可能是 Ctrl+C 中斷或 runner 超時。已寫入的評論仍然是有效的。

🔍 Worked Example:用 Viewer 除錯失敗的評審

Step 1 — 啟動 viewer

ocr viewer

Step 2 — 瀏覽器打開 localhost:5483

→ 看到 repo 列表

Step 3 — 點進目標 repo

→ 看到會話清單,最新在前

Step 4 — 點進目標會話

→ 看到五條泳道

Step 5 — 找到 main_task 泳道中的錯誤卡片

→ 發現模型在第 12 輪報 tool execution error

Step 6 — 檢查 JSONL 原始資料

cat ~/.opencodereview/sessions/.../session.jsonl | jq '.tool_call.error'

預期產出

Task Card #12 (main_task):
  Model: claude-opus-4-6
  Tool: file_read → Error: file not found
  Response: "I'll proceed with the available context..."
  → Agent continued despite error, produced 1 comment

常見錯誤與診斷

錯誤訊息診斷修復
檔案顯示零評論模型沒主動呼叫 task_done 或有錯誤看 main_task 泳道是否以錯誤卡片結束
start_line: 0 / end_line: 0錨定失敗評論是真的,只是沒自動放置
DNS-rebinding 防護阻擋存取從 LAN 存取需要設定允許主機設 OCR_VIEWER_ALLOWED_HOSTS

練習 / 驗收清單

  • 能描述 viewer 的三個頁面
  • 能區分五條泳道各對應哪個任務型別
  • 能判斷「檔案為何靜默」的三種可能
  • 能解釋 JSONL 的 append-only 性質
看完這頁你應該能說出:viewer 的三個頁面、五條泳道各對應哪個任務型別、「檔案為何靜默」的三種可能、以及 JSONL 的 append-only 性質與隱私保證。

延伸閱讀:架構(五種任務型別) · 工具 · CLI 參考

① 進階真實情境 Worked Example:用 Viewer 復盤一次「品質異常」的評審

情境

你的團隊抱怨「OCR 這次審出來的評論特別淺」,主管要你給出證據而不是感覺。你要用 Viewer 從一個 session 裡復盤:模型用了多少工具、走了幾輪、壓縮幾次、過濾刪了什麼、為何某些檔案靜默——把「品質異常」拆成可檢視的數據。

Step 1 — 鎖定目標 session

ocr session list | head -10   # 找「品質異常」的那次

Step 2 — 開 Viewer,逐檔看 main_task 泳道

ocr viewer  # 瀏覽器開 localhost:5483 → 目標 repo → 目標 session

Step 3 — 統計「每檔的工具呼叫次數」

jq -r 'select(.type=="tool_call") | .data.tool' < session.jsonl \
  | sort | uniq -c | sort -rn
6 file_read        ← 讀檔多
3 code_search      ← 搜尋多
1 code_comment     ← 評論少(異常!6 次讀檔只產 1 條評論)

Step 4 — 檢查 filter 與壓縮是否「吃掉」了評論

jq -r 'select(.type=="llm_request") | .data.task' < session.jsonl | sort | uniq -c
# 看 REVIEW_FILTER_TASK 幾次、MEMORY_COMPRESSION_TASK 幾次

Step 5 — 給主管一份「數據化復盤」

session f3a9c2: 6 檔
  每檔平均 9 次工具呼叫(基準 4)
  壓縮 3 次(基準 0-1)→ 大檔 + 長迴圈 → 摘要損失
  filter 移除 4 條評論(基準 0-1)
  → 推論: 檔案過大 + 壓縮頻繁 → 早期發現被摘要掉 → 評論變少變淺

為什麼選這條審查路徑

「品質異常」是抽象抱怨,Viewer + JSONL 把它變成可測量的管線數據。不看泳道,你只能猜「模型變笨了」;看了數據,你會發現多半是壓縮太頻繁或 filter 太嚴格——這兩個才是可調的(--max-tokens、--max-tools)。復盤的目的不是責怪,是定位「該調哪個旋鈕」。

預期產出

根因: 6 檔都是 400+ 行大檔,壓縮 3 次吃掉早期脈絡
修正: 每檔 --max-tokens 從 58,888 → 150,000
復審: 評論 1→5 條,filter 移除降至 1 條

② 深入原理擴充:JSONL 的 append-only 是「審計證據」而非「丟失資料」

為什麼不完整 JSONL 仍然有用

JSONL 每行一個事件,append-only——中斷只是「停在某一行」。Viewer 渲染「已寫入的部分」,已寫入的評論仍然有效。這特性讓 JSONL 變成審計軌跡:任何一次評審的完整經過都可重放——誰、何時、對哪檔、用了哪個工具、回了什麼、產了什麼評論。比「看輸出報告」更接近真相。

「看起來一切正常但其實有隱患」的案例

# 你看到 session 的 main_task 泳道「乾淨地結束」:
#   有工具呼叫 + task_done → 你判定「這檔審得沒問題」

# 但細節藏在一張卡片裡:
#   Tool call: file_read → Error: file not found
#   Response: "I'll proceed with the available context..."
#   → 模型其實讀不到它想讀的檔,只憑 diff 殘缺脈絡硬審

# 泳道看起來「完成」了,實際上是「帶傷完成」

「完成」分兩種:乾淨完成(工具都成功)與帶傷完成(工具失敗但模型繼續)。泳道結尾的 task_done 兩者都會出現。防禦:不只看「有無 task_done」,還要看卡片裡的錯誤徽章與「Response 是否 mention 錯誤」。養成習慣:對「重要檔」檢查它的每張卡片有沒有 error badge。

③ 診斷式疑難排解

症狀可能原因解決方案
某檔 main_task 泳道只有 1 張卡片就結束模型快速判斷沒問題就 task_done(低 Recall 或模型偷懶)對照該檔複雜度;若檔大卻審太快,檢查 prompt/模型
re_location_task 泳道塞滿卡片錨定頻繁失敗檢查 diff 格式;改善 existing_code 唯一性
memory_compression 比預期多大檔/長迴圈觸發多次壓縮調高 --max-tokens;或拆檔審
viewer 連不上 localhost:5483viewer 沒在前台跑,或 port 被占用確認 ocr viewer 在跑;換 --addr :3000
從 LAN 連不上DNS-rebinding 防護阻擋設 OCR_VIEWER_ALLOWED_HOSTS

④ 進階挑戰題

  1. 挑戰一:寫一個 jq 查詢,統計一個 session 裡「有 error badge 的工具呼叫」數量(提示:JSONL 的 tool_call 事件結構)。
  2. 挑戰二:設計一個「review 健康度」指標:用 session JSONL 的數據(工具呼叫數、壓縮次數、filter 移除數、re_location 次數)定義一個「檔案級健康分數」。
  3. 挑戰三:判斷題——「泳道以 task_done 結束 = 這檔審完了」。這句話的漏洞在哪?舉一個「帶傷完成」的具體場景。