fix(role): 近期工作記憶注入全文,不再只給一句 summary
重開工作階段後角色仍答不出「上一段進行到哪」:inboxBlock() 的迴圈只解構 了 [meta],content(body)從頭到尾沒被用到,因此 SessionStart 注入的 「近期工作記憶」只有一行 summary。summary 是一句話的標題,只夠讓角色知道 「有這件事」,講不出還差什麼、下一步是什麼 —— 實測角色仍得自己去翻 inbox/ 檔案才答得出內容,交接等於失效。長期記憶的「(全文)」區塊本來就 會注入 body(cmdLoad),inbox 更近、更該給。 預算改成逐則計費:原本是把整段組好再 slice(0, limit) 硬切,放全文後會把 最舊那則砍成半句,讀起來像壞掉的資料。改成超出預算就整則略過,並在結尾 誠實標示略過幾則;最新一則永遠保留,必要時只截它自己的內文。 ROLE_LOAD_INBOX_LIMIT 預設 1200 → 3600。1200 是「只注入 summary 一行」 時代的額度,改注入全文後單則約 300~600 字元,1200 只夠兩則就開始丟最舊 的。3600 約可容納 6~8 則,足以覆蓋一次睡眠整理週期內的工作。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-9
@@ -69,7 +69,9 @@ const CONTENT_LIMIT = 1200;
|
||||
const DEFAULT_LOAD_LIMIT = 4000;
|
||||
const DEFAULT_FULL_MIN_PRIORITY = 4;
|
||||
const DEFAULT_DIGEST_MIN_PRIORITY = 3;
|
||||
const DEFAULT_LOAD_INBOX_LIMIT = 1200;
|
||||
// 1200 是「只注入 summary 一行」時代的額度;改成注入全文後,單則約 300~600 字元,
|
||||
// 1200 只夠兩則就開始丟最舊的。3600 約可容納 6~8 則,足以覆蓋一次睡眠整理週期內的工作。
|
||||
const DEFAULT_LOAD_INBOX_LIMIT = 3600;
|
||||
const DEFAULT_LOAD_INBOX_COUNT = 10;
|
||||
|
||||
function readStdin() {
|
||||
@@ -375,9 +377,13 @@ function listInbox(role) {
|
||||
return items;
|
||||
}
|
||||
|
||||
// 近期工作記憶區塊:SessionStart 載入尚未整理的 inbox 摘要。
|
||||
// 近期工作記憶區塊:SessionStart 載入尚未整理的 inbox 全文。
|
||||
// inbox 是「剛剛發生的事」,但整理(NREM/REM)永遠跑在載入之後(role_load.sh 先 load 再背景 catchup),
|
||||
// 若不在此載入,重開工作階段時角色會看不到上一段工作,表現得像失去記憶。
|
||||
//
|
||||
// 為什麼要注入 body 而不只是 summary:summary 是一句話的標題,只夠讓角色知道「有這件事」,
|
||||
// 講不出「進行到哪、還差什麼、下一步是什麼」——實測重開後角色仍得自己去翻 inbox 檔案才答得出來,
|
||||
// 等於交接失效。長期記憶的「(全文)」區塊本來就會注入 body,inbox 更近、更該給。
|
||||
// 使用獨立字元預算,不佔用長期記憶的 ROLE_LOAD_LIMIT。
|
||||
function inboxBlock(role, count, limit) {
|
||||
if (limit <= 0 || count <= 0) return "";
|
||||
@@ -386,17 +392,34 @@ function inboxBlock(role, count, limit) {
|
||||
// 已過期的臨時授權即使還在 inbox 也不該注入,否則會被當成當下有效的許可
|
||||
const alive = items.filter(([meta]) => !expiryState(meta).expired);
|
||||
const recent = alive.slice(-count).reverse(); // 檔名為時間戳,取最後 N 則後反轉成最新在前
|
||||
const lines = ["### 近期工作記憶(未整理,最新在前)"];
|
||||
for (const [meta] of recent) {
|
||||
const header = "### 近期工作記憶(未整理,最新在前,含全文)";
|
||||
const lines = [header];
|
||||
let used = header.length;
|
||||
let dropped = 0;
|
||||
for (const [meta, content] of recent) {
|
||||
const when = typeof meta.created === "string" && meta.created.length >= 16 ? meta.created.slice(11, 16) : "--:--";
|
||||
const tags = (meta.tags || []).join("、");
|
||||
lines.push(`- ${when} ${meta.summary || "(無總結)"}${tags ? `(${tags})` : ""}`);
|
||||
const entry = [`- ${when} **${meta.summary || "(無總結)"}**${tags ? `(${tags})` : ""}`];
|
||||
for (const line of String(content || "").split(/\r?\n/)) {
|
||||
if (line.trim()) entry.push(` ${line.trim()}`);
|
||||
}
|
||||
let chunk = entry.join("\n");
|
||||
// 逐則計費而非最後整段硬切:整段 slice 會把最舊那則砍成半句,讀起來像壞掉的資料。
|
||||
// 最新一則永遠保留(必要時只截它自己的內文),其餘超出預算就整則略過並在結尾誠實計數。
|
||||
if (used + 1 + chunk.length > limit) {
|
||||
if (lines.length > 1) {
|
||||
dropped += 1;
|
||||
continue;
|
||||
}
|
||||
chunk = `${chunk.slice(0, Math.max(0, limit - used - 1))}…(本則內文已截斷)`;
|
||||
}
|
||||
lines.push(chunk);
|
||||
used += 1 + chunk.length;
|
||||
}
|
||||
let text = lines.join("\n");
|
||||
if (text.length > limit) {
|
||||
text = `${text.slice(0, limit)}\n> (近期工作記憶超過 ${limit} 字元預算已截斷;可用 ROLE_LOAD_INBOX_LIMIT 調整)`;
|
||||
if (dropped) {
|
||||
lines.push(`> (另有 ${dropped} 則較舊的近期記憶超出 ${limit} 字元預算未載入;可用 ROLE_LOAD_INBOX_LIMIT 調整,完整內容仍保存在磁碟)`);
|
||||
}
|
||||
return text;
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function findMemory(role, memoryId) {
|
||||
|
||||
Reference in New Issue
Block a user