昨天量的是記憶那一層,寫入由模型決定、核准閘門預設關閉。同一套自我改進機制在技能這一側有一個背景程序在跑,它會標記、封存、整併它管理範圍內的技能。今天把它跑起來,然後故意封存一個技能再回復,看整條流程留下哪些紀錄。以下依序釐清三件事:它管的範圍到哪裡、一次實跑產生哪些檔案、回復之後的狀態與原本是否相同。
hermes curator status 在還沒跑過任何一輪時的輸出:
curator: ENABLED
runs: 0
last summary: deferred first run — curator seeded, will run after one interval
interval: every 7d
stale after: 30d unused
archive after: 90d unused
consolidate: off (prune-only; LLM merge pass opt-in)
curator-managed skills: 58 total (agent-created=0 bundled=58)
active 58
stale 0
archived 0
組態集中在 curator 這個區段:
| 鍵 | 預設值 | 作用 |
|---|---|---|
interval_hours |
168 | 兩輪之間的間隔,等於 7 天 |
min_idle_hours |
2 | agent 閒置這麼久之後才跑 |
stale_after_days |
30 | 未使用滿這麼多天標為 stale |
archive_after_days |
90 | 未使用滿這麼多天移進 .archive/ |
consolidate |
False |
LLM 整併那一趟預設關閉 |
prune_builtins |
True |
內建技能也納入閒置封存 |
archive_ttl_days |
0 | 封存內容永久保留 |
backup.keep |
5 | 保留最近五份全樹快照 |
consolidate 關閉時一輪只做確定性的閒置修剪,輔助模型維持在旁待命,這一輪的 API 成本是零。註解另外說明封存做的是搬移,真正的刪除一律由明確的指令發動。
同一份檔案裡有三處在講管理範圍:
prune_builtins 的註解:內建技能同樣累積使用遙測,閒置時鐘從 curator 第一次看到它算起,hub 安裝的才永不修剪status 的輸出:58 個內建技能全部列為 curator-managed依預設值,內建技能也在管理範圍內。 hub 安裝的技能有外部的上游擁有者,這一類三處說法一致。
--dry-run 只產生報告,狀態維持原狀:
curator: running DRY-RUN (report only, no mutations)...
curator: dry-run auto: no changes; llm: skipped (consolidation off)
auto (preview): 58 candidate skill(s) — no transitions applied in dry-run
拿掉旗標實際跑一輪,指令總共花 7.8 秒,run.json 記的審查時間是 6.79 秒:
curator: snapshot created (2026-09-22T03-12-56Z)
curator: auto: no changes; llm: skipped (consolidation off)
auto: checked=58 stale=0 archived=0 reactivated=0
一輪留下兩組檔案:
| 檔案 | 位置 | 大小 |
|---|---|---|
skills.tar.gz |
skills/.curator_backups/<UTC 時間戳>/ |
920,012 B |
manifest.json |
同上 | 316 B |
REPORT.md |
logs/curator/<本地時間戳>/ |
827 B |
run.json |
同上 | 896 B |
兩份機器可讀的紀錄各記不同的事:
manifest.json:skill_files: 58、reason: "pre-curator-run",另記 cron 任務的備份狀態run.json:auto_transitions 是 checked: 58 與 seeded: 58,其餘歸零seeded: 58 對應註解說的閒置時鐘從第一次看到算起,因此第一輪做的是登記。
REPORT.md 的標題行寫的是 Agent-created skills: 58 → 58 (+0),status 那一側同一批技能報的是 agent-created=0 bundled=58。同一批技能在兩份輸出裡掛在不同的分類名下。
skills.ledger 預設開啟,每一次技能變動附加一筆 JSONL 到 profile 目錄下的 skills/.curator_ledger.jsonl,檔案內容以 SHA-256 內容定址存進 .curator_backups/blobs/。註解標明它是遙測而非閘門,寫紀錄失敗擋不住變動本身。
手動封存一個內建技能:
$ hermes curator archive gif-search
curator: archived to ...\profiles\<profile 名稱>\skills\.archive\gif-search
$ hermes curator ledger
id when actor action skill
2d9ddd015fc4 1s ago user archive gif-search
actor 欄位區分三種發動者,curator、agent 與 user,這一筆記的是 user。同時間量 prompt 的技能索引:
| 時點 | 索引 byte | 索引裡的技能數 |
|---|---|---|
| 封存前 | 5,076 | 51 |
| 封存後 | 5,011 | 50 |
少掉的 65 B 對得上:這個技能的索引行 64 B,加上一個換行。
單筆回復吃 ledger 的條目 id,執行前先把打算做的事列出來並要求確認:
$ hermes curator rollback 2d9ddd015fc4
Rollback target: ledger entry 2d9ddd015fc4
action: archive
skill: gif-search
actor: user
files: 3
Restore this mutation's before-state? [y/N]
帶 -y 執行之後的回報,把安全網也一起講明:
rolled back entry 2d9ddd015fc4 (archive on 'gif-search'): 2 file(s) restored, 1 removed. Safety entry 426d5e0e171c captured the pre-rollback state
一次封存加一次回復,ledger 累積三筆:
| id | actor | action | 內容 |
|---|---|---|---|
2d9ddd015fc4 |
user | archive | before 兩個路徑、after 一個路徑 |
426d5e0e171c |
agent | pre-rollback | 回復之前的狀態快照 |
18c62e2c0b43 |
agent | rollback | restored: 2、removed: 1 |
回復動作本身也進異動紀錄,而且先為自己留一份 pre-rollback。 三筆紀錄裡的每一個檔案引用都帶同一個 SHA-256,blob 目錄裡只有一個 2,720 B 的物件,內容定址的去重在這裡看得到。
回復完成之後再量一次技能索引:
| 時點 | 索引 byte | 索引裡的技能數 |
|---|---|---|
| 封存前 | 5,076 | 51 |
| 封存後 | 5,011 | 50 |
| 回復後 | 5,167 | 52 |
回復後比封存前多了 91 B,技能多了一個。 磁碟上的狀態有兩處與封存前不同:
media/gif-search/SKILL.md 與 media/gif-search/media/gif-search/SKILL.md 兩個檔案各 2,720 B、內容相同,索引因此把同一個技能名列了兩次.archive/ 底下那個目錄已經沒有檔案,list-archived 仍然報得出這個技能名原因在 ledger 那筆封存紀錄裡看得出來:它的 before 列了兩個路徑,兩個的 SHA-256 相同,其中一個就是那條巢狀路徑。封存前實際量到的是一個檔案、51 個索引條目。
回復動作重現的是異動紀錄裡的 before 狀態,而那份紀錄與封存前量到的狀態不一致
手動刪掉巢狀目錄與空的封存目錄之後,索引回到 5,076 B、51 個條目,list-archived 回報沒有封存的技能。
| 路徑 | 粒度 | 指令 |
|---|---|---|
| 單筆變動回復 | 一次變動涉及的檔案 | curator rollback <entry-id> |
| 全樹快照回復 | 整個技能目錄 | curator rollback --list 挑一份快照 |
| 封存區取回 | 一個技能 | curator restore <name>,或直接 mv |
archive_ttl_days 預設 0,封存內容永久保留,curator purge 是唯一會真正刪除的指令,只在明確下達時執行,並且記進 ledger。
寫入側的兩個閘門預設都是關閉:
skills.write_approval:False,開啟後技能寫入改為暫存待審,/skills pending 列出、/skills diff <id> 看完整差異、/skills approve 或 /skills reject 結案memory.write_approval:False,開啟後前景寫入即時詢問,背景審查分支的寫入改為暫存兩個閘門的設計取向一致:前景寫入當場有人回答,背景審查分支的寫入因此改為暫存待審。
這套異動紀錄的設計比我預期的完整。回復自己也留紀錄、回復之前先快照、刪除要明確下令、封存永久保留,四件事合起來讓每一次變動都有回頭路。blob 用內容定址去重,同一份檔案被三筆紀錄引用只存一次。
真正的收穫是那 91 B。整條流程的每一個指令都回報成功,ledger 三筆紀錄齊全,rollback 說了 restored 2、removed 1,數字全部一致,而回復出來的狀態與原本不同。差異不在流程,在那筆紀錄本身寫進去的 before 狀態。
這件事量得出來的原因是有第二個來源可以對照。技能索引的 byte 數來自 prompt 的組成,走的是異動紀錄之外的另一條路徑。
異動紀錄證明的是流程跑完,至於回到哪一個狀態,要靠紀錄之外的量測
前天那條 write–manage–read 迴圈裡的 manage,在技能這一側有完整的紀錄與三條回復路徑,驗證那一段則要靠紀錄之外的第二個量測。
身分宣告寫在哪裡才具持久性,以及 SOUL.md 與脈絡注入屬於同一套機制的哪個位置。