iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
AI Engineering

Harness Engineering × Pi Agent 實戰:打造可觀測、可評估的 AI Coding Agent系列 第 6

Day6:Agent 黑箱裡發生了什麼?從 Session 記錄還原八輪決策

  • 分享至 

  • xImage
  •  

文件沒寫的,記錄裡都有

Day4 提過一件事:Pi 的官方文件沒有用文字說明它的 agent loop 怎麼運作。但它有一個更直接的東西——每一次對話都會留下完整的 session 記錄。今天就把這份記錄打開,看 Day2 那個「看現況、想下一步、動手做、看結果」的迴圈,在真實資料裡到底長什麼樣子。

檔案在哪

session 預設存在 Pi 家目錄底下:

<PI_CODING_AGENT_DIR>\sessions\--D--ithome_2026-test--\2026-09-06T15-07-13-313Z_<uuid>.jsonl

資料夾名稱是工作目錄轉換來的,檔名是開始時間加上一個 UUID。如果想放到別的地方,可以用 --session-dir 指定——後面的量測台就是這樣讓每次實驗各存各的。

格式是 JSON Lines:一行一個事件,依發生順序往下寫。

一行一行看

下面是 Day5 那個修 bug 的 session,我把太長的欄位剪掉,保留結構:

{"type":"session","version":3,"id":"01a07742-…","cwd":"D:\\ithome_2026\\test"}
{"type":"model_change","provider":"openai-codex","modelId":"gpt-5.6-luna"}
{"type":"thinking_level_change","thinkingLevel":"medium"}
{"type":"message","message":{"role":"user","content":[{"type":"text","text":"這個專案的測試沒過。請找出原因、修好…"}]}}
{"type":"message","message":{"role":"assistant",
  "content":[{"type":"thinking","thinking":""},
             {"type":"toolCall","name":"bash","arguments":{"command":"ls -la && python -m pytest"}}],
  "usage":{"input":1121,"output":39,"cacheRead":0,"totalTokens":1160,"cost":{"total":0.000271}},
  "stopReason":"toolUse"}}
{"type":"message","message":{"role":"toolResult","toolName":"bash","isError":true,
  "content":[{"type":"text","text":"… 2 failed, 3 passed … Command exited with code 1"}]}}

前三行是「開場資訊」:session 的 ID 和工作目錄、用了哪個模型、thinking level 是多少。之後每一行都是一則訊息,差別在 role

role 是什麼 對應 Day2 的哪一站
user 使用者交代的任務 迴圈的起點
assistant 模型的一次回應,裡面可能有思考、文字、工具呼叫 想下一步、決定動手
toolResult harness 實際執行工具後的結果 看結果

有兩個欄位特別值得記住:

  • stopReasontoolUse 代表模型還要呼叫工具,迴圈繼續;stop 代表模型覺得做完了,迴圈結束。整個 agent loop 何時停下,就寫在這個欄位裡。
  • usage:每一輪模型呼叫用了多少 tokens、花了多少錢。input 是這輪沒命中快取的輸入,cacheRead 是命中 prompt cache 的部分,cost.total 是這一輪的美元成本。

另外,每一行都有 idparentId,所以記錄其實是一棵樹而不是一條線——在互動模式裡回到之前的某一步重新分岔時,不需要另開一個檔案。

還有一個容易誤會的地方:system prompt 不會出現在 session 記錄裡。 所以專案裡的 AGENTS.md 雖然每次都被載入,你在記錄中看不到它被 read。這一點 Day8 講載入順序時還會再提。

一份更長的真實 session:新增一個 API

Day5 的例子只有 5 輪,太短了。下面這份是量測台實際跑出來的記錄:任務是替一個小型 API 專案新增 GET /projects/{project_id}/stats。這個專案規定新增 endpoint 要改三個地方,而且完成前必須跑過 scripts/check.py,這些規定都寫在專案的 AGENTS.md 裡。

我把 8 輪模型呼叫整理成一張表:

模型決定做什麼 這輪輸入(未快取+快取)
1 讀規格測試、registry.pyschemas/__init__.py,列出專案檔案 1,509
2 再讀 6 個檔:routes、services、schema、repository、dispatcher、產生的 model 2,467
3 再讀 6 個檔,包括 scripts/check.py 本身 4,244
4 grep 確認專案裡還有沒有 stats 相關程式碼 6,906
5 新增 schemas/project_stats.py 7,300
6 同一輪改 4 個檔:route、service、schema 匯出、ROUTE_REGISTRY 7,517
7 python scripts/check.py,六項全過 8,338
8 回報完成,stopReason: stop 8,436

總共 23 次工具呼叫,成本約 0.0066 美元,任務成功。

每一輪送給模型的 context 有多大

這份記錄裡有三件事,光看最後的回答是看不出來的:

  1. 大部分工作是「讀」。 前三輪讀了 15 個檔案才開始動手。真正改程式碼只花了兩輪。
  2. context 一路變大。 從第一輪的 1,509 tokens 長到最後一輪的 8,436——這就是 Day2 講的「模型不是記得,而是每一輪被重新提醒一次」,在資料裡看得一清二楚。橘色是命中 prompt cache 的部分;有意思的是第 5 輪命中的快取反而變少了,快取有幫助,但不是保證
  3. 它照著 AGENTS.md 做事。 三個註冊點在第 6 輪一次改齊,完成前主動跑了 check.py。這些行為如果拿掉 AGENTS.md 還會不會發生?那正是 Day9 要量的東西。

從記錄算出數字

有了這份記錄,成本、tokens、工具呼叫次數都不用猜。核心邏輯只有十幾行:

import json
from collections import Counter

tools, cost, tokens = Counter(), 0.0, 0
for line in open(session_path, encoding="utf-8"):
    entry = json.loads(line)
    message = entry.get("message") or {}
    if entry.get("type") != "message" or message.get("role") != "assistant":
        continue
    usage = message.get("usage") or {}
    cost += usage.get("cost", {}).get("total", 0)
    tokens += usage.get("totalTokens", 0)
    for block in message.get("content", []):
        if block.get("type") == "toolCall":
            tools[block["name"]] += 1

print(f"cost=${cost:.4f} tokens={tokens} tools={dict(tools)}")

量測台實際用的版本在 bench/runner/metrics.py,多記了幾件事:工具錯誤次數、傳輸層失敗次數、跑過哪些 bash 指令、讀過哪些文件、有沒有讀 SKILL.md。後面每一個實驗的數字,都是從這份記錄算出來的。

明天

能從一份記錄算出數字,還不等於能做實驗。一次的結果可能只是運氣,要比較「有沒有某個元件」,至少要有:可以重複執行的任務、每次都一模一樣的起點、不靠人工判斷的驗收方式。Day7 會把這些組成一個最小的量測台,而且刻意讓它跟 harness 無關——換成別的 coding agent 也能跑同一組任務。


上一篇
Day5:安裝 Pi Coding Agent ( Windows )
系列文
Harness Engineering × Pi Agent 實戰:打造可觀測、可評估的 AI Coding Agent6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言