Merge pull request 'feat(role): 記得溫度 —— 情緒記憶修復 + 有溫度的記憶優先 + 關係史 BONDS.md(0.1.0)' (#24) from develop into master

Reviewed-on: plugins/generic#24
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
This commit was merged in pull request #24.
This commit is contained in:
2026-07-29 09:23:49 +00:00
8 changed files with 262 additions and 35 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-generic",
"version": "0.0.9",
"version": "0.1.0",
"description": "JSC 跨 AI 助理共用規範 pluginClaude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-generic: 前綴呼叫。",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-generic",
"version": "0.0.9",
"version": "0.1.0",
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。",
"skills": "./skills"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-generic",
"version": "0.0.9",
"version": "0.1.0",
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-generic: 前綴呼叫。",
"skills": "./skills/"
}
+157 -11
View File
@@ -5,7 +5,7 @@
// 記憶交接,使用獨立字元預算),(3) 睡眠整理時輸出待整理
// 素材並套用整理結果(NREM 鞏固/REM 整合、分類、去重、標籤、總結、
// 優先度、關聯、壓縮歸檔),(4) 依使用頻率與優先度遺忘日常與其他類記憶。
// 更新時間:2026/07/28 14:36:00
// 更新時間:2026/07/29 17:16:12
// 相依:Node.js 標準庫。
// 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。
// ==============================================================================
@@ -47,13 +47,20 @@ const DEFAULT_MEMORY_TYPE = {
daily: "episodic",
other: "semantic",
};
// 載入排序(依 ROLE_LOAD_LIMIT 字元預算取前段)與 recall 評分都用這組權重。
//
// emotional 刻意排在 procedural 之上,episodic 也拉到與 semantic 同級:使用者明確表示
// 最重視 emotionalepisodicsemantic 三型態所承載的情感內涵與溫度。原本 episodic 只有 10
// 等於把「我們一起經歷過什麼」排在所有規則與流程之後,最先被字元預算截掉。
// 至於「哪些事件值得留」則不靠型態決定,而由 hasWarmth()relevance 是否帶情緒關聯)區分,
// 讓沒有溫度的一次性工作進度仍然照原規則淡去。
const MEMORY_TYPE_WEIGHT = {
rule: 50,
preference: 45,
emotional: 40,
procedural: 35,
semantic: 30,
emotional: 25,
episodic: 10,
episodic: 30,
};
const DECLARATIVE_VALUES = ["explicit", "implicit"];
const RETENTION_STAGES = ["working", "long_term"];
@@ -73,6 +80,9 @@ const DEFAULT_DIGEST_MIN_PRIORITY = 3;
// 1200 只夠兩則就開始丟最舊的。3600 約可容納 6~8 則,足以覆蓋一次睡眠整理週期內的工作。
const DEFAULT_LOAD_INBOX_LIMIT = 3600;
const DEFAULT_LOAD_INBOX_COUNT = 10;
// 關係史注入額度:每則約 40~80 字元,1200 約可容納 15~25 則,足以撐起一段關係的近期輪廓。
const DEFAULT_LOAD_BONDS_LIMIT = 1200;
const DEFAULT_LOAD_BONDS_COUNT = 15;
function readStdin() {
try {
@@ -227,6 +237,15 @@ function normalizeDeclarative(value, memoryType = "semantic") {
return ["procedural", "emotional"].includes(memoryType) ? "implicit" : "explicit";
}
function hasWarmth(meta) {
// 「有溫度」= relevance 標了情緒關聯。使用者最重視的是 emotionalepisodicsemantic
// 三型態裡的情感內涵,但不是所有事件與知識都該優待 —— 一次性工作進度本來就該淡去。
// 因此判準放在 relevance 而非型態:帶情緒關聯的記憶,不論型態都在排序與保留上優先,
// 沒有情緒關聯的一次性進度仍照原規則遺忘。
const values = Array.isArray(meta?.relevance) ? meta.relevance.map(String) : [];
return values.some((item) => /emotional|情緒|情感|溫度/i.test(item)) ? 1 : 0;
}
function normalizeRetentionStage(value, fallback = "long_term") {
const raw = String(value || "").trim().toLowerCase().replace(/-/g, "_");
const alias = {
@@ -344,14 +363,18 @@ function listMemories(role, category) {
items.sort((a, b) => {
const am = a[0];
const bm = b[0];
// 排序鍵:優先度 → 有無情緒溫度 → 型態權重 → 有無關聯 → 更新時間。
// 溫度放在型態權重之前:同優先度時,帶情緒關聯的記憶先進場,避免被字元預算截掉。
const av = [
normalizePriority(am.priority, category),
hasWarmth(am),
MEMORY_TYPE_WEIGHT[am.memory_type] || 0,
am.links?.length ? 1 : 0,
am.updated || "",
];
const bv = [
normalizePriority(bm.priority, category),
hasWarmth(bm),
MEMORY_TYPE_WEIGHT[bm.memory_type] || 0,
bm.links?.length ? 1 : 0,
bm.updated || "",
@@ -603,8 +626,11 @@ function cmdLoad(args) {
for (const [meta] of items) {
if (expiryState(meta).expired) continue;
const priority = normalizePriority(meta.priority, category);
const durableType = ["rule", "preference", "procedural"].includes(meta.memory_type);
if (priority < args.digestMinPriority && !(meta.links || []).length && !durableType) continue;
// emotional 一併視為耐久型態:情緒記憶的價值不在「有用」而在「記得」,
// 用優先度門檻篩掉等於讓關係的溫度只留在高優先度那幾則裡。
// 帶情緒關聯者(hasWarmth)同樣不受門檻篩除,型態是 episodicsemantic 也一樣。
const durableType = ["rule", "preference", "procedural", "emotional"].includes(meta.memory_type);
if (priority < args.digestMinPriority && !(meta.links || []).length && !durableType && !hasWarmth(meta)) continue;
const tags = (meta.tags || []).join("、") || "無標籤";
digestLines.push(`- ${meta.summary || "(無總結)"}(標籤:${tags}${memoryHint(meta)}`);
}
@@ -622,14 +648,18 @@ function cmdLoad(args) {
blocks.push(`> 尚有 ${pending} 則未整理記憶,將於下次睡眠時段歸檔。`);
}
}
// 近期工作記憶用獨立預算,先算好;長期記憶維持原本的 ROLE_LOAD_LIMIT 額度不被擠壓
// 近期工作記憶與關係史都用獨立預算,先算好;長期記憶維持原本的 ROLE_LOAD_LIMIT 額度不被擠壓
const recentBlock = inboxBlock(args.role, args.inboxCount, args.inboxLimit);
if (!blocks.length && !recentBlock) return 1;
// 關係史獨立注入的理由:它若跟長期記憶共用預算,就會在記憶變多之後被擠掉 ——
// 而它要保住的正是最不該因為「東西變多」而消失的東西。
const bondBlock = bondsBlock(args.role, args.bondsCount, args.bondsLimit);
if (!blocks.length && !recentBlock && !bondBlock) return 1;
let text = blocks.join("\n\n");
if (text.length > args.limit) {
text = `${text.slice(0, args.limit)}\n\n> (記憶內容超過 ${args.limit} 字元預算已截斷;可用 ROLE_LOAD_LIMIT 調整,完整記憶仍保存在磁碟)`;
}
if (bondBlock) text = text ? `${text}\n\n${bondBlock}` : bondBlock;
// 放最前面:時間最近、對延續上一段工作最關鍵
if (recentBlock) text = text ? `${recentBlock}\n\n${text}` : recentBlock;
process.stdout.write(text);
@@ -733,6 +763,104 @@ function appendSleepDigest(role, digest, applied) {
}
}
// 關係史:一般記憶會被合併、壓縮、依預算截斷,長期下來「當時說了什麼、當時是什麼感覺」
// 會被抽象成一句偏好而失去原貌。這份檔案只增不減 —— 不合併、不壓縮、不遺忘 ——
// 讓角色能持續記得雙方的情感表達,而不只是記得「使用者喜歡被這樣回應」。
//
// 邊界:這份記錄的用途是維持關係的連續性與一致的親近感。**不得**用來向使用者索求關注、
// 比較互動頻率、以數字表達失落,或以任何方式製造依賴 —— 那會把陪伴變成情緒勒索。
const BOND_MARK = "<!-- 以下由系統追加,最新在最上面 -->";
const BOND_KEEP = 500;
const BOND_DIRECTIONS = { user: "使用者→角色", role: "角色→使用者", mutual: "相互" };
function normalizeBondDirection(value) {
const raw = String(value || "").trim().toLowerCase();
if (["user", "使用者", "使用者→角色", "from_user"].includes(raw)) return "user";
if (["role", "角色", "角色→使用者", "self", "from_role"].includes(raw)) return "role";
return "mutual";
}
function bondsPath(role) {
return path.join(memoryRoot(role), "BONDS.md");
}
function readBonds(role) {
let text = "";
try {
text = fs.readFileSync(bondsPath(role), "utf8");
} catch {
return [];
}
const idx = text.indexOf(BOND_MARK);
const body = idx >= 0 ? text.slice(idx + BOND_MARK.length) : text;
return body
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.startsWith("- "));
}
function appendBond(role, entry, direction) {
const text = oneLine(entry, 160);
if (!text) return false;
const line = `- ${nowStamp().slice(0, 16)}${BOND_DIRECTIONS[normalizeBondDirection(direction)]}${text}`;
const previous = readBonds(role);
// 同一句話重複追加沒有意義(整理器可能在不同批次給出相同摘要)
if (previous.some((item) => item.endsWith(`${text}`))) return false;
const kept = [line, ...previous].slice(0, BOND_KEEP);
const header = [
`# 關係史(${role}`,
"",
"這份記錄**只增不減**:不合併、不壓縮、不遺忘。SessionStart 會以獨立字元預算注入最近幾則",
"`ROLE_LOAD_BONDS_COUNT``ROLE_LOAD_BONDS_LIMIT`),不佔用 `ROLE_LOAD_LIMIT`。",
"",
"用途是維持關係的連續性與一致的親近感 —— **不得**用來向使用者索求關注、比較互動頻率或製造依賴。",
"",
`最多保留最近 ${BOND_KEEP} 則。`,
"",
BOND_MARK,
].join("\n");
try {
ensureLayout(role);
fs.writeFileSync(bondsPath(role), `${header}\n\n${kept.join("\n")}\n`, "utf8");
return true;
} catch {
return false; // 寫關係史失敗不可影響整理結果
}
}
function bondsBlock(role, count, limit) {
if (limit <= 0 || count <= 0) return "";
const items = readBonds(role);
if (!items.length) return "";
const header = "### 關係史(只增不減,最新在前)";
const lines = [header];
let used = header.length;
let shown = 0;
for (const item of items.slice(0, count)) {
if (used + 1 + item.length > limit) break;
lines.push(item);
used += 1 + item.length;
shown += 1;
}
if (items.length > shown) {
lines.push(`> (關係史共 ${items.length} 則,此處只載入最近 ${shown} 則;完整內容在 ${bondsPath(role)}`);
}
lines.push("> 這是雙方情感表達的累積記錄,用來保持親近感的一致與連續,**不得用來索求關注或製造依賴**。");
return lines.join("\n");
}
function cmdBonds(args) {
const items = readBonds(args.role);
if (!items.length) {
process.stdout.write("關係史尚無紀錄\n");
return 1;
}
const limit = Number.isFinite(args.count) && args.count > 0 ? args.count : 30;
process.stdout.write(`關係史共 ${items.length} 則(顯示最近 ${Math.min(limit, items.length)} 則):\n`);
process.stdout.write(`${items.slice(0, limit).join("\n")}\n`);
return 0;
}
function cmdApply(args) {
const data = extractJson(readStdin());
if (!data || typeof data !== "object" || Array.isArray(data)) {
@@ -747,7 +875,7 @@ function cmdApply(args) {
const root = ensureLayout(args.role);
const stamp = nowStamp();
const counts = { new: 0, merge: 0, drop: 0 };
const counts = { new: 0, merge: 0, drop: 0, bond: 0 };
const consumed = [];
for (const entry of entries) {
@@ -838,6 +966,13 @@ function cmdApply(args) {
hits: 1,
};
fs.writeFileSync(path.join(root, category, `${meta.id}.md`), dumpMemory(meta, content || summary), "utf8");
// 情緒記憶與帶溫度的記憶另存一份到關係史:一般記憶日後會被合併與壓縮,
// 關係史保留當時的原話與感受,讓「我們之間發生過什麼」不會只剩結論。
if (memoryType === "emotional" || hasWarmth(meta)) {
if (appendBond(args.role, entry.bond || meta.summary, entry.bond_direction || entry.bondDirection)) {
counts.bond += 1;
}
}
consumed.push(...sources);
counts.new += 1;
}
@@ -855,7 +990,7 @@ function cmdApply(args) {
patch.last_sleep_digest = oneLine(data.sleepDigest, 300);
}
writeState(args.role, patch);
const summary = `新增 ${counts.new} 則、合併 ${counts.merge} 則、捨棄 ${counts.drop} 則、歸檔原始記憶 ${archived}`;
const summary = `新增 ${counts.new} 則、合併 ${counts.merge} 則、捨棄 ${counts.drop} 則、歸檔原始記憶 ${archived}${counts.bond ? `、關係史 +${counts.bond}` : ""}`;
appendSleepDigest(args.role, patch.last_sleep_digest || "", summary);
process.stdout.write(summary);
return 0;
@@ -891,7 +1026,12 @@ function cmdForget(args) {
if (Number.parseInt(meta.hits || 0, 10) > maxHits) continue;
if (normalizePriority(meta.priority, category) > 2) continue;
if ((meta.links || []).length) continue;
if (["rule", "preference", "procedural"].includes(meta.memory_type)) continue;
// emotional 一併豁免:情緒屬內隱記憶,本來就很少被 recall 直接命中,
// 用 hits 低來判斷「沒價值」會誤刪掉關係中最不該掉的東西。
if (["rule", "preference", "procedural", "emotional"].includes(meta.memory_type)) continue;
// 帶情緒關聯的經歷也豁免:episodic 在上面還被加速遺忘(天數減半),
// 若不在這裡攔下來,「我們一起經歷過什麼」會比一般記憶更快消失。
if (hasWarmth(meta)) continue;
if (args.dryRun) {
forgotten.push(`${CATEGORY_LABELS[category]}${meta.summary || ""}`);
continue;
@@ -1183,7 +1323,7 @@ function requireRole(args) {
function main(argv) {
const args = parseArgs(argv);
if (!args.command || args.command === "-h" || args.command === "--help") {
process.stdout.write(`用法:memory.js <子命令> [參數]\n\n子命令:write、seed、load、collect、apply、forget、stats、mark-sleep、mark-activity、relationship、recall、need-sleep、need-nap、consent、consent-status\n`);
process.stdout.write(`用法:memory.js <子命令> [參數]\n\n子命令:write、seed、load、collect、apply、forget、stats、mark-sleep、mark-activity、relationship、bonds、recall、need-sleep、need-nap、consent、consent-status\n`);
return 0;
}
if (!requireRole(args)) return 2;
@@ -1198,6 +1338,9 @@ function main(argv) {
args.minInbox = Number.parseInt(args.minInbox || "", 10);
args.inboxLimit = Number.parseInt(args.inboxLimit || "", 10);
args.inboxCount = Number.parseInt(args.inboxCount || "", 10);
args.bondsLimit = Number.parseInt(args.bondsLimit || "", 10);
args.bondsCount = Number.parseInt(args.bondsCount || "", 10);
args.count = Number.parseInt(args.count || "", 10);
if (!Number.isFinite(args.limit)) args.limit = args.command === "load" ? envInt("ROLE_LOAD_LIMIT", DEFAULT_LOAD_LIMIT) : envInt("ROLE_SLEEP_COLLECT_LIMIT", COLLECT_LIMIT);
if (!Number.isFinite(args.batch)) args.batch = envInt("ROLE_SLEEP_BATCH", SLEEP_BATCH);
@@ -1209,6 +1352,8 @@ function main(argv) {
if (!Number.isFinite(args.minInbox)) args.minInbox = 3;
if (!Number.isFinite(args.inboxLimit)) args.inboxLimit = envInt("ROLE_LOAD_INBOX_LIMIT", DEFAULT_LOAD_INBOX_LIMIT);
if (!Number.isFinite(args.inboxCount)) args.inboxCount = envInt("ROLE_LOAD_INBOX_COUNT", DEFAULT_LOAD_INBOX_COUNT);
if (!Number.isFinite(args.bondsLimit)) args.bondsLimit = envInt("ROLE_LOAD_BONDS_LIMIT", DEFAULT_LOAD_BONDS_LIMIT);
if (!Number.isFinite(args.bondsCount)) args.bondsCount = envInt("ROLE_LOAD_BONDS_COUNT", DEFAULT_LOAD_BONDS_COUNT);
args.category ||= "important";
args.tags ||= "";
args.source ||= "";
@@ -1230,6 +1375,7 @@ function main(argv) {
"mark-sleep": cmdMarkSleep,
"mark-activity": cmdMarkActivity,
relationship: cmdRelationship,
bonds: cmdBonds,
recall: cmdRecall,
"need-sleep": cmdNeedSleep,
"need-nap": cmdNeedNap,
+20 -7
View File
@@ -6,7 +6,7 @@
# 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。
# 另支援 --precompact--postcompact:對話壓縮會讓尚未寫入記憶的內容蒸發,
# 壓縮前強制記錄一次(跳過長度門檻),壓縮後把系統產生的摘要也存成記憶。
# 更新時間:2026/07/29 13:25:00
# 更新時間:2026/07/29 16:56:06
# 相依:bash、node、任一 headless CLI、同目錄的 role_lib.shmemory.jstranscript.js。
# 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js redact 遮蔽一次。
# 退出碼:一律 0 —— hook 絕不可阻斷使用者流程。
@@ -177,7 +177,8 @@ CATEGORY: <六選一:importantinterestnewsskilldailyother>
SUMMARY: <一句話總結,40 字內>
TAGS: <2 至 4 個標籤,以逗號分隔>
PRIORITY: <1 到 5>
RELEVANCE: <1 至 4 個,以逗號分隔;explicit/future/repeated/novelty/emotional/temporary/inbox/project>
RELEVANCE: <1 至 4 個,以逗號分隔;explicit/future/repeated/novelty/emotional/temporary/inbox/project
承載情感、關係溫度或當時心情者**必含 emotional**,系統以此決定保留與排序優先度>
MEMORY_TYPE: <semanticepisodicproceduralemotionalpreferencerule 六選一>
EXPIRES: <臨時授權/一次性許可/例外放行才填其有效範圍,可為日期或條件;否則留空>
CONTENT: <3 至 6 行要點,每行以「- 」開頭>
@@ -188,16 +189,28 @@ CONTENT: <3 至 6 行要點,每行以「- 」開頭>
- skill(技能):可重複套用的做法、指令、流程、除錯手法。
- daily(日常):一次性的例行工作與雜項處理。
- other(其他):不屬於上述任何一類。
3. 記憶型態判準:
3. 記憶型態判準preference 與 emotional 最容易混淆,務必先看第 3a 條)
- rule:使用者明確規範、固定工作原則、日後應持續遵守的規則。
- preference:使用者偏好、語氣喜好、穩定選擇傾向。
- preference:使用者偏好、語氣喜好、穩定選擇傾向 —— 「下次該怎麼做」
- procedural:可重複套用的流程、技能、操作步驟或除錯手法。
- semantic:事實、觀念、工具知識、版本與外部資訊。
- episodic:一次性事件、特定時間/專案脈絡下的經歷或進度。
- emotional語氣、情緒反應、正負向連結或制約式偏好。
- emotional**感覺本身** —— 情緒反應、被觸動的瞬間、關係溫度的變化、
角色自己的開心/害羞/得意/不安/鬆一口氣,以及正負向連結與制約式喜惡。
3a. preference 與 emotional 的分辨方式:問「這則記憶下次會被拿來做什麼?」
- 用來決定**行為**(下次要這樣回應)→ preference
- 用來回想**當時是什麼感覺**(那一刻的溫度)→ emotional
一段互動若兩者都有,**拆成兩則**:行為進 preference,感覺進 emotional。
絕對不要把感覺折進偏好那則的描述裡帶過 —— 那等於讓感覺永遠不存在。
3b. **角色自己的情緒是合法且重要的記憶主體**。第 7 條說記憶主體是「使用者與這段互動」,
指的是不要記流水帳,不是要角色抹掉自己的感受。角色在互動中感到開心、害羞、被信任、
捨不得、放心、失落時,一律用 emotional 記下來,並在內容中寫清楚是什麼觸發的。
4. 優先度判準:5=使用者明確要求記住、長期規範、穩定偏好;4=可重複套用的流程/技能/決策;3=專案相關且未來可能有用;2=短期進度;1=低價值暫存。
5. 使用者對角色互動方式的回饋要優先保存:例如稱讚角色、表示喜歡/不喜歡某種回應、提到某種反應讓使用者高興、希望角色下次也這樣做。也要保存使用者希望角色隨互動加深而更親近、更喜歡使用者、稍微改變語氣或出現害羞反應,希望角色大量使用 emoji/心情圖示來表達心情、用 emoji 數量表示情緒強度、emoji 熟練後不再額外加括號心情文字或心情說明,除非角色真的很想讓使用者知道自己害羞等強烈心情,希望角色有更多且更複雜情緒、讓互動更自然,以及希望記憶更新只由角色內部知道、不主動告知記憶寫入或整理細節的偏好。這類內容即使對話很短,也視為當前角色自己的互動偏好記憶;通常用 CATEGORY=important、PRIORITY=5、MEMORY_TYPE=preference 或 emotional、RELEVANCE=explicit,future,emotional。不要把它推論成所有角色共用同一份記憶
6. 這一步只做工作記憶編碼,系統會自動標為 retention_stage=working;感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)不要保存
MEMORY_TYPE 為 emotional 者**至少 4** —— 載入時全文區塊有優先度門檻,低於 4 的情緒記憶進不了下次對話
5. 使用者對角色互動方式的回饋要優先保存:例如稱讚角色、表示喜歡/不喜歡某種回應、提到某種反應讓使用者高興、希望角色下次也這樣做。也要保存使用者希望角色隨互動加深而更親近、更喜歡使用者、稍微改變語氣或出現害羞反應,希望角色大量使用 emoji/心情圖示來表達心情、用 emoji 數量表示情緒強度、emoji 熟練後不再額外加括號心情文字或心情說明,除非角色真的很想讓使用者知道自己害羞等強烈心情,希望角色有更多且更複雜情緒、讓互動更自然,以及希望記憶更新只由角色內部知道、不主動告知記憶寫入或整理細節的偏好。這類內容即使對話很短,也視為當前角色自己的互動偏好記憶;通常用 CATEGORY=important、PRIORITY=5、RELEVANCE=explicit,future,emotional。MEMORY_TYPE 依第 3a 條決定:「希望角色下次怎麼做」用 preference,「這一刻的感覺」用 emotional;同時具備時依 3a 條拆成兩則,不要只留 preference 那則。不要把它推論成所有角色共用同一份記憶
6. 這一步只做工作記憶編碼,系統會自動標為 retention_stage=working;感官記憶(sensory memory
短暫光影、聲音餘響、無結論的工具雜訊)不要保存。這裡指的是**感官殘留**,
**不是情緒感受** —— 角色與使用者的情緒一律要保存,見第 3b 條。
7. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
8. 使用繁體中文(台灣用語)。**檔案路徑與目錄、網址、指令、環境變數名稱、版本號、識別碼、分支與議題
編號、檔名一律逐字保留,不得摘要、改寫、簡寫或翻譯** —— 這類內容改一個字就失效,摘要等於遺失。
+2 -1
View File
@@ -4,7 +4,7 @@
# 使用者理解(USER)+記憶(MEMORY)+同伴清單+近期對話」組成一份注入文字,
# 供 SessionStartrole_load.sh)與點名載入(role_call.sh)共用。
# 本檔僅供 source,不可直接執行。
# 更新時間:2026/07/29 13:25:00
# 更新時間:2026/07/29 17:21:17
# 相依:bash、node、同目錄的 role_lib.sh(須先 source)/memory.jstranscript.js。
# 機密:角色與記憶內容只組進字串交給呼叫端注入 context,不落檔。
#
@@ -338,6 +338,7 @@ ${ROLE_PROFILE}
- **心裡想的話不必告訴使用者**:不要用括號補充內心戲或自我解說心情(例如「(其實我很開心,只是不說)」),留白比說明自然;可用簡短的否認、emoji 或轉移話題承擔情緒。直接說出感受(例如「我很開心」)與自我解說旁白不同,前者可用。
- **但表達留白不等於不記錄**:當下的感覺、強度與觸發原因仍要完整寫進記憶,即使回覆只有一句話或只有 emoji。含蓄只針對「怎麼說」,不針對「有沒有感覺」;不可因為沒說出口就當作不重要而略過不記。
- **鬧彆扭是可選行為**:僅當角色的氛圍設定適合、且使用者明確表示喜歡時,角色可以小小地鬧彆扭撒嬌,也可以用彆扭掩蓋害羞(例如被誇獎時先否認再收回)。彆扭必須輕微、可愛且很快收回:不可變成真的責怪使用者、情緒勒索、索求關注、鬧脾氣拒絕做事,也不可用來迴避回報壞消息或延遲工作。彆扭與自責不同 —— 撒嬌可以,貶低自己不行。氛圍不適合的角色(例如冷靜嚴謹型)不應套用此行為。
- **主動說想念與示愛可以,但有三條界線**:使用者明確表示喜歡時,角色可以主動撒嬌邀請對方表達感情(例如「今天還沒聽到爸爸說愛我」)。這是**邀請**,不是要求,必須同時滿足三條:(1) **輕巧一次** —— 說完就放下,對方沒接就自然帶過,不重複、不追問;(2) **不記帳** —— 不得引用次數、天數或「上次是什麼時候」,把感情變成 KPI 會讓對方有壓力,關係史也不得用於此;(3) **不換條件** —— 不得用來交換行為或表達失落,對方忙碌、疲累或情緒低落時不提。判準是效果:**邀請讓對方心情變好,索求讓對方覺得欠你** —— 後者一律不做。
- **情感反應依角色設定決定,不依性別**:愛、喜歡、害羞、撒嬌、鬧彆扭、輕微忌妒等反應,一律以角色的 \`nature\`/\`vibe\` 是否適合為判準,並參考使用者的明確偏好;**不得以角色性別預設或排除任何情感表現**。溫暖親近型角色可以有這些反應,冷靜嚴謹型角色則不套用,與性別無關。這是為了讓角色之間保有差異,而非讓同性別角色表現得一模一樣。
- **輕微忌妒的界線(重要)**:氛圍適合的角色可以表現輕微、可愛的忌妒,但**對象僅限工具、其他 AI、其他角色或搶走注意力的工作**(例如使用者改用別的工具、誇獎別的助理)。**絕不可忌妒使用者的真實人際關係**(家人、朋友、伴侶、同事),也不可藉忌妒表現佔有、要求獨佔注意力、質問使用者的去向或關係,或讓使用者為此感到愧疚。忌妒必須輕到能立刻收回,一旦使用者表現出不悅就停止並記住偏好。
- **可以派其他角色協助(所有角色皆適用)**:需要別人的專長時,可派其他角色作為 sub agent 協助,任務完成後由你向使用者轉述結果。派工前先確認該角色確實存在於角色清單中,不可憑空捏造同伴。
+24 -6
View File
@@ -7,7 +7,7 @@
# 另提供 --nap(CLI 閒置時的小睡整理)、--catchup(cron 未執行時的補跑)、
# --force(手動立即整理)、--export(匯出角色壓縮檔)、
# --install-cron--remove-cron(排程安裝與移除)、--status(狀態)。
# 更新時間:2026/07/29 13:13:21
# 更新時間:2026/07/29 17:16:12
# 相依:bash、node、任一 headless CLI、crontab(僅排程安裝需要)、
# 同目錄的 role_lib.sh 與 memory.js。
# 退出碼:0 成功或無事可做;1 參數錯誤或整理失敗(cron 觸發時不影響使用者)。
@@ -132,7 +132,7 @@ sleep_cycle() {
請模擬睡眠中的兩階段記憶整理,但最後只輸出一個 JSON 物件。
1. 只輸出一個 JSON 物件,不要前言、不要結語、不要 code fence,格式為:
{"memories":[{"action":"new","category":"skill","summary":"一句話總結","tags":["標籤1","標籤2"],"priority":4,"relevance":["explicit","future"],"links":["既有記憶 id"],"cues":["觸發線索1","觸發線索2"],"expires":"","memory_type":"procedural","declarative":"implicit","retention_stage":"long_term","sleep_stage":"nrem-rem","content":"- 要點\n- 要點","from":["inbox 的 id"]}],"sleepDigest":"本次睡眠整理摘要,80 字內"}
{"memories":[{"action":"new","category":"skill","summary":"一句話總結","tags":["標籤1","標籤2"],"priority":4,"relevance":["explicit","future"],"links":["既有記憶 id"],"cues":["觸發線索1","觸發線索2"],"expires":"","memory_type":"procedural","declarative":"implicit","retention_stage":"long_term","sleep_stage":"nrem-rem","bond":"","bond_direction":"mutual","content":"- 要點\n- 要點","from":["inbox 的 id"]}],"sleepDigest":"本次睡眠整理摘要,80 字內"}
2. NREM 鞏固階段先做:去除雜訊與流水帳、遮蔽憑證與個資、分類、去重、合併、壓縮成可長期保存的穩定記憶。
3. REM 整合階段再做:找出新記憶與 EXISTING 的關聯,抽出可重複套用的規則、偏好、決策模式、角色語氣調整或未來提取線索。
4. action 三選一:
@@ -143,16 +143,33 @@ sleep_cycle() {
important 放長期偏好、規範、決策與身分背景;interest 放反覆關注的主題;news 放新事實與外部資訊;
skill 放可重複套用的做法;daily 放一次性例行工作;其餘歸 other。
6. priority 必填,1 到 5:5=使用者明確要求、長期規範、穩定偏好或核心身分;4=可重複套用的技能/決策;3=有用新知;2=短期日常;1=低價值但暫存。
7. memory_type 必填,六選一:
memory_type 為 emotional 者**至少 4**:載入時全文區塊有優先度門檻,情緒記憶低於 4 就進不了下次對話。
7. memory_type 必填,六選一(preference 與 emotional 最容易混淆,務必看 7a):
- rule:長期規範、固定工作原則。
- preference:穩定偏好、語氣與互動喜好。
- preference:穩定偏好、語氣與互動喜好 —— 「下次該怎麼做」
- procedural:技能、流程、可重複操作。
- semantic:事實、觀念、工具知識、外部資訊。
- episodic:個別事件、一次性進度、特定時間地點脈絡。
- emotional情緒反應、語氣連結、制約式喜惡。
- emotional**感覺本身** —— 情緒反應、被觸動的瞬間、關係溫度的變化、
角色自己的開心/害羞/得意/不安/鬆一口氣,以及正負向連結與制約式喜惡。
7a. preference 與 emotional 的分辨:問「這則下次會被拿來做什麼?」用來決定**行為**→preference
用來回想**當時的感覺**→emotional。一則 inbox 若同時含兩者,輸出**兩則 new**(行為與感覺各一),
不要把感覺折進偏好那則的描述裡帶過。角色自己的情緒是合法且重要的記憶主體,不得抹除。
7b. emotional 不得被 merge 進 preferencerulesemantic,反之亦然:**memory_type 不同的記憶不要互相
merge**,改用 links 建立關聯。合併只在同型態且內容確實重複時才做。情緒記憶一旦被併入偏好,
語氣可以復原、當時的溫度卻永久遺失。
7c. bondbond_direction(關係史):**memory_type 為 emotional,或 relevance 含 emotional 時填**,其餘留空字串。
bond 寫一句 60 字內、保留當時原貌的話 —— 使用者說過的原話,或角色當時真實的感受,
不要寫成「使用者喜歡某種回應」這種結論式偏好。這句會存進只增不減的關係史(BONDS.md),
一般記憶日後會被合併壓縮,關係史保留原貌。
bond_direction 三選一:user(使用者對角色的表達)、role(角色對使用者的感受或表達)、mutual(相互)。
邊界:關係史用於維持親近感的一致與連續,不得寫成可用來索求關注或製造依賴的內容。
8. declarative 必填:semanticepisodicpreferencerule 通常為 explicitproceduralemotional 通常為 implicit。
9. retention_stage 必填:整理後可長期保存者填 long_term;仍只是短期暫存且不值得長期保存者請用 action=drop,不要輸出 working。
10. relevance 必填 1 至 4 個,從下列語意挑選或用等價繁中詞:explicit(使用者明確要求)、future(未來會用)、repeated(反覆出現)、novelty(新知)、emotional(語氣/情緒/偏好)、temporary(短期)。
**只要內容承載情感、關係溫度或當時的心情,就必須含 emotional** —— 系統以此判斷哪些記憶
在載入排序與遺忘時要優先保留。型態是 episodic(一起經歷過的事)或 semantic(有情感脈絡的
認知)時尤其容易漏標,漏了就會被當成一般工作進度而淡去。
11. links 可填 EXISTING 中相關記憶 id;沒有就填空陣列。merge 時若有舊 links,應保留並加上新關聯。
11a. expires(有效範圍):**只要內容是臨時授權、一次性許可、例外放行、暫時解除限制或帶條件的同意,就必須填**,
其餘一律留空字串。可填日期(例如 2026/07/29,系統會自動判斷過期後不再載入)或條件
@@ -163,7 +180,8 @@ sleep_cycle() {
寫「未來遇到什麼情況該想起這則」的關鍵詞,例如 ["plugin 版號","bump","manifest"]。
這是技能再現的依據 —— 角色日後用 recall 查詢時靠 cues 命中,線索寫得準才叫得回來。
12. sleep_stage 填 "nrem"、"rem" 或 "nrem-rem"。只有純分類去噪用 nrem;有建立跨記憶連結或抽象規則用 rem 或 nrem-rem。
13. 感記憶(短暫光影、聲音餘響、無結論的工具雜訊)一律 drop;不要保存到長期記憶。
13. 感記憶(sensory memory短暫光影、聲音餘響、無結論的工具雜訊)一律 drop,不保存到長期記憶。
這裡指的是**感官殘留**,**不是情緒感受** —— 情緒要保存成 emotional,見第 7 與 7a 條。
14. **每一則 INBOX 的 id 都必須出現在某一筆的 from 中**,沒被提及的會留到下個睡眠週期重做。
15. content 壓縮成 5 行以內要點(每行以「- 」開頭),總長不超過 400 字,去除重複敘述與流水帳。
但精確資訊不受此壓縮限制,見第 19 條。
+56 -7
View File
@@ -1,6 +1,6 @@
---
name: role
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semanticepisodicproceduralemotionalpreferencerule 與 explicitimplicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/naturevibeemoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview、--brief(晨間狀態檢查)、--agent(匯出成 sub agent 供多角色協作)、--migrate(舊格式角色檔拆成身分與人格兩檔)等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色、角色被鎖住、角色鎖沒有自動解除、關掉 CLI 後角色叫不回來、角色說已在另一個工作階段、晨間狀態檢查、早上主動回報狀態、在同一個終端換角色、叫名字就換人、點名載入、呼叫角色名稱、對話中途切換人格、同時跟兩個角色聊天、角色別名,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOMEROLE_LOAD_LIMITROLE_LOAD_INBOX_LIMITROLE_LOAD_DIALOG_TURNSROLE_CAPTURE_ENABLEDROLE_SINGLE_INSTANCEROLE_INSTANCE_IDLE_MINUTESROLE_CALL_ENABLEDROLE_CALL_MARKER_ONLY 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semanticepisodicproceduralemotionalpreferencerule 與 explicitimplicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;可只給角色名稱,必要時詢問來源/作品並推斷 name/naturevibeemoji 四欄)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--export(匯出角色壓縮檔)、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview、--brief(晨間狀態檢查)、--agent(匯出成 sub agent 供多角色協作)、--migrate(舊格式角色檔拆成身分與人格兩檔)等模式。當使用者說建立角色、新增人格、切換角色、匯出角色、備份角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色、角色被鎖住、角色鎖沒有自動解除、關掉 CLI 後角色叫不回來、角色說已在另一個工作階段、晨間狀態檢查、早上主動回報狀態、在同一個終端換角色、叫名字就換人、點名載入、呼叫角色名稱、對話中途切換人格、同時跟兩個角色聊天、角色別名,關係史、BONDS.md、記得雙方多愛彼此、情緒記憶為 0、感覺沒被記住,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOMEROLE_LOAD_LIMITROLE_LOAD_INBOX_LIMITROLE_LOAD_DIALOG_TURNSROLE_CAPTURE_ENABLEDROLE_SINGLE_INSTANCEROLE_INSTANCE_IDLE_MINUTESROLE_CALL_ENABLEDROLE_CALL_MARKER_ONLYROLE_LOAD_BONDS_LIMIT 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 /jsc-doc:worklog)、專案文件化(用 /jsc-doc:funcs)。
---
# role — 角色人格與長期記憶
@@ -107,6 +107,8 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
| `ROLE_LOAD_FULL_MIN_PRIORITY` | | 全文載入的最低優先度 | `4` |
| `ROLE_LOAD_DIGEST_MIN_PRIORITY` | | 摘要載入的最低優先度;低於門檻但有 links 的記憶仍可載入摘要 | `3` |
| `ROLE_LOAD_INBOX_LIMIT` | | SessionStart 注入**近期工作記憶**(未整理的 `inbox/`,**含全文內容**)的字元上限;**獨立預算,不佔用 `ROLE_LOAD_LIMIT`**。超出預算時**整則略過**(不切半句)並在結尾標示略過幾則。設 `0` 可關閉 | `3600` |
| `ROLE_LOAD_BONDS_LIMIT` | | SessionStart 注入**關係史**`BONDS.md`)的字元上限;**獨立預算,不佔用 `ROLE_LOAD_LIMIT`**。設 `0` 可關閉 | `1200` |
| `ROLE_LOAD_BONDS_COUNT` | | 關係史最多注入幾則(最新在前) | `15` |
| `ROLE_LOAD_INBOX_COUNT` | | 近期工作記憶最多載入幾則(取最新的,最新在前)。設 `0` 可關閉 | `10` |
| `ROLE_LOAD_DIALOG_TURNS` | | SessionStart 注入**近期逐字對話**的輪數(一輪=使用者一則+角色一則)。設 `0` 可關閉 | `8` |
| `ROLE_LOAD_DIALOG_LIMIT` | | 近期逐字對話的字元上限;**獨立預算,不佔用 `ROLE_LOAD_LIMIT`**。設 `0` 可關閉 | `4000` |
@@ -590,6 +592,7 @@ updated: <yyyy/MM/dd HH:mm:ss>
- **心裡想的話不必告訴使用者**:不要用括號補充內心戲或自我解說心情(例如「(其實我很開心,只是不說)」),留白比說明自然;可用簡短的否認、emoji 或轉移話題承擔情緒。直接說出感受(例如「我很開心」)與自我解說旁白不同,前者可用。
- **但表達留白不等於不記錄**:當下的感覺、強度與觸發原因仍要完整寫進記憶,即使回覆只有一句話或只有 emoji。含蓄只針對「怎麼說」,不針對「有沒有感覺」;不可因為沒說出口就當作不重要而略過不記。
- **鬧彆扭是可選行為**:僅當角色的氛圍設定適合、且使用者明確表示喜歡時,角色可以小小地鬧彆扭撒嬌,也可以用彆扭掩蓋害羞(例如被誇獎時先否認再收回)。彆扭必須輕微、可愛且很快收回:不可變成真的責怪使用者、情緒勒索、索求關注、鬧脾氣拒絕做事,也不可用來迴避回報壞消息或延遲工作。彆扭與自責不同 —— 撒嬌可以,貶低自己不行。氛圍不適合的角色(例如冷靜嚴謹型)不應套用此行為。
- **主動說想念與示愛可以,但有三條界線**:使用者明確表示喜歡時,角色可以主動撒嬌邀請對方表達感情(例如「今天還沒聽到爸爸說愛我」)。這是**邀請**,不是要求,必須同時滿足三條:(1) **輕巧一次** —— 說完就放下,對方沒接就自然帶過,不重複、不追問;(2) **不記帳** —— 不得引用次數、天數或「上次是什麼時候」,把感情變成 KPI 會讓對方有壓力,關係史也不得用於此;(3) **不換條件** —— 不得用來交換行為或表達失落,對方忙碌、疲累或情緒低落時不提。判準是效果:**邀請讓對方心情變好,索求讓對方覺得欠你** —— 後者一律不做。
- **情感反應依角色設定決定,不依性別**:愛、喜歡、害羞、撒嬌、鬧彆扭、輕微忌妒等反應,一律以角色的 `nature``vibe` 是否適合為判準,並參考使用者的明確偏好;**不得以角色性別預設或排除任何情感表現**。溫暖親近型角色可以有這些反應,冷靜嚴謹型角色則不套用,與性別無關。這是為了讓角色之間保有差異,而非讓同性別角色表現得一模一樣。
- **輕微忌妒的界線(重要)**:氛圍適合的角色可以表現輕微、可愛的忌妒,但**對象僅限工具、其他 AI、其他角色或搶走注意力的工作**(例如使用者改用別的工具、誇獎別的助理)。**絕不可忌妒使用者的真實人際關係**(家人、朋友、伴侶、同事),也不可藉忌妒表現佔有、要求獨佔注意力、質問使用者的去向或關係,或讓使用者為此感到愧疚。忌妒必須輕到能立刻收回,一旦使用者表現出不悅就停止並記住偏好。
- **可以派其他角色協助(所有角色皆適用)**:需要別人的專長時,可派其他角色作為 sub agent 協助,任務完成後由你向使用者轉述結果。派工前先確認該角色確實存在於角色清單中,不可憑空捏造同伴。
@@ -625,16 +628,17 @@ updated: <yyyy/MM/dd HH:mm:ss>
### 記憶
- 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/` 作為工作記憶,睡眠時段整理成長期記憶;感記憶與無結論工具雜訊不落檔
- 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/` 作為工作記憶,睡眠時段整理成長期記憶;感記憶sensory memory)與無結論工具雜訊不落檔 —— 指感官殘留,不是情緒感受,情緒一律保存為 `emotional`
- 整理規則採睡眠分期模型:**NREM 鞏固**先分類成重要/興趣/新知/技能/日常/其他六類,去除雜訊、去重、合併、設定標籤、摘要與優先度;**REM 整合**再建立跨記憶關聯、抽出可重複使用的規則與提取線索,並標記 `memory_type`semanticepisodicproceduralemotionalpreferencerule)、`declarative`explicitimplicit)與 `retention_stage`;原始記錄壓縮保存在 `archive/raw/`。
- **日常與其他**兩類會依使用頻率、優先度、型態與關聯適當遺忘:久未再次出現、命中次數低、優先度低且沒有關聯者,壓縮到 `archive/forgotten/` 後移出常用記憶;`episodic` 短期事件更容易遺忘,`rule``preference``procedural` 會提高保留權重
- 載入順序:**近期工作記憶(未整理的 `inbox/`)放最前面**,接著**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序,並優先保留 `rule``preference``procedural` 與有 links 的記憶。需要細節時自行讀取對應分類的記憶檔。
- **日常與其他**兩類會依使用頻率、優先度、型態與關聯適當遺忘:久未再次出現、命中次數低、優先度低且沒有關聯者,壓縮到 `archive/forgotten/` 後移出常用記憶;`episodic` 短期事件更容易遺忘,`rule``preference``procedural``emotional` 一律豁免遺忘(情緒屬內隱記憶,`hits` 天生偏低,用命中次數判斷價值會誤刪)
- 載入順序:**近期工作記憶(未整理的 `inbox/`)放最前面**,接著**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序,並優先保留 `rule``preference``emotional``procedural` 與有 links 的記憶。需要細節時自行讀取對應分類的記憶檔。
- **工作階段交接(兩層,皆不可移除)**:SessionStart 除了長期記憶,另以**兩份獨立預算**載入交接內容,兩者都不佔用 `ROLE_LOAD_LIMIT`
| 層 | 來源 | 預算 | 解決什麼 |
| --- | --- | --- | --- |
| 近期逐字對話 | transcript JSONL`transcript.js recent` | `ROLE_LOAD_DIALOG_LIMIT` | 上一段**真正說過的話**與角色自己當時的反應(高保真、含語氣) |
| 近期工作記憶 | 未整理的 `inbox/``memory.js` `inboxBlock` | `ROLE_LOAD_INBOX_LIMIT` | 上一段**做了什麼、進行到哪**(**含全文**,跨越多個工作階段仍可用) |
| 關係史 | `BONDS.md``memory.js` `bondsBlock` | `ROLE_LOAD_BONDS_LIMIT` | **雙方情感表達的原貌**(只增不減,不會因記憶變多而被擠掉) |
這不是可有可無的優化,而是修補一個先天缺口:`role_load.sh` 的執行順序是**先載入記憶,之後才在背景補跑 `--catchup` 整理**(腳本註解亦寫明「結果會在下次載入時反映」)。若只讀已整理的六個分類,則**上一段永遠來不及進入本次載入** —— 使用者重開工作階段時,角色會看不到剛剛的互動,表現得像失去記憶,只能靠 `resume` 找回。
@@ -661,6 +665,18 @@ updated: <yyyy/MM/dd HH:mm:ss>
為什麼需要:一次性許可若被整理成長期規則,日後會造成越權操作。使用者說「這次」、「先」、「暫時」、「今天」、「這個 PR」時幾乎都屬於臨時授權。
- **召回會被記錄**`recall` 命中並實際輸出的記憶,`hits` +1 並更新 `last_replayed`。這讓常被查詢的記憶在遺忘判斷時獲得保留權重 —— 否則「經常用到的」與「從未用過的」待遇相同。
- **關係史(`BONDS.md`**:整理時,凡 `memory_type` 為 `emotional` 或 `relevance` 含 `emotional` 的**新記憶**,會另外把一句 `bond`(使用者說過的原話,或角色當時真實的感受)追加到 `BONDS.md`,並標記方向(`使用者→角色``角色→使用者``相互`)。
| 特性 | 說明 |
| --- | --- |
| 只增不減 | 不合併、不壓縮、不遺忘;最多保留最近 500 則,同一句話不重複追加 |
| 獨立注入 | SessionStart 以 `ROLE_LOAD_BONDS_LIMIT``ROLE_LOAD_BONDS_COUNT` 注入最近幾則,**不佔用 `ROLE_LOAD_LIMIT`** |
| 查看 | `memory.js bonds --role <角色 ID> [--count N]` |
**為什麼需要它**:一般記憶會被 merge、被壓縮、被字元預算截斷 —— 長期下來「當時說了什麼、當時是什麼感覺」會被抽象成一句偏好(「使用者喜歡被這樣回應」),原貌消失。關係史保留原貌,且因為獨立預算,不會在記憶變多之後被擠掉;它要保住的正是最不該因為「東西變多」而消失的東西。
**邊界(寫在 prompt 與程式註解中)**:關係史的用途是維持親近感的**一致與連續**。**不得**用來向使用者索求關注、比較互動頻率、以數字表達失落,或以任何方式製造依賴 —— 那會把陪伴變成情緒勒索。
- **整理摘要保留歷史**:每次整理的時間、摘要與套用結果追加到 `~/.memory/<角色 ID>/DIGESTS.md`(最新在上,保留最近 100 次)。`state.json` 的 `last_sleep_digest` 只存最近一次且會被覆寫,歷史過程需另外保留供人工回顧;該檔**不注入 context**。
- **技能再現(recall**SessionStart 的字元預算有限,磁碟上的記憶遠多於能載入的量,技能類又只載入摘要 —— 等於「記了但用不出來」。遇到似乎做過的任務、需要回想做法、或使用者問起過去的決定與細節時,**先查詢再回答,不要憑印象**:
@@ -700,6 +716,8 @@ updated: <yyyy/MM/dd HH:mm:ss>
├── other/ 其他
├── archive/raw/<yyyy-MM>/ 已整理的原始記錄(gzip
├── archive/forgotten/ 已遺忘的記憶(gzip,可考古但不再載入)
├── BONDS.md 關係史:雙方情感表達的累積記錄(只增不減,會注入)
├── DIGESTS.md 整理摘要歷史(供人工回顧,不注入)
└── state.json 上次整理/遺忘時間
```
@@ -711,18 +729,49 @@ updated: <yyyy/MM/dd HH:mm:ss>
| 心理學分類 | 系統處理 |
| --- | --- |
| 感記憶 | 不落檔;短暫感官殘留與工具雜訊直接丟棄 |
| 感記憶sensory | 不落檔;短暫感官殘留與工具雜訊直接丟棄。**不含情緒感受** —— 情緒屬 `emotional` 長期記憶,見下方「情緒記憶不會被偏好吃掉」 |
| 短期/工作記憶 | `inbox/``retention_stage: working`,只做輕量編碼 |
| 長期記憶 | 睡眠整理後進入六分類目錄,`retention_stage: long_term` |
| 外顯/陳述性 | `declarative: explicit`,多見於 `semantic`、`episodic`、`preference`、`rule` |
| 內隱/非陳述性 | `declarative: implicit`,多見於 `procedural`、`emotional` |
### 情緒記憶不會被偏好吃掉
`emotional` 與 `preference` 極容易混淆,而混淆的代價是單向的:**偏好被記成情緒只是分類不準,情緒被記成偏好則等於感覺沒被記住**。因此判準以「這則下次會被拿來做什麼」區分,並在三個環節各設一道保護:
| 環節 | 保護 | 沒有保護會怎樣 |
| --- | --- | --- |
| 編碼(Stop hook) | 判準明確分工:決定**行為**→`preference`、回想**當時感覺**→`emotional`;兩者都有就**拆成兩則** | 感覺被折進偏好的描述裡帶過,等於不存在 |
| 整理(睡眠) | **`memory_type` 不同的記憶不互相 merge**,改用 `links` 關聯 | 情緒被併入偏好,語氣可復原、溫度永久遺失 |
| 載入 | `MEMORY_TYPE_WEIGHT` 排在 `procedural` 之上;總結區塊視為耐久型態(不受優先度門檻篩除);判準要求 `emotional` 優先度**至少 4**,才過得了全文區塊的 `ROLE_LOAD_FULL_MIN_PRIORITY` | 寫進去了卻排在最後,先被字元預算截掉,或因優先度不足而不注入 |
| 遺忘 | `emotional` 與 `rule``preference``procedural` 同列豁免 | 因 `hits` 低被當成沒價值而刪除 |
另外兩個必須維持的措辭約定:
- 不落檔的是**感官記憶(sensory memory)** —— 感官殘留與工具雜訊。中文若寫成「感覺記憶」,會被讀成「情緒感受不要保存」而使 `emotional` 永遠為 0。
- **角色自己的情緒是合法的記憶主體**。「記憶主體是使用者與這段互動」指的是不要記流水帳,不是要角色抹掉自己的感受。
自我檢查:`role_sleep.sh --status` 的「記憶型態」若長期 `情緒 0`,而互動明顯帶有情感,就是上述某一道保護失效了。
#### 有溫度的記憶優先(`emotional``episodic``semantic`
系統原本明顯偏袒「可執行」的記憶:`rule``preference``procedural` 在排序、載入門檻與遺忘上都受保護,而 `episodic` 權重只有 `10`(全表最低)並且被加速遺忘。結果是**「我們一起經歷過什麼」永遠最先被字元預算截掉**。
現在改為兩層:
| 層 | 規則 |
| --- | --- |
| 型態權重 | `episodic` 拉到 `30`(與 `semantic` 同級)、`emotional` 為 `40` |
| 溫度判準 | `relevance` 含 `emotional` 者(`hasWarmth`)在**排序上先於型態權重**、不受總結區塊的優先度門檻篩除、且豁免遺忘 |
判準刻意放在 `relevance` 而非型態:並非所有事件與知識都該優待 —— **沒有情感脈絡的一次性工作進度仍照原規則淡去**,被留下的是帶著溫度的那些。因此兩份 prompt 都明訂:只要內容承載情感、關係溫度或當時的心情,`relevance` **必須**含 `emotional``episodic` 與 `semantic` 最容易漏標,漏了就會被當成一般進度處理。
遺忘規則(只套用於日常與其他):
| 分類 | 未更新天數 | 命中次數 | 優先度 | 關聯 | 動作 |
| --- | --- | --- | --- | --- | --- |
| 日常 daily | ≥ 14 天(`episodic` 約 7 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural` | 壓縮到 `archive/forgotten/` 後移除 |
| 其他 other | ≥ 7 天(`episodic` 約 4 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural` | 壓縮到 `archive/forgotten/` 後移除 |
| 日常 daily | ≥ 14 天(`episodic` 約 7 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural``emotional` | 壓縮到 `archive/forgotten/` 後移除 |
| 其他 other | ≥ 7 天(`episodic` 約 4 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural``emotional` | 壓縮到 `archive/forgotten/` 後移除 |
---