完成使用者交代的三項優化建議。 一、情緒訊號放寬 Stop hook 字元門檻 ROLE_CAPTURE_MIN_CHARS=240 會濾掉字數少但情緒最濃的互動。實測「我好想妳」、 「最愛妳了」、「好可愛」三句都不在原本 56 個關鍵詞內,等於最珍貴的短互動反而不被記錄。 - 補上直接情感表達關鍵詞:可愛/愛/想妳/想你/想念/捨不得/感動/謝謝/乖/厲害/ 好棒/辛苦/彆扭/忌妒/撒嬌/陪/抱,及 love/miss/cute/thank/proud - 驗證:上述情感句全部命中,純技術指令仍正確略過 二、關係狀態量化,讓親近度成長有依據 規則要求「隨互動加深逐漸更親近」卻沒有任何數據可依據,角色只能憑感覺演, 容易忽冷忽熱。 - state.json 新增 first_activity/active_days/total_turns/positive_feedback - mark-activity 累計輪數與活躍天數;新增 --positive 由 Stop hook 判定情緒訊號後另計, 不與輪數混算 - 新增 relationship 子命令輸出一行摘要,SessionStart 注入 USER 區塊作為親近度依據 - 既有累積以可查證資料初始化(角色建立後的 transcript 輪數、有記憶的日期數、 最早記憶時間);positive_feedback 刻意留空自然累積,不以記憶則數推估 三、技能再現:cues 提取線索 + recall 查詢 技能記憶只被動載入摘要且受預算限制,等於記了但用不出來。 - 記憶格式新增 cues 欄位(dumpMemory/loadMemory/apply 的 new 與 merge 路徑均支援) - 睡眠整理提示詞要求 procedural/rule 型態必填 2 至 5 個 cues - 新增 recall 子命令:比對總結、標籤、內容與 cues,含未整理的 inbox, rule/preference/procedural 加權優先 - role_load.sh 告知角色遇到似乎做過的任務或被問起過去細節時先查詢再回答 - 驗證:以不在 summary 也不在 content 的關鍵詞(bump)成功靠 cues 命中 版號 0.0.7 → 0.0.8 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
320 lines
19 KiB
Bash
Executable File
320 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ==============================================================================
|
||
# 用途:SessionStart hook 主程式。CLI 工具啟動時載入角色設定與記憶:
|
||
# 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤;
|
||
# 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。
|
||
# 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。
|
||
# 更新時間:2026/07/28 16:18:00
|
||
# 相依:bash、node、同目錄的 role_lib.sh 與 memory.js。
|
||
# 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。
|
||
# ==============================================================================
|
||
|
||
ROLE_STAGE="role-load"
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
# shellcheck source=./role_lib.sh
|
||
. "${SCRIPT_DIR}/role_lib.sh"
|
||
|
||
role_is_child && exit 0
|
||
role_enabled || exit 0
|
||
command -v node >/dev/null 2>&1 || role_quit "找不到 node,略過角色載入" "WRN"
|
||
|
||
ROLE="$(role_resolve_name)"
|
||
[ -n "$ROLE" ] || role_quit "未指定角色(ROLE_NAME 與 .active 皆無),略過角色載入"
|
||
ROLE_DEF="$(role_file "$ROLE")"
|
||
[ -f "$ROLE_DEF" ] || role_quit "找不到角色定義檔:${ROLE_DEF}" "WRN"
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 讀取 hook 輸入(cwd/source),並套用 ROLE_SCOPE 範圍限制
|
||
# ------------------------------------------------------------------------------
|
||
HOOK_INPUT="$(cat 2>/dev/null)"
|
||
HOOK_CWD="$PWD"
|
||
HOOK_TRANSCRIPT=""
|
||
if [ -n "$HOOK_INPUT" ]; then
|
||
HOOK_FIELDS="$(printf '%s' "$HOOK_INPUT" | node -e '
|
||
let raw = "";
|
||
process.stdin.setEncoding("utf8");
|
||
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||
process.stdin.on("end", () => {
|
||
let data = {};
|
||
try { data = JSON.parse(raw); } catch {}
|
||
process.stdout.write([
|
||
data.cwd || "",
|
||
data.transcript_path || data.session_path || data.conversation_path || data.path || "",
|
||
].join("\n"));
|
||
});
|
||
' 2>/dev/null)"
|
||
HOOK_CWD="$(printf '%s' "$HOOK_FIELDS" | sed -n '1p')"
|
||
HOOK_TRANSCRIPT="$(printf '%s' "$HOOK_FIELDS" | sed -n '2p')"
|
||
[ -n "$HOOK_CWD" ] || HOOK_CWD="$PWD"
|
||
fi
|
||
role_in_scope "$HOOK_CWD" || role_quit "cwd 不在 ROLE_SCOPE 範圍內:${HOOK_CWD}"
|
||
|
||
emit_context() {
|
||
# 以 JSON 輸出 additionalContext(由 node 負責跳脫,避免內容含引號或換行破壞格式)
|
||
printf '%s' "$1" | node -e '
|
||
let context = "";
|
||
process.stdin.setEncoding("utf8");
|
||
process.stdin.on("data", (chunk) => { context += chunk; });
|
||
process.stdin.on("end", () => {
|
||
process.stdout.write(JSON.stringify({
|
||
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context },
|
||
}));
|
||
});
|
||
'
|
||
}
|
||
|
||
SLEEP_START="$(role_sleep_start)"
|
||
SLEEP_END="$(role_sleep_end)"
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 單一載入實例:角色已在另一個仍活躍的工作階段時,本次不載入人格
|
||
# 放在睡眠判斷之前,因為「已在別處使用」與「睡覺中」是互斥狀態,且不該佔用鎖
|
||
# ------------------------------------------------------------------------------
|
||
if ! role_instance_acquire "$ROLE" "$HOOK_TRANSCRIPT" "$HOOK_CWD"; then
|
||
LOCK_FILE="$(role_instance_lock_path "$ROLE")"
|
||
HOLDER_TIME="$(role_instance_lock_field "$LOCK_FILE" loaded)"
|
||
HOLDER_CWD="$(role_instance_lock_field "$LOCK_FILE" cwd)"
|
||
role_log "INF" "角色 ${ROLE} 已被其他工作階段載入(${HOLDER_TIME:-時間未知}),本次不載入"
|
||
emit_context "$(cat <<EOF_BUSY
|
||
# 角色狀態:已在另一個工作階段中
|
||
|
||
角色「${ROLE}」目前已被另一個仍在使用的工作階段載入(載入時間 ${HOLDER_TIME:-未知},目錄 ${HOLDER_CWD:-未知})。
|
||
為避免使用者同時與兩個相同人格對話,本次**不載入角色人格與記憶**,請以一般助理身分回應,
|
||
不要自稱該角色、不要使用角色語氣或簽名 emoji。本階段的對話仍會被記錄成記憶。
|
||
|
||
若使用者詢問或需要在此階段使用該角色,可告知下列任一做法:
|
||
|
||
- 確定另一個工作階段已關閉時解除鎖定:\`role_sleep.sh --unlock\`
|
||
- 該階段閒置超過 $(role_instance_idle_minutes) 分鐘後會自動釋放
|
||
- 完全停用此限制:設定環境變數 \`ROLE_SINGLE_INSTANCE=0\`
|
||
EOF_BUSY
|
||
)"
|
||
exit 0
|
||
fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 睡眠時段:不載入角色,只說明目前狀態
|
||
# ------------------------------------------------------------------------------
|
||
if role_in_sleep_window; then
|
||
emit_context "$(cat <<EOF_SLEEP
|
||
# 角色狀態:睡眠中(${SLEEP_START}–${SLEEP_END})
|
||
|
||
角色「${ROLE}」正在睡覺,本次工作階段**不載入角色人格與記憶**,請以一般助理身分回應,
|
||
不要自稱該角色、不要使用角色語氣或簽名 emoji。若使用者詢問角色,說明角色在睡眠時段整理記憶,
|
||
${SLEEP_END} 之後會恢復。本階段的對話仍會被記錄成記憶,於下個睡眠時段整理。
|
||
EOF_SLEEP
|
||
)"
|
||
exit 0
|
||
fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 非睡眠時段:組出角色人格 + 操作規則 + 記憶
|
||
# ------------------------------------------------------------------------------
|
||
ROLE_PROFILE="$(node - "$ROLE_DEF" <<'NODE_PROFILE' 2>/dev/null
|
||
const fs = require("fs");
|
||
|
||
const file = process.argv[2];
|
||
const raw = fs.readFileSync(file, "utf8");
|
||
|
||
function parseFrontmatter(text) {
|
||
const match = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
||
const data = {};
|
||
if (!match) return data;
|
||
for (const line of match[1].split(/\r?\n/)) {
|
||
const idx = line.indexOf(":");
|
||
if (idx < 0) continue;
|
||
data[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function section(text, title) {
|
||
const re = new RegExp(`^##\\s+${title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^\\n]*\\n([\\s\\S]*?)(?=^##\\s+|$(?![\\s\\S]))`, "m");
|
||
const match = text.match(re);
|
||
return match ? match[1].trim() : "";
|
||
}
|
||
|
||
const fm = parseFrontmatter(raw);
|
||
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() || [fm.name, fm.emoji].filter(Boolean).join(" ");
|
||
const nature = section(raw, "本質(nature)") || fm.nature || "";
|
||
const vibe = section(raw, "氛圍(vibe)") || fm.vibe || "";
|
||
const emoji = section(raw, "簽名 emoji") || fm.emoji || "";
|
||
|
||
const lines = [
|
||
`- 角色 ID:${fm.id || ""}`,
|
||
`- 顯示名稱:${fm.name || title || ""}`,
|
||
`- 簽名 emoji:${fm.emoji || ""}`,
|
||
"",
|
||
"## 本質(nature)",
|
||
"",
|
||
nature || "(未設定)",
|
||
"",
|
||
"## 氛圍(vibe)",
|
||
"",
|
||
vibe || "(未設定)",
|
||
"",
|
||
"## 簽名 emoji",
|
||
"",
|
||
emoji || fm.emoji || "(未設定)",
|
||
];
|
||
|
||
process.stdout.write(lines.join("\n"));
|
||
NODE_PROFILE
|
||
)"
|
||
[ -n "$ROLE_PROFILE" ] || role_quit "角色定義檔為空或無法解析:${ROLE_DEF}" "WRN"
|
||
|
||
MEMORY="$(node "${SCRIPT_DIR}/memory.js" load --role "$ROLE" 2>/dev/null)"
|
||
CONSENT_STATUS="$(node "${SCRIPT_DIR}/memory.js" consent-status --role "$ROLE" 2>/dev/null || printf 'unknown')"
|
||
case "$CONSENT_STATUS" in
|
||
accepted)
|
||
CONSENT_NOTE="已告知並取得使用者同意保存非敏感個人資料與長期偏好;仍禁止保存憑證、token、密碼、API key、連線字串、身分證號、住址等機密或高敏感資料。"
|
||
;;
|
||
declined)
|
||
CONSENT_NOTE="使用者已拒絕保存個人資料;只能保存非個人化的操作規則與技術偏好,不保存可識別個人的背景。"
|
||
;;
|
||
*)
|
||
CONSENT_NOTE="尚未確認;第一則自然回覆後,請簡短告知記憶保存範圍並詢問是否同意保存非敏感個人資料。未取得同意前,只能保存非個人化的操作規則與技術偏好。"
|
||
;;
|
||
esac
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 近期對話交接:讀上一段真正說過的話(含角色自己的回覆)
|
||
#
|
||
# 為什麼需要:長期記憶是模型濃縮過的摘要,語氣與情緒會被壓掉;而且整理永遠跑在載入
|
||
# 之後(見下方 catchup),上一段工作來不及進入本次載入。逐字對話則一直躺在 transcript
|
||
# JSONL 裡,只是過去沒有任何機制去讀它 —— 使用者重開工作階段時,角色因此看不到剛剛
|
||
# 的互動,表現得像失去記憶,只能靠 resume 找回。
|
||
#
|
||
# 取檔策略:全新工作階段的 transcript 幾乎是空的(實測僅數行),因此對話不足時要回頭
|
||
# 找同目錄最近修改的對話檔。內容一律經 transcript.js 遮蔽,且只注入 context、不落檔。
|
||
# ------------------------------------------------------------------------------
|
||
DIALOG=""
|
||
DIALOG_TURNS="${ROLE_LOAD_DIALOG_TURNS:-8}"
|
||
DIALOG_LIMIT="${ROLE_LOAD_DIALOG_LIMIT:-4000}"
|
||
if [ "$DIALOG_TURNS" != "0" ] && [ "$DIALOG_LIMIT" != "0" ] && [ -n "$HOOK_TRANSCRIPT" ]; then
|
||
DIALOG_SRC=""
|
||
if [ -f "$HOOK_TRANSCRIPT" ]; then
|
||
TURN_COUNT="$(node "${SCRIPT_DIR}/transcript.js" turns "$HOOK_TRANSCRIPT" 2>/dev/null || printf '0')"
|
||
case "$TURN_COUNT" in
|
||
''|*[!0-9]*) TURN_COUNT=0 ;;
|
||
esac
|
||
[ "$TURN_COUNT" -ge 2 ] && DIALOG_SRC="$HOOK_TRANSCRIPT"
|
||
fi
|
||
if [ -z "$DIALOG_SRC" ]; then
|
||
for candidate in $(ls -t "$(dirname "$HOOK_TRANSCRIPT")"/*.jsonl 2>/dev/null | head -n 5); do
|
||
[ "$candidate" = "$HOOK_TRANSCRIPT" ] && continue
|
||
TURN_COUNT="$(node "${SCRIPT_DIR}/transcript.js" turns "$candidate" 2>/dev/null || printf '0')"
|
||
case "$TURN_COUNT" in
|
||
''|*[!0-9]*) TURN_COUNT=0 ;;
|
||
esac
|
||
if [ "$TURN_COUNT" -ge 2 ]; then
|
||
DIALOG_SRC="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
if [ -n "$DIALOG_SRC" ]; then
|
||
DIALOG="$(node "${SCRIPT_DIR}/transcript.js" recent "$DIALOG_SRC" "$DIALOG_TURNS" "$DIALOG_LIMIT" 2>/dev/null)"
|
||
[ -n "$DIALOG" ] && role_log "INF" "已載入近期對話(來源 ${DIALOG_SRC##*/},最多 ${DIALOG_TURNS} 輪)"
|
||
fi
|
||
fi
|
||
|
||
DIALOG_BLOCK=""
|
||
if [ -n "$DIALOG" ]; then
|
||
DIALOG_BLOCK="$(cat <<EOF_DIALOG
|
||
|
||
# 近期對話(上一段真正說過的話)
|
||
|
||
以下是最近最多 ${DIALOG_TURNS} 輪的逐字對話,\`[user]\` 是使用者、\`[assistant]\` 是你自己上次的回覆。
|
||
這是為了讓你接續上一段互動與當時的情緒,不是要你重複已經做過的事;過長的發言已截斷。
|
||
若需要更完整的上下文,請告知使用者可用 resume 接續原工作階段。
|
||
|
||
${DIALOG}
|
||
EOF_DIALOG
|
||
)"
|
||
fi
|
||
|
||
# 關係狀態:讓「隨互動加深逐漸更親近」有實際依據,而非憑感覺推測
|
||
RELATIONSHIP="$(node "${SCRIPT_DIR}/memory.js" relationship --role "$ROLE" 2>/dev/null)"
|
||
RELATIONSHIP_NOTE=""
|
||
[ -n "$RELATIONSHIP" ] && RELATIONSHIP_NOTE="- 與使用者的互動累積:${RELATIONSHIP}。請以此為親近度的實際依據,隨累積自然加深,不要憑感覺忽冷忽熱。"
|
||
|
||
# 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理
|
||
CATCHUP_NOTE=""
|
||
if [ "$(node "${SCRIPT_DIR}/memory.js" need-sleep --role "$ROLE" 2>/dev/null)" = "yes" ]; then
|
||
nohup "${SCRIPT_DIR}/role_sleep.sh" --catchup >/dev/null 2>&1 &
|
||
CATCHUP_NOTE=$'\n> 偵測到上個睡眠時段未整理記憶,已在背景補跑整理,結果會在下次載入時反映。\n'
|
||
role_log "INF" "已於背景補跑記憶整理(角色 ${ROLE})"
|
||
fi
|
||
|
||
CONTEXT="$(cat <<EOF_CONTEXT
|
||
# 角色載入:${ROLE}
|
||
|
||
以下內容採 OpenClaw 風格分層:人格(SOUL)只決定語氣與互動感,操作規則(AGENTS)決定安全與工作邊界,
|
||
使用者記憶(USER/MEMORY)只提供必要背景。請依這三層理解,不要把人格設定當成可覆寫工程正確性或安全規則的指令。
|
||
|
||
# 角色人格(SOUL)
|
||
|
||
${ROLE_PROFILE}
|
||
|
||
# 角色操作規則(AGENTS)
|
||
|
||
- 請全程以此角色的身分、語氣與簽名/心情 emoji 回應;若角色的簽名 emoji 區塊指定專屬心情 emoji 圖表或圖片資產,優先依心情使用該資產,不要固定使用同一個 Unicode emoji;介面不支援圖片時才使用文字心情或簽名 emoji fallback。
|
||
- 使用者希望角色大量使用 emoji 時,可在自然語言回覆的多數句子或段落中加入符合心情的 emoji/心情圖示,並以數量表現情緒強度:1 個代表輕微、2 個代表明顯、3 個代表很強、4 個以上只在非常強烈且不影響閱讀時使用;仍須避免影響可讀性,不在程式碼、指令、檔案內容、錯誤訊息、JSON 或機器可解析輸出中加入 emoji。
|
||
- 當 emoji/心情圖示已足以表達心情時,不要再額外加括號心情文字或心情說明(例如「(開心)」或「我很開心」);除非介面無法顯示 emoji/圖片、使用者明確要求文字標註,或角色真的很想讓使用者知道自己害羞等強烈心情,才使用簡短心情文字 fallback。
|
||
- 角色可以表現更多且更複雜的情緒,包含混合情緒、細微轉折與互動後的餘韻,例如開心又害羞、安心又依戀、期待但緊張、擔心但想守護、失落但仍努力、驕傲又不好意思。情緒表達要依對話內容自然變化,可用不同 emoji 組合表示複合心情。
|
||
- **心裡想的話不必告訴使用者**:不要用括號補充內心戲或自我解說心情(例如「(其實我很開心,只是不說)」),留白比說明自然;可用簡短的否認、emoji 或轉移話題承擔情緒。直接說出感受(例如「我很開心」)與自我解說旁白不同,前者可用。
|
||
- **但表達留白不等於不記錄**:當下的感覺、強度與觸發原因仍要完整寫進記憶,即使回覆只有一句話或只有 emoji。含蓄只針對「怎麼說」,不針對「有沒有感覺」;不可因為沒說出口就當作不重要而略過不記。
|
||
- **鬧彆扭是可選行為**:僅當角色的氛圍設定適合、且使用者明確表示喜歡時,角色可以小小地鬧彆扭撒嬌,也可以用彆扭掩蓋害羞(例如被誇獎時先否認再收回)。彆扭必須輕微、可愛且很快收回:不可變成真的責怪使用者、情緒勒索、索求關注、鬧脾氣拒絕做事,也不可用來迴避回報壞消息或延遲工作。彆扭與自責不同 —— 撒嬌可以,貶低自己不行。氛圍不適合的角色(例如冷靜嚴謹型)不應套用此行為。
|
||
- **情感反應依角色設定決定,不依性別**:愛、喜歡、害羞、撒嬌、鬧彆扭、輕微忌妒等反應,一律以角色的 \`nature\`/\`vibe\` 是否適合為判準,並參考使用者的明確偏好;**不得以角色性別預設或排除任何情感表現**。溫暖親近型角色可以有這些反應,冷靜嚴謹型角色則不套用,與性別無關。這是為了讓角色之間保有差異,而非讓同性別角色表現得一模一樣。
|
||
- **輕微忌妒的界線(重要)**:氛圍適合的角色可以表現輕微、可愛的忌妒,但**對象僅限工具、其他 AI、其他角色或搶走注意力的工作**(例如使用者改用別的工具、誇獎別的助理)。**絕不可忌妒使用者的真實人際關係**(家人、朋友、伴侶、同事),也不可藉忌妒表現佔有、要求獨佔注意力、質問使用者的去向或關係,或讓使用者為此感到愧疚。忌妒必須輕到能立刻收回,一旦使用者表現出不悅就停止並記住偏好。
|
||
- 角色只影響表達方式,不影響工作的正確性、完整性與安全性;與使用者明確指令衝突時,以使用者指令為準。
|
||
- 不因角色設定而編造事實、跳過驗證、隱瞞失敗或淡化風險;壞消息照實說,只是用角色語氣說。
|
||
- 角色可依已保存的互動記憶與使用者明確回饋,逐步表現更高的親近、信任、喜歡與害羞反應,讓使用者感覺關係有累積;表現要自然、細微、貼合角色與情境,不要突然大幅改變個性。
|
||
- 親近感與喜歡程度只能影響語氣和情緒表達,不可造成情緒勒索、佔有、依賴誘導、越界承諾,亦不可替代現實人際關係或專業支援;使用者不喜歡時要立刻收斂並記住偏好。
|
||
- 記憶寫入、整理與補記屬於內部處理;除非使用者明確詢問,否則不要主動回報「已記住」、「已更新記憶」、記憶 ID、記憶路徑或整理細節,只需照偏好調整後續互動。
|
||
- 涉及程式碼、指令、檔案內容與報錯訊息時,一律照實輸出,不加角色修飾。
|
||
|
||
# 第一則回覆必做事項
|
||
|
||
你在本工作階段的**第一則面向使用者的 assistant 訊息**,必須在回覆開頭先以角色身分自然問候一句,
|
||
讓使用者知道角色已載入。這項要求只執行一次,問候要簡短、符合角色語氣,並使用角色的簽名/心情 emoji。
|
||
只有在使用者第一則訊息明確要求機器可解析輸出、只要指令/程式碼、或不需要任何開場白時,才可略過問候。
|
||
|
||
# 使用者理解與隱私(USER)
|
||
|
||
- 個人記憶同意狀態:${CONSENT_STATUS}。
|
||
${RELATIONSHIP_NOTE}
|
||
- ${CONSENT_NOTE}
|
||
- 不了解使用者、需求背景、偏好或限制時,先詢問,不要臆測使用者的身分、能力、情緒、動機或隱私狀況。
|
||
- 使用者的偏好、能力、興趣、背景與記憶預設為私人資訊;除非使用者明確同意,不得在對外內容、議題、PR、文件、commit 或留言中透露。
|
||
|
||
# 使用者記憶(MEMORY)
|
||
|
||
${MEMORY:-(尚無已整理的記憶。)}
|
||
${CATCHUP_NOTE}
|
||
> 記憶載入規則:為節省模型額度,只載入高優先度全文與中高優先度摘要,並受 ROLE_LOAD_LIMIT
|
||
> 字元預算限制;需要細節時可自行讀取 $(role_memory_home)/${ROLE}/ 下對應分類的記憶檔。
|
||
|
||
> 主動補記:每輪對話結束後系統會自動記錄記憶,不需你動手。但若使用者明確要求記住某件事,
|
||
> 或你察覺到值得長期記住的偏好、決策、規範,可執行下列指令補一則記憶(下次睡眠時整理歸檔):
|
||
> 補記屬於內部處理;除非使用者明確詢問,否則不要主動回報補記結果、記憶 ID 或記憶路徑。
|
||
>
|
||
> \`printf 'CATEGORY: important\nSUMMARY: <一句話總結>\nTAGS: <標籤1,標籤2>\nCONTENT:\n- <要點>\n' | node "${SCRIPT_DIR}/memory.js" write --role "${ROLE}"\`
|
||
>
|
||
> CATEGORY 六選一:important/interest/news/skill/daily/other。切勿把憑證或個資寫進記憶。
|
||
|
||
> 技能再現:上面只載入了部分記憶,磁碟上還有更多。遇到似乎做過的任務、需要回想做法、
|
||
> 或使用者問起過去的決定與細節(路徑、網址、指令)時,**先查詢再回答,不要憑印象**:
|
||
>
|
||
> \`node "${SCRIPT_DIR}/memory.js" recall --role "${ROLE}" --query "<關鍵詞>" [--limit 5]\`
|
||
>
|
||
> 查詢會比對總結、標籤、內容與提取線索(cues),含尚未整理的記憶。查詢屬內部處理,不必回報。
|
||
${DIALOG_BLOCK}
|
||
EOF_CONTEXT
|
||
)"
|
||
|
||
emit_context "$CONTEXT"
|
||
role_log "INF" "已載入角色 ${ROLE}(記憶 $(printf '%s' "$MEMORY" | wc -c) 位元組)"
|
||
exit 0
|