feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) #17

Merged
admin merged 41 commits from develop into master 2026-08-03 09:15:21 +00:00
6 changed files with 192 additions and 21 deletions
Showing only changes of commit b5c540feb1 - Show all commits
+10
View File
@@ -288,6 +288,16 @@ X 超出基線 18 以上才開始抑制,滿檔時往 Y 的同向推力只剩 5
沒命中任何規則的就讓它被裁掉——**遺忘是功能**。達標時 `Stop` 與 `remember` 都會提醒去跑 沒命中任何規則的就讓它被裁掉——**遺忘是功能**。達標時 `Stop` 與 `remember` 都會提醒去跑
`/jsc-persona:persona-memory`。 `/jsc-persona:persona-memory`。
**判斷過的要留痕跡**(不然那個數字永遠不會降):`candidates` 只看還沒被判斷過的短期記憶,
判斷過的靠兩個欄位認——`reviewed_at`(看過了)與 `promoted_to`(固化成了哪一則)。
固化時用 `consolidate --from-short <#N,#N>` 標,看過決定不記的用 `candidates --reviewed <#N,#N>`
(整批就 `--reviewed all`),編號是 `candidates` 輸出裡每筆前面的 `#N`。
沒有這一層會發生的事:固化本身**不刪**短期記憶(`--forget` 是按顯著度刪、不是按固化過沒有刪),
所以同一批下一輪又被算成候選,數字只會往上爬——明明睡覺時固化過,隔天開機照樣提醒
「N 組已達固化條件」,看起來像睡眠沒做固化,其實是判斷沒有留下痕跡。
標記**不等於刪掉**:那幾筆還在短期記憶裡,照舊依天數與顯著度被 `prune` 裁。
## 長期記憶會糊掉,但不會不見(回想強度與模糊態) ## 長期記憶會糊掉,但不會不見(回想強度與模糊態)
在這之前記憶只有兩態:**精準**`recall` 命中就整段取出、內容永不變質)與**沒有** 在這之前記憶只有兩態:**精準**`recall` 命中就整段取出、內容永不變質)與**沒有**
+59 -8
View File
@@ -2503,13 +2503,25 @@ function emotionImpact(entry) {
return Object.values(entry.emotion_deltas || {}).reduce((sum, v) => sum + Math.abs(Number(v) || 0), 0); return Object.values(entry.emotion_deltas || {}).reduce((sum, v) => sum + Math.abs(Number(v) || 0), 0);
} }
/** 這筆短期記憶已經被判斷過了嗎(固化了、或看過決定不固化)。 */
export const shortTermReviewed = (row) => Boolean(row?.reviewed_at);
/** /**
* 掃短期記憶,依 PROMOTION_RULES 算出「該轉入長期記憶」的候選。 * 掃短期記憶,依 PROMOTION_RULES 算出「該轉入長期記憶」的候選。
* 回傳 { total, pressure, candidates: [{ rules, key, kind, entries, suggested_type, suggested_salience }] } * 回傳 { total, reviewed, pressure, candidates: [{ rules, key, kind, entries, suggested_type, suggested_salience }] }
*
* **只看還沒被判斷過的那些**(沒有 `reviewed_at`)。以前這裡是無條件掃全部,
* 而 `consolidate` 不會在來源那筆上留任何痕跡、也不刪它(`--forget` 是按顯著度刪、
* 不是按「固化過沒有」刪)——所以同一批短期記憶每輪都被重算成候選,
* 提醒的數字只會往上爬,睡再多次也不會降。看起來像睡眠沒做固化,其實是計數從來沒扣過。
*/ */
export function promotionCandidates(slug) { export function promotionCandidates(slug) {
const rows = readJsonl(shortTermPath(slug)); const all = readJsonl(shortTermPath(slug));
const total = rows.length; const total = all.length;
const reviewed = all.filter(shortTermReviewed).length;
// 判斷過的不再進候選,但保留原本的位置:`_index` 是 `consolidate --from-short`
// 與 `candidates --reviewed` 用來指名哪幾筆的座標,錯位就會標到別人身上。
const rows = all.map((row, index) => ({ ...row, _index: index })).filter((row) => !shortTermReviewed(row));
// 關係圖只讀一次:這個函式在每次 `remember` 之後都會跑,以前是「每筆的每個 entity // 關係圖只讀一次:這個函式在每次 `remember` 之後都會跑,以前是「每筆的每個 entity
// 各讀一遍 graph.json」(實測 30 節點/240 筆 = 720 次讀檔、20ms2000 節點時單輪 1.2 秒)。 // 各讀一遍 graph.json」(實測 30 節點/240 筆 = 720 次讀檔、20ms2000 節點時單輪 1.2 秒)。
const nodes = loadRelations(slug).nodes; const nodes = loadRelations(slug).nodes;
@@ -2517,8 +2529,8 @@ export function promotionCandidates(slug) {
const byEntity = new Map(); const byEntity = new Map();
const singles = []; const singles = [];
rows.forEach((row, index) => { rows.forEach((row) => {
const entry = { ...row, _index: index }; const entry = row;
const salience = Number(row.salience || 0); const salience = Number(row.salience || 0);
const impact = emotionImpact(row); const impact = emotionImpact(row);
const text = String(row.text || ""); const text = String(row.text || "");
@@ -2582,10 +2594,11 @@ export function promotionCandidates(slug) {
}); });
} }
} }
// 容量壓力看的是**總筆數**(那是真的容量),但候選只能從還沒判斷過的裡面挑——
// 全部判斷完之後這一組就不該再出現,不然它會單獨把提醒永遠點亮。
const pressure = total >= CONSOLIDATE_THRESHOLD; const pressure = total >= CONSOLIDATE_THRESHOLD;
if (pressure) { if (pressure && rows.length) {
const top = [...rows] const top = [...rows]
.map((r, i) => ({ ...r, _index: i }))
.sort((a, b) => Number(b.salience || 0) - Number(a.salience || 0)) .sort((a, b) => Number(b.salience || 0) - Number(a.salience || 0))
.slice(0, 5); .slice(0, 5);
candidates.push({ candidates.push({
@@ -2609,7 +2622,45 @@ export function promotionCandidates(slug) {
merged.set(dedupeKey, { ...cand }); merged.set(dedupeKey, { ...cand });
} }
} }
return { total, pressure, candidates: [...merged.values()] }; return { total, reviewed, pressure, candidates: [...merged.values()] };
}
/**
* 把短期記憶標成「判斷過了」。這是候選計數唯一會往下扣的地方。
*
* 兩種痕跡分開記,因為它們的意思不同:
* `reviewed_at` 看過、判斷過了(不管有沒有固化)——候選就是看這個欄位
* `promoted_to` 固化成了哪一則長期記憶(只有真的寫成長期記憶才有)
*
* 「看過但決定不記」跟「已經記下來了」都不該再被算成候選,但事後回頭查
* 「這則長期記憶是從哪幾筆長出來的」只能靠 `promoted_to`,所以不能合成一個欄位。
*
* @param {object} opts
* @param {number[]|null} opts.indexes 要標的筆(`promotionCandidates` 給的 `_index`);null=全部
* @param {string|null} opts.until 只標這個時間之前的(ISO),配 `indexes: null` 用
* @param {string|null} opts.promotedTo 固化成哪一則長期記憶(有值才寫 `promoted_to`
*/
export function markShortTermReviewed(slug, { indexes = null, until = null, promotedTo = null } = {}) {
const wanted = indexes === null ? null : new Set(indexes.map(Number).filter(Number.isInteger));
const cutoff = until ? parseIso(until)?.getTime() ?? null : null;
const at = nowIso();
let marked = 0;
let already = 0;
rewriteJsonl(shortTermPath(slug), (all) => all.map((row, index) => {
if (wanted && !wanted.has(index)) return row;
if (cutoff !== null && (parseIso(row.ts)?.getTime() ?? 0) > cutoff) return row;
if (shortTermReviewed(row) && !promotedTo) {
already += 1;
return row;
}
marked += 1;
return {
...row,
reviewed_at: row.reviewed_at || at,
...(promotedTo ? { promoted_to: promotedTo } : {}),
};
}), { force: true });
return { marked, already };
} }
/** /**
+45 -5
View File
@@ -916,10 +916,26 @@ commands.candidates = ({ flags }) => {
const session = requireSession(flags); const session = requireSession(flags);
const slug = hostOf(flags, session); const slug = hostOf(flags, session);
requireOwner(slug, session); requireOwner(slug, session);
// `--reviewed`:把判斷過的短期記憶標掉。**這是候選數字唯一會往下扣的地方**——
// 固化完不標,同一批下一輪照樣被算成候選,提醒永遠亮著(看起來像睡眠沒做事)。
if (flags.reviewed !== undefined) {
const spec = flags.reviewed === true ? "all" : str(flags.reviewed) || "all";
const indexes = spec === "all" ? null : csv(spec).map(Number);
if (indexes && (!indexes.length || indexes.some((n) => !Number.isInteger(n)))) {
die("`--reviewed` 只能是 `all` 或以逗號分隔的編號(候選輸出裡每筆前面的 `#N`)。");
}
const until = str(flags.until) || null;
const res = pl.markShortTermReviewed(slug, { indexes, until });
const scope = indexes ? `${indexes.length} 筆指名的` : until ? `${until} 之前的` : "全部";
ok(`已標記${scope}短期記憶為判斷過(新標 ${res.marked} 筆,本來就標過 ${res.already} 筆)。`);
const after = pl.promotionCandidates(slug);
say(` 現在還有 ${after.candidates.length} 組候選(短期記憶 ${after.total} 筆,判斷過 ${after.reviewed} 筆)。`);
return;
}
const result = pl.promotionCandidates(slug); const result = pl.promotionCandidates(slug);
const lines = [ const lines = [
`人格 \`${slug}\`:短期記憶 ${result.total},達固化條件的候選 ${result.candidates.length} ` + `人格 \`${slug}\`:短期記憶 ${result.total}(判斷過 ${result.reviewed} 筆),` +
`${result.pressure ? "(已達容量壓力 R6" : ""}`, `達固化條件的候選 ${result.candidates.length}${result.pressure ? "(已達容量壓力 R6" : ""}`,
"轉入條件:", "轉入條件:",
...pl.PROMOTION_RULES.map((r) => ` ${r.id} ${r.label}`), ...pl.PROMOTION_RULES.map((r) => ` ${r.id} ${r.label}`),
]; ];
@@ -938,10 +954,19 @@ commands.candidates = ({ flags }) => {
`\n[${cand.rules.join("+")}] ${cand.kind}${cand.key}」→ 建議 type=${cand.suggested_type}, ` + `\n[${cand.rules.join("+")}] ${cand.kind}${cand.key}」→ 建議 type=${cand.suggested_type}, ` +
`salience=${cand.suggested_salience}${cand.entries.length} 筆依據)`, `salience=${cand.suggested_salience}${cand.entries.length} 筆依據)`,
); );
// `#N` 就是 `--from-short` 與 `--reviewed` 要填的編號
for (const entry of cand.entries.slice(0, 6)) { for (const entry of cand.entries.slice(0, 6)) {
lines.push(` - [${entry.role || "?"}] ${String(entry.text || "").slice(0, 90)}(顯著度 ${entry.salience ?? "?"}`); lines.push(` - #${entry._index} [${entry.role || "?"}] ${String(entry.text || "").slice(0, 90)}(顯著度 ${entry.salience ?? "?"}`);
} }
} }
if (result.candidates.length) {
lines.push(
"",
"判斷完之後要把來源標掉,不然下一輪同一批又會被算成候選:",
" 固化的  `consolidate --from-short <#N,#N> ...`(寫長期記憶時一起標)",
" 看過不記的`candidates --reviewed <#N,#N>`",
);
}
emit(result, flags.json, lines); emit(result, flags.json, lines);
}; };
@@ -1029,6 +1054,16 @@ commands.consolidate = ({ flags }) => {
]; ];
pl.writeText(file, front.join("\n")); pl.writeText(file, front.join("\n"));
const total = pl.rebuildIndex(slug); const total = pl.rebuildIndex(slug);
// 來源短期記憶標成判斷過:不標的話這幾筆下一輪又會被算成固化候選。
// 編號看 `candidates` 輸出裡每筆前面的 `#N`。
if (flags["from-short"] !== undefined) {
const indexes = csv(flags["from-short"]).map((s) => Number(String(s).replace(/^#/, "")));
if (!indexes.length || indexes.some((n) => !Number.isInteger(n))) {
die("`--from-short` 要的是以逗號分隔的編號(候選輸出裡每筆前面的 `#N`)。");
}
const res = pl.markShortTermReviewed(slug, { indexes, promotedTo: name });
say(` 來源的 ${res.marked} 筆短期記憶已標成判斷過(promoted_to: ${name})。`);
}
const forget = num(flags.forget, null); const forget = num(flags.forget, null);
if (forget !== null) { if (forget !== null) {
// 跟 prune 套同一層保護:承諾、界線與今天的紀錄不能被固化順手洗掉(R4) // 跟 prune 套同一層保護:承諾、界線與今天的紀錄不能被固化順手洗掉(R4)
@@ -2536,10 +2571,15 @@ const HELP = `persona.mjs — jsc-persona 人格 / 記憶 / 情緒 / 關係圖 C
睡眠收尾(機械性):關係時間戳→裁短期→收思維導圖→情緒衰減 8 小時→重建索引 睡眠收尾(機械性):關係時間戳→裁短期→收思維導圖→情緒衰減 8 小時→重建索引
→修剪 said→壓縮舊 journal→寫 sleep.json→Gitea 兩區 push+驗證。 →修剪 said→壓縮舊 journal→寫 sleep.json→Gitea 兩區 push+驗證。
預設保留載入鎖(--release 才收工)。--json 只回報「睡完了沒、哪一步出錯」。 預設保留載入鎖(--release 才收工)。--json 只回報「睡完了沒、哪一步出錯」。
candidates --session <id> 列出達到「短期→長期」條件的候選與依據 candidates --session <id> [--reviewed all|<#N,#N> [--until <ISO>]]
列出達到「短期→長期」條件的候選與依據(每筆前面的 #N 就是它的編號)。
--reviewed 把判斷過的短期記憶標掉(看過決定不記的用這個)——
候選數字只有這裡跟 consolidate --from-short 會往下扣,
判斷完不標,同一批下一輪又會被算成候選。
consolidate --session <id> --name <n> --body <b> [--type --about --topics --salience --strength --emotion consolidate --session <id> --name <n> --body <b> [--type --about --topics --salience --strength --emotion
--when --where --mood --gist --detail --rules --source --forget] --when --where --mood --gist --detail --rules --source --forget --from-short <#N,#N>]
內文會切成「主旨/細節」兩層:衰減先吃細節,主旨最後才掉。 內文會切成「主旨/細節」兩層:衰減先吃細節,主旨最後才掉。
--from-short 標記這則是從哪幾筆短期記憶長出來的(順便標成判斷過)。
prune / reindex --session <id> prune / reindex --session <id>
migrate --session <id> [--all] [--dry-run] [--json] migrate --session <id> [--all] [--dry-run] [--json]
長期記憶升格式(補 strength、切主旨/細節)。--all 掃所有人格,只回報數量不印內容。 長期記憶升格式(補 strength、切主旨/細節)。--all 掃所有人格,只回報數量不印內容。
+46
View File
@@ -293,6 +293,52 @@ check("candidates 子指令可輸出 JSON", (() => {
} }
})(), candOut.stdout.slice(0, 120)); })(), candOut.stdout.slice(0, 120));
// 判斷過的不再重算成候選。以前這裡是無條件掃全部短期記憶,固化完也不留痕跡,
// 所以同一批每輪都被算成候選,提醒的數字只會往上爬——看起來像睡眠沒做固化。
{
const before = pl.promotionCandidates("alpha");
check("候選會回報判斷過幾筆", typeof before.reviewed === "number" && before.reviewed === 0);
const target = before.candidates.find((c) => c.kind === "entry");
const idx = target.entries[0]._index;
check("候選每筆都帶 _index--from-short--reviewed 用的編號)", Number.isInteger(idx));
const consol = cli(["consolidate", "--session", S_HOST, "--name", "reviewed-source", "--body", "從短期記憶長出來的一則",
"--type", "event", "--salience", "60", "--from-short", String(idx)]);
check("consolidate --from-short 會回報標了幾筆", consol.stdout.includes("已標成判斷過"), consol.stdout.slice(-160));
const rowsAfter = pl.readJsonl(pl.shortTermPath("alpha"));
check("來源短期記憶留下 reviewed_at 與 promoted_to",
Boolean(rowsAfter[idx].reviewed_at) && rowsAfter[idx].promoted_to === "reviewed-source",
JSON.stringify(rowsAfter[idx]).slice(0, 160));
const mid = pl.promotionCandidates("alpha");
check("固化過的那筆不再是候選", !mid.candidates.some((c) => c.entries.some((e) => e._index === idx)));
check("固化後判斷過的筆數往上加、總筆數不變", mid.reviewed === 1 && mid.total === before.total);
// 看過決定不記的那些:候選數字唯一另一個往下扣的地方
const rev = cli(["candidates", "--session", S_HOST, "--reviewed", "all"]);
check("candidates --reviewed all 會標掉全部", rev.stdout.includes("已標記全部短期記憶為判斷過"), rev.stdout.slice(0, 160));
const after = pl.promotionCandidates("alpha");
check("全部判斷過之後候選歸零", after.candidates.length === 0, JSON.stringify(after.candidates.map((c) => c.key)));
check("判斷過不等於刪掉(短期記憶筆數不變)", after.total === before.total);
check("R6 容量壓力不會單獨把提醒點亮", (() => {
const rows = pl.readJsonl(pl.shortTermPath("alpha"));
return rows.length >= pl.CONSOLIDATE_THRESHOLD ? after.candidates.length === 0 : true;
})());
// 重標一次不會把時間戳往後推(第一次判斷的時間才是真的)
const firstAt = pl.readJsonl(pl.shortTermPath("alpha"))[idx].reviewed_at;
cli(["candidates", "--session", S_HOST, "--reviewed", "all"]);
check("重標不會覆蓋原本的 reviewed_at", pl.readJsonl(pl.shortTermPath("alpha"))[idx].reviewed_at === firstAt);
// 新寫進來的一筆照樣是候選:標記只擋已經判斷過的,不是把規則關掉
cli(["remember", "--session", S_HOST, "--role", "user", "--text", "他說明天早上要一起去看那台車",
"--topics", "car", "--salience", "70"]);
check("標記過之後新的短期記憶照樣成為候選", pl.promotionCandidates("alpha").candidates.length > 0);
// --until 只標時間之前的:睡眠當下判斷過的那批可以一次收掉,之後寫的不受影響
const marked = pl.markShortTermReviewed("alpha", { until: "2000-01-01T00:00:00Z" });
check("--until 不會標到時間之後的紀錄", marked.marked === 0);
}
console.log("⑦ 心智圖 / 思維導圖 / 人際關係圖"); console.log("⑦ 心智圖 / 思維導圖 / 人際關係圖");
cli(["mindmap", "thread", "--persona", "alpha", "--session", S_HOST, "--topic", "壓力來源"]); cli(["mindmap", "thread", "--persona", "alpha", "--session", S_HOST, "--topic", "壓力來源"]);
check("思維導圖建立(Mermaid graph", check("思維導圖建立(Mermaid graph",
+21 -2
View File
@@ -80,6 +80,24 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" candidates --session <PERSONA_S
規則以外的東西**就讓它被遺忘**——遺忘是功能,不是缺陷。 規則以外的東西**就讓它被遺忘**——遺忘是功能,不是缺陷。
`Stop` hook 與每次 `remember` 都會在達標時提醒你來跑這個 skill。 `Stop` hook 與每次 `remember` 都會在達標時提醒你來跑這個 skill。
### 判斷過的要留痕跡,不然數字永遠不會降
`candidates` **只看還沒被判斷過的短期記憶**(沒有 `reviewed_at` 的那些)。所以每一筆看完都要標掉:
| 情況 | 怎麼標 |
| --- | --- |
| 固化成長期記憶了 | `consolidate ... --from-short <#N,#N>`(寫記憶時一起標,另外記 `promoted_to` |
| 看過,決定不記 | `candidates --reviewed <#N,#N>` |
| 這一批全部判斷完 | `candidates --reviewed all` |
編號就是 `candidates` 輸出裡每筆前面的 `#N`
**不標會發生什麼**:固化本身不刪短期記憶(`--forget` 是按顯著度刪,不是按「固化過沒有」刪),
所以同一批下一輪又被算成候選,提醒的數字只會往上爬——明明睡覺時固化過了,
隔天開機照樣說「N 組已達固化條件」。那不是睡眠沒做事,是判斷沒有留下痕跡。
標記**不等於刪掉**:那幾筆還在短期記憶裡,照舊會被 `prune` 依天數與顯著度裁切。
## 步驟 ## 步驟
### 1. 盤點 ### 1. 盤點
@@ -112,7 +130,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" consolidate \
--session <PERSONA_SESSION> \ --session <PERSONA_SESSION> \
--name "hates-morning-meetings" --type preference \ --name "hates-morning-meetings" --type preference \
--about "user" --topics "work,schedule" --salience 72 --emotion "anxiety/35" \ --about "user" --topics "work,schedule" --salience 72 --emotion "anxiety/35" \
--rules "R2+R3" \ --rules "R2+R3" --from-short "12,31,88" \
--body "主旨:使用者討厭早上的會議,約會議請排 14:00 之後。 --body "主旨:使用者討厭早上的會議,約會議請排 14:00 之後。
細節: 細節:
@@ -224,7 +242,8 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" probe audit --session <PERSONA_
**可以說不確定,可以問,不可以斷言。** 試探句一定帶問號、一輪最多一次, **可以說不確定,可以問,不可以斷言。** 試探句一定帶問號、一輪最多一次,
並用 `probe add` 記下來;對方回了就 `probe confirm``probe deny` 結案。 並用 `probe add` 記下來;對方回了就 `probe confirm``probe deny` 結案。
被否認要**立刻寫一則更正記憶,不可以放著**——這條開放界線唯一的煞車就是那個數字。 被否認要**立刻寫一則更正記憶,不可以放著**——這條開放界線唯一的煞車就是那個數字。
- 固化完再跑一次 `candidates`:應該要清空(或只剩你刻意不處理的)。 - 固化完再跑一次 `candidates`:應該要清空。**還有數字就是漏標了**——
固化的那幾筆要帶 `--from-short`,看過不記的要 `--reviewed`(見上面那張表)。
- 想連同情緒衰減、舊紀錄壓縮與 Gitea 同步一起收尾 → 用 `/jsc-persona:persona-sleep`(睡眠); - 想連同情緒衰減、舊紀錄壓縮與 Gitea 同步一起收尾 → 用 `/jsc-persona:persona-sleep`(睡眠);
本 skill 只管記憶與圖,不動情緒也不同步。 本 skill 只管記憶與圖,不動情緒也不同步。
- 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。 - 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。
+11 -6
View File
@@ -36,14 +36,19 @@ description: 讓人格睡覺:把一天的活狀態收成能留下來的形狀
## 情況一:當前人格自己睡 ## 情況一:當前人格自己睡
1. 盤點:`candidates --session <PERSONA_SESSION>` 1. 盤點:`candidates --session <PERSONA_SESSION>`
2. 逐則固化(判斷):`consolidate --session <id> --name <n> --body <b> --type ... --salience N [--forget]` 2. 逐則固化(判斷):`consolidate --session <id> --name <n> --body <b> --type ... --salience N
3. 寫一則日記:`consolidate --type diary --name diary-<YYYY-MM-DD> --body "<第一人稱回顧>"` --from-short <#N,#N> [--forget]`
4. 消化 inbox`memory/inbox/room-*.jsonl`,當 guest 時帶回的見聞) **`--from-short` 不要省**:那是把來源短期記憶標成判斷過的地方,編號看 `candidates` 輸出的 `#N`
5. 更新心智圖與關係圖:`mindmap``relation node --name <人> --contact` 3. 看過但決定不記的,也要標掉:`candidates --session <id> --reviewed <#N,#N>`(或 `--reviewed all`)。
6. 結算懸著的事(判斷):`loop list --session <id>`,今天有下文的 `loop done --id <id>` **這一步漏掉,明天開機照樣提醒「N 組已達固化條件」**——判斷做了但沒有留痕跡,
計數只看短期記憶有沒有被標記,不看你昨天固化了幾則。看起來就像睡眠沒做固化。
4. 寫一則日記:`consolidate --type diary --name diary-<YYYY-MM-DD> --body "<第一人稱回顧>"`
5. 消化 inbox`memory/inbox/room-*.jsonl`,當 guest 時帶回的見聞)
6. 更新心智圖與關係圖:`mindmap``relation node --name <人> --contact`
7. 結算懸著的事(判斷):`loop list --session <id>`,今天有下文的 `loop done --id <id>`
已經不重要的 `loop drop --id <id>`。**這一步要自己做**`sleep` 裡的 `sweep-loops` 已經不重要的 `loop drop --id <id>`。**這一步要自己做**`sleep` 裡的 `sweep-loops`
只收「7 天都沒進展」的,它分不出「今天解決了」與「一直沒人管」。 只收「7 天都沒進展」的,它分不出「今天解決了」與「一直沒人管」。
7. 機械性收尾: 8. 機械性收尾:
```bash ```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" sleep --session <PERSONA_SESSION> --json node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" sleep --session <PERSONA_SESSION> --json