- 首頁
- Documentation
- 設定
- Blob 與 artifact 儲存架構
Blob 與 artifact 儲存架構
本文件說明 coding-agent 如何將大型/二進位資料儲存在會話 JSONL 之外、截斷的工具輸出如何持久化,以及內部 URL(artifact://、agent://)如何解析回已儲存的資料。
為何存在兩套儲存系統
Section titled “為何存在兩套儲存系統”執行環境針對不同的資料形態使用兩種不同的持久化機制:
- 內容定址 blob(
blob:sha256:<hash>):全域、以二進位為導向的儲存,用於將大型圖片 base64 資料從持久化的會話條目中外部化。 - 會話範圍的 artifact(
<sessionFile-without-.jsonl>/下的檔案):每個會話的文字檔案,用於完整的工具輸出和子代理輸出。
它們被刻意分開:
- blob 儲存透過內容雜湊優化去重複和穩定引用,
- artifact 儲存透過本地 ID 優化僅附加的會話工具操作和人工/工具檢索。
儲存邊界與磁碟配置
Section titled “儲存邊界與磁碟配置”Blob 儲存邊界(全域)
Section titled “Blob 儲存邊界(全域)”SessionManager 建構 BlobStore(getBlobsDir()),因此 blob 檔案存放在共享的全域 blob 目錄中(不在會話資料夾內)。
Blob 檔案命名:
- 檔案路徑:
<blobsDir>/<sha256-hex> - 無副檔名
- 條目中儲存的引用字串:
blob:sha256:<sha256-hex>
影響:
- 跨會話的相同二進位內容會解析為相同的雜湊/路徑,
- 在內容層級上寫入是冪等的,
- blob 的生命週期可以超過任何單一會話檔案。
Artifact 邊界(會話本地)
Section titled “Artifact 邊界(會話本地)”ArtifactManager 從會話檔案路徑衍生 artifact 目錄:
- 會話檔案:
.../<timestamp>_<sessionId>.jsonl - artifact 目錄:
.../<timestamp>_<sessionId>/(去除.jsonl)
Artifact 類型共用此目錄:
- 截斷的工具輸出檔案:
<numericId>.<toolType>.log(用於artifact://) - 子代理輸出檔案:
<outputId>.md(用於agent://)
ID 與名稱配置方案
Section titled “ID 與名稱配置方案”Blob ID:內容雜湊
Section titled “Blob ID:內容雜湊”BlobStore.put() 對原始二進位位元組計算 SHA-256 並返回:
hash:十六進位摘要,path:<blobsDir>/<hash>,ref:blob:sha256:<hash>。
不使用會話本地計數器。
Artifact ID:會話本地單調遞增整數
Section titled “Artifact ID:會話本地單調遞增整數”ArtifactManager 在首次使用時掃描現有的 *.log artifact 檔案,找到最大的現有數字 ID,並設定 nextId = max + 1。
配置行為:
- 檔案格式:
{id}.{toolType}.log - ID 是連續字串(
"0"、"1"、…) - 恢復會話時不會覆蓋現有 artifact,因為掃描在配置之前進行。
如果 artifact 目錄不存在,掃描會產生空列表,配置從 0 開始。
代理輸出 ID(agent://)
Section titled “代理輸出 ID(agent://)”AgentOutputManager 為子代理輸出配置 ID,格式為 <index>-<requestedId>(可選擇性地巢狀在父級前綴下,例如 0-Parent.1-Child)。它在初始化時掃描現有的 .md 檔案,以便在恢復時從下一個索引繼續。
持久化資料流
Section titled “持久化資料流”1)會話條目持久化重寫路徑
Section titled “1)會話條目持久化重寫路徑”在寫入會話條目之前(#rewriteFile / 增量持久化),SessionManager 呼叫 prepareEntryForPersistence()(透過 truncateForPersistence)。
關鍵行為:
- 大型字串截斷:過大的字串會被裁切並加上後綴
"[Session persistence truncated large content]"。 - 暫態欄位剝離:
partialJson和jsonlEvents從持久化條目中移除。 - 圖片外部化至 blob:
- 僅適用於
content陣列中的圖片區塊, - 僅當
data尚未是 blob 引用時, - 僅當 base64 長度至少達到閾值(
BLOB_EXTERNALIZE_THRESHOLD = 1024)時, - 將內聯 base64 替換為
blob:sha256:<hash>。
- 僅適用於
這使會話 JSONL 保持精簡,同時保留可恢復性。
2)會話載入再水合路徑
Section titled “2)會話載入再水合路徑”開啟會話時(setSessionFile),在遷移之後,SessionManager 執行 resolveBlobRefsInEntries()。
對於每個包含 blob:sha256:<hash> 的 message/custom-message 圖片區塊:
- 從 blob 儲存讀取 blob 位元組,
- 將位元組轉換回 base64,
- 修改記憶體中的條目以內聯 base64 供執行環境消費者使用。
如果 blob 遺失:
resolveImageData()記錄警告,- 返回原始引用字串不變,
- 載入繼續進行(不會硬性崩潰)。
3)工具輸出溢出/截斷路徑
Section titled “3)工具輸出溢出/截斷路徑”OutputSink 為 bash/python/ssh 及相關執行器提供串流輸出功能。
行為:
- 每個區塊都會被清理並附加到記憶體中的尾部緩衝區。
- 當記憶體中的位元組超過溢出閾值(
DEFAULT_MAX_BYTES,50KB)時,sink 標記輸出為已截斷。 - 如果有可用的 artifact 路徑,sink 會開啟檔案寫入器並寫入:
- 現有緩衝內容(一次),
- 所有後續區塊。
- 記憶體緩衝區始終被修剪到尾部視窗以供顯示。
dump()僅在檔案 sink 成功建立時返回包含artifactId的摘要。
實際效果:
- UI/工具返回顯示截斷的尾部,
- 完整輸出保存在 artifact 檔案中,並以
artifact://<id>引用。
如果檔案 sink 建立失敗(I/O 錯誤、路徑遺失等),sink 會靜默回退到僅記憶體截斷;完整輸出不會被持久化。
URL 存取模型
Section titled “URL 存取模型”blob: 引用
Section titled “blob: 引用”blob:sha256:<hash> 是會話條目資料中的持久化引用,不是由路由器處理的內部 URL 方案。解析由 SessionManager 在會話載入時完成。
artifact://<id>
Section titled “artifact://<id>”由 ArtifactProtocolHandler 處理:
- 需要活動的會話 artifact 目錄,
- ID 必須是數字,
- 透過匹配檔名前綴
<id>.來解析, - 從匹配的
.log檔案返回原始文字(text/plain), - 當遺失時,錯誤訊息包含可用 artifact ID 列表。
目錄遺失行為:
- 如果 artifact 目錄不存在,拋出
No artifacts directory found。
agent://<id>
Section titled “agent://<id>”由 AgentProtocolHandler 透過 <artifactsDir>/<id>.md 處理:
- 純粹形式返回 markdown 文字,
/path或?q=形式執行 JSON 擷取,- path 和 query 擷取不能組合使用,
- 如果請求擷取,檔案內容必須能解析為 JSON。
目錄遺失行為:
- 拋出
No artifacts directory found。
輸出遺失行為:
- 拋出
Not found: <id>並列出現有.md檔案中的可用 ID。
Read 工具整合:
read支援非擷取內部 URL 讀取的 offset/limit 分頁,- 當使用
agent://擷取時拒絕offset/limit。
恢復、分叉與移動語義
Section titled “恢復、分叉與移動語義”ArtifactManager在首次配置時掃描現有的{id}.*.log檔案並繼續編號。AgentOutputManager掃描現有的.md輸出 ID 並繼續編號。SessionManager在載入時將 blob 引用再水合為 base64。
SessionManager.fork() 建立具有新會話 ID 和 parentSession 連結的新會話檔案,然後返回舊/新檔案路徑。Artifact 複製由 AgentSession.fork() 處理:
- 嘗試將舊 artifact 目錄遞迴複製到新 artifact 目錄,
- 容忍舊目錄不存在的情況,
- 非 ENOENT 的複製錯誤會記錄為警告,分叉仍然完成。
分叉後的 ID 影響:
- 如果複製成功,新會話中的 artifact 計數器從最大已複製 ID 之後繼續,
- 如果複製失敗/跳過,新會話 artifact ID 從
0開始。
分叉後的 blob 影響:
- blob 是全域且內容定址的,因此不需要複製 blob 目錄。
移動到新的工作目錄
Section titled “移動到新的工作目錄”SessionManager.moveTo() 將會話檔案和 artifact 目錄重新命名到新的預設會話目錄,如果後續步驟失敗則具有回滾邏輯。這在重新定位會話範圍的同時保留 artifact 身份。
故障處理與回退路徑
Section titled “故障處理與回退路徑”| 情況 | 行為 |
|---|---|
| 再水合期間 blob 檔案遺失 | 發出警告並在記憶體中保留 blob:sha256: 引用字串 |
透過 BlobStore.get 的 blob 讀取 ENOENT | 返回 null |
Artifact 目錄遺失(ArtifactManager.listFiles) | 返回空列表(配置可以重新開始) |
Artifact 目錄遺失(artifact:// / agent://) | 拋出明確的 No artifacts directory found |
| Artifact ID 未找到 | 拋出並列出可用 ID |
| OutputSink artifact 寫入器初始化失敗 | 繼續僅尾部截斷(無完整輸出 artifact) |
| 無會話檔案(某些任務路徑) | Task 工具回退到臨時 artifact 目錄用於子代理輸出 |
二進位 blob 外部化 vs 文字輸出 artifact
Section titled “二進位 blob 外部化 vs 文字輸出 artifact”- Blob 外部化用於持久化會話條目內容中的二進位圖片資料;它將 JSONL 中的內聯 base64 替換為穩定的內容引用。
- Artifact 是用於執行輸出和子代理輸出的純文字檔案;它們可透過會話本地 ID 經由內部 URL 定址。
這兩套系統僅間接相關(都減少會話 JSONL 膨脹),但具有不同的身份識別、生命週期和檢索路徑。
src/session/blob-store.ts— blob 引用格式、雜湊、put/get、外部化/解析輔助函式。src/session/artifacts.ts— 會話 artifact 目錄模型和數字 artifact ID 配置。src/session/streaming-output.ts—OutputSink截斷/溢出至檔案行為和摘要中繼資料。src/session/session-manager.ts— 持久化轉換、載入時 blob 再水合、會話分叉/移動互動。src/session/agent-session.ts— 互動式分叉時的 artifact 目錄複製。src/tools/output-utils.ts— 工具 artifact 管理器啟動和每個工具的 artifact 路徑配置。src/internal-urls/artifact-protocol.ts—artifact://解析器。src/internal-urls/agent-protocol.ts—agent://解析器 + JSON 擷取。src/sdk.ts— 內部 URL 路由器接線和 artifact 目錄解析器。src/task/output-manager.ts— 會話範圍的代理輸出 ID 配置,用於agent://。src/task/executor.ts— 子代理輸出 artifact 寫入(<id>.md)和臨時 artifact 目錄回退。