fix(role): 修好睡眠整理死鎖 —— 素材不再硬切、批次降為 4、錯誤訊息可診斷

角色整晚沒睡:05:03 到 08:33 每 10 分鐘試一次,連續 22 次全部失敗,
last_sleep 停在前一天 15:02,inbox 從 12 累積到 23 則。
錯誤訊息只有一句「整理結果無法套用」,看不出任何原因。

診斷過程中推論錯了三次,過程留在註解裡(每一次都很容易再犯):

1. 推論「輸出被 SLEEP_OUTPUT_LIMIT 截斷」→ 實測輸出僅 5772~6224,遠未達 8000。錯。
2. 推論「素材字元數過大」→ 實測素材 8815(6 則)失敗、8940(3 則)成功,
   字元數幾乎相同。錯。
3. 推論「純粹是則數問題」→ 對一半:另有一種失敗是 CLI 回傳 Execution error。

真相是兩種失敗混在一起,而錯誤訊息把它們蓋成同一句話:
- CLI 偶發 Execution error → 需要重試
- 一次要求模型輸出太多筆 JSON → 需要降批

修正:

- 錯誤訊息附上素材大小、輸出長度、批次與輸出前 200 字元(已 redact)
  —— 這是最先做的一步,沒有它只能靠猜
- cmdCollect 不再 slice() 硬切素材:改為逐則累加、超出預算留到下批,
  並替 EXISTING 保留固定比例預算。舊版會切在記憶中間、甚至切掉整個 EXISTING 區塊,
  而批次固定時每輪都收到同樣殘缺的素材 → 死鎖
- SLEEP_BATCH 12 → 4(實測 3~4 則穩定、6 則以上開始失敗)
- 失敗時逐次降批(4→2→1)作為保險,仍失敗才留到下個週期
- 批次預設值統一來源:role_sleep.sh 首次收集不傳 --batch,實際則數由素材反推
  —— 原本兩邊各寫一個預設,改了 memory.js 的 SLEEP_BATCH 卻不會生效

實測:修正後第一次嘗試即成功,未觸發降批;連續整理清空 inbox(23 → 0),
情緒型態記憶由 1 增至 9,關係史累積 13 則。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-07-30 10:22:23 +08:00
co-authored by Claude Opus 5
parent adc5521302
commit 9512293dc9
6 changed files with 178 additions and 71 deletions
+25
View File
@@ -786,6 +786,31 @@ updated: <yyyy/MM/dd HH:mm:ss>
判準刻意放在 `relevance` 而非型態:並非所有事件與知識都該優待 —— **沒有情感脈絡的一次性工作進度仍照原規則淡去**,被留下的是帶著溫度的那些。因此兩份 prompt 都明訂:只要內容承載情感、關係溫度或當時的心情,`relevance` **必須**含 `emotional``episodic` 與 `semantic` 最容易漏標,漏了就會被當成一般進度處理。
### 整理批次與失敗診斷
**單批則數預設 4**,並在失敗時逐次降批(4→2→1)。這個值被實測修正過三次,過程留在 `memory.js` 的註解裡,因為每一次的錯誤推論都很容易再犯:
| 推論 | 實測 |
| --- | --- |
| 輸出被 `SLEEP_OUTPUT_LIMIT` 截斷 | ❌ 輸出僅 57726224 字元,遠未達 8000 |
| 素材字元數過大 | ❌ 素材 8815(6 則)失敗、8940(3 則)成功 —— 字元數幾乎相同 |
| 純粹是則數問題 | ⚠️ 對一半 —— 另有一種失敗是 CLI 回傳 `Execution error` |
**限制因素是「一次要求模型輸出幾筆結構化 JSON」**,不是素材或輸出的字元量:則數越多,模型在中途寫壞引號或轉義的機率越高,而 JSON 壞一個字元整批就套用失敗。
失敗有兩類,對策不同 —— 舊的錯誤訊息只寫「無法套用」,把兩者蓋成同一句話,導致無法診斷:
| 現象 | 原因 | 對策 |
| --- | --- | --- |
| 輸出極短且為 `Execution error` | CLI 偶發執行錯誤 | 重試(同批次) |
| 輸出是不完整的 JSON | 一次要求輸出太多筆 | 降批 |
因此 `--force`/cron 的失敗訊息會附上**素材大小、輸出長度、批次,以及輸出的前 200 字元**(已 redact)。沒有這一步就只能靠猜。
**素材必須結構完整**`cmdCollect` 逐則累加、超出預算就留到下批,並替 `EXISTING` 區塊保留固定比例的預算。舊版是先組好再 `slice()` 硬切,會切在某則記憶中間、甚至把整個 `EXISTING` 區塊切掉 —— 而批次固定時每輪都收到同樣被截斷的素材,形成**死鎖**(實測連續 22 次失敗、`inbox` 從 12 累積到 23 則、角色整晚沒睡)。
**批次預設值只能有一個來源**`memory.js` 的 `SLEEP_BATCH`)。`role_sleep.sh` 首次收集刻意不傳 `--batch`,實際則數由素材反推 —— 曾經兩邊各寫一個預設,改了 `memory.js` 卻沒生效。
### 繁簡防線(寫檔前的第二道)
兩份 prompt 已明令輸出繁體,但**實測仍出現整則簡體記憶**(連 `summary` 與 `tags` 都是),而且該則的 `sources` 指向另一個專案 —— 不同環境下 CLI 的行為並不一致,光靠 prompt 擋不住。因此 `memory.js` 在寫檔前再擋一次:`cmdWrite`inbox)、`cmdApply`(整理落檔)、`appendBond`(關係史)都會經過。