iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天量的是一個 session 開場時的固定開銷,對話往下走之後,這個數字會一路長到觸發壓縮。以下依序釐清三個問題:session 保存在哪裡、壓縮在哪一個數字上啟動、壓縮之後原始內容去了哪裡。


Session 保存在一個 SQLite 檔案裡

每個 profile 各有一份 state.db,hermes sessions stats 讀的就是它:

$ hermes sessions stats
Total sessions: 224
Total messages: 1402
  cli: 17 sessions
  telegram: 5 sessions
  discord: 4 sessions
Database size: 15.9 MB

這份資料庫裡有 sessions 與 messages 兩張主表,sessions 有 59 個欄位,messages 有 24 個。除此之外還有 FTS5 的全文索引、壓縮鎖、gateway 路由、交付義務等十餘張輔助表。

三個 profile 的規模差距很大:

profile sessions messages 資料庫大小
長期使用的那一個 224 1,402 15.9 MB
用過幾次的 11 138 0.9 MB
剛建立的 1 2 0.3 MB

15.9 MB 這個數字拆開之後,訊息內容只佔其中一小部分:

項目 大小
全文索引 messages_fts_trigram_data 3.78 MB
訊息內容 messages.content 1.24 MB
system prompt 去重表 system_prompts 0.84 MB
全文索引 messages_fts_data 0.59 MB

三連字組(trigram)索引是它所索引內容的三倍大,1.24 MB 的訊息內容對應 3.78 MB 的索引。sessions optimize 合併的就是這一層的片段。

system prompt 走的是雜湊去重。sessions.system_prompt 這個欄位在 224 筆資料列裡全部是 NULL,實際內容存在 system_prompts 表,以 system_prompt_hash 對應:

  • 224 個 session 對應 42 個相異的雜湊
  • 42 份 prompt 合計 884,184 chars,平均每份 21,052 chars
  • 逐列各存一份的話,同樣的內容會膨脹到約 4.7 MB

來源分布與報表涵蓋的範圍

上面那份報表列出三個來源,加起來 26 筆,總數卻是 224 筆。以 --source 逐一查詢,實際分布是這樣:

來源 sessions
cron 187
cli 17
tui 11
telegram 5
discord 4

五項加總正好 224。報表的來源清單是寫死的,hermes_cli/sessions_cmd.py:1443 那一行走訪的是 ["cli", "telegram", "discord", "whatsapp", "slack"],cron 與 tui 在這份清單之外。

同一個指令在 console 介面另有一份實作,hermes_cli/console_engine.py:1378 走訪的是 ["cli", "tui", "telegram", "discord", "slack", "cron"],並且多印一行 Listable sessions。

兩條路徑各自維護一份來源清單,同一個指令因此在兩個介面回報不同的分布

取得完整分布的方式是以 --source 逐項查詢,或直接查資料庫。


壓縮在哪一個數字上啟動

壓縮的組態全部集中在 compression 這個區段,預設值與註解都寫在 hermes_cli/config_defaults.py:

鍵 預設值 作用
threshold 0.50 context 用量超過這個比例時壓縮
threshold_tokens None 絕對 token 上限,設值後取兩者的較低者
target_ratio 0.20 保留為近期尾段的比例
tail_mode lean 尾段保留政策
protect_last_n 20 最少保持未壓縮的近期訊息數
protect_first_n 3 永遠逐字保留的開頭訊息數
min_tail_user_messages 1 保證存活在尾段的真實使用者訊息數
max_attempts 3 一輪之內的壓縮重試次數

threshold 的 0.50 有一條抬升規則,註解寫得很明確:

context window 低於 512K 的模型下限抬到 0.75(只升不降),壓縮才不會在視窗還空著一半時就啟動

這套組態接的是 gemini-3.5-flash-lite,model.context_length 是 131,072,在 512K 以下:

  • 比例:0.75(由下限抬升而來,組態裡寫的 0.50 在這個模型上讓位)
  • 觸發點:131,072 × 0.75 = 98,304 tokens
  • 尾段:lean 模式取視窗的 2.5%,131,072 × 2.5% = 3,277,低於 10K 下限,因此實際保留 10,000 tokens

lean 模式除了尾段之外還會產生分塊摘要、機械式的錨點索引、逐字保留的使用者訊息,以及寫進摘要的 session_search 回復指標。註解標示它比舊的 legacy 模式少留約三分之一的 token,代價是在壓縮邊界多跑幾次摘要呼叫。


壓縮之後原始內容去哪裡

in_place 這個鍵預設是 True,它決定壓縮要不要換一個新的 session id:

  • session id 維持同一個,整段對話一生共用一個 id,壓縮前後的紀錄都掛在它底下
  • 壓縮前的回合以軟封存保留,同一個 id 底下標記成 active=0、compacted=1
  • 軟封存的內容仍可搜尋,session_search 找得到,也回復得了

messages 表確實有 active 與 compacted 這兩個欄位。以唯讀模式開啟 default profile 的 state.db 查詢分組結果:

active  compacted  count
1       0          1402

1,402 則訊息全部是 (1, 0),這份資料庫的壓縮觸發次數是 0。平均每個 session 6.3 則訊息,最長的一個 116 則,距離 98,304 tokens 的觸發點還很遠。

壓縮的組態一直生效,實際跑不跑得到那個數字由對話長度決定


匯出:一個 2 則訊息的 session 佔 21,864 B

hermes sessions export 支援 jsonl、md、qmd、html、trace 五種格式,附帶二十餘個篩選條件。把一個只有 2 則訊息的 session 匯成 JSONL:

$ hermes sessions export --format jsonl --redact --session-id <session id> s.jsonl --yes
Exported 1 session to ...\s.jsonl

結果是一行 JSON,21,864 B。拆開來看:

項目 數量
檔案總大小 21,864 B
system_prompt 欄位 18,749 chars
對話內容(2 則訊息) 8 chars
頂層欄位數 59

匯出會把去重的 system prompt 還原成內聯欄位,資料庫裡那個 NULL 欄位在輸出檔裡帶著完整的 18,749 個字元。model 欄位存的是這個 session 建立當時用的模型,與現在組態裡的那一個不同,換模型並不會回頭改寫已存的資料列。

遮蔽行為依格式而異:

  • trace 格式:強制遮蔽,--no-redact 的說明寫的是「跳過強制的密鑰遮蔽,人工審閱過後才使用」
  • 其餘格式:遮蔽由 --redact 開啟

--lineage 另有 single 與 logical 兩個值,後者匯出的是一筆資料列的整條壓縮譜系,對應上一節的軟封存。


修剪與封存

指令 行為
sessions prune 刪除,無篩選條件時預設 90 天以上
sessions archive 軟隱藏,資料保留
sessions delete 刪除指定的一筆
sessions optimize 合併 FTS5 片段並 VACUUM,資料內容維持原狀

prune 預設會跳過已封存與已釘選的 session,要納入得各自加上 --include-archived 與 --include-pinned。所有篩選條件都支援 --dry-run。

檔案系統那一側另有一套快照,hermes checkpoints 管理的是一個影子 git repo,在 write_file、patch 與 terminal 呼叫之前對工作目錄拍快照:

$ hermes checkpoints status
Checkpoint base: ...\profiles\<profile 名稱>\checkpoints
Total size:      0 B
Projects:        0

這個 profile 目前是 0 B,快照的建立時機限於 agent 對工作目錄的寫入動作。


心得

壓縮這件事在組態裡讀起來像是隨時會發生,量完才知道這台機器上它的觸發次數是 0。

threshold 寫著 0.50,實際生效的是那條抬升規則,128K 的模型被抬到 0.75,觸發點是 98,304 tokens。這份資料庫的 1,402 則訊息、平均每個 session 6.3 則,距離那個數字差了兩個數量級。

動手量之前,預期佔掉 15.9 MB 的是對話內容。拆開之後對話內容是 1.24 MB,最大的一項是全文索引的 3.78 MB。同一份資料存兩次,一次為了讀回來,一次為了搜尋,而搜尋那一份更貴。

儲存成本的分布要拆到表的層級才看得出來,總量只說得出它有多大


明天

工具 schema 的裁剪實測,以及 Hermes 對大量工具的漸進式揭露機制。


上一篇
【Day 12】固定 prompt 預算:量出每一項實際佔多少
系列文
打造具備記憶與執行能力的常駐 AI Agent:Hermes Agent × Gemini × MCP 的 Harness 設計 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言