// memdir.ts
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000 // ~125 chars/line × 200 linesMEMORY.md 是「索引文件」而非記憶本身,每個條目格式為:
- [Title](file.md) — one-line hook(限 ~150 chars)
每個記憶文件採用 YAML frontmatter:
---
name: {{memory name}}
description: {{one-line description — used to decide relevance in future conversations, so be specific}}
type: {{user, feedback, project, reference}}
---
{{memory content — for feedback/project types, structure as: rule/fact, then **Why:** and **How to apply:** lines}}description 欄位極為關鍵:它是 findRelevantMemories 篩選時的主要依據,必須足夠具體讓 LLM 判斷相關性。
export function truncateEntrypointContent(raw: string): EntrypointTruncation {
// 行截斷優先(自然邊界),再做位元組截斷(避免切斷中途行)
let truncated = wasLineTruncated
? contentLines.slice(0, MAX_ENTRYPOINT_LINES).join('\n')
: trimmed
if (truncated.length > MAX_ENTRYPOINT_BYTES) {
const cutAt = truncated.lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)
truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES)
}
// 附加警告說明觸發原因
}截斷時會同時偵測「行超限」與「位元組超限」,警告訊息精確說明哪個上限被觸發,例:
197KB (limit: 25KB) — index entries are too long287 lines (limit: 200)
buildMemoryLines() 組裝的 prompt 結構:
# auto memory(或自訂 displayName)- 記憶目錄路徑 +
DIR_EXISTS_GUIDANCE - 記憶系統目的說明
- 四種記憶類型區塊(
TYPES_SECTION_INDIVIDUAL) ## What NOT to save in memory## How to save memories(兩步驟流程)## When to access memories## Before recommending from memory(新鮮度驗證)- 記憶 vs 其他持久化機制的使用時機
## Searching past context(若 feature flag 啟用)
buildMemoryPrompt() 在此基礎上追加 MEMORY.md 的實際內容。
skipIndex 模式(tengu_moth_copse flag):省略 MEMORY.md 索引步驟,模型直接寫記憶文件,不維護 index。
1. CLAUDE_CODE_DISABLE_AUTO_MEMORY env var → true = 關閉
2. CLAUDE_CODE_SIMPLE (--bare) → 關閉
3. CCR 模式無 CLAUDE_CODE_REMOTE_MEMORY_DIR → 關閉
4. settings.json autoMemoryEnabled 欄位
5. 預設:啟用
1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE env var
2. settings.json autoMemoryDirectory(policy > flag > local > user)
3. {memoryBase}/projects/{sanitizePath(gitRoot)}/memory/
安全驗證(validateMemoryPath)拒絕:
- 非絕對路徑
- 長度 < 3(
/根目錄) - Windows drive root(
C:) - UNC paths(
\\server\share) - null bytes
export function getAutoMemDailyLogPath(date: Date = new Date()): string {
return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)
}KAIROS(長期存活 assistant session)改為 append-only 模式:每天追加到日誌,不維護 MEMORY.md;由夜間 /dream 技能蒸餾成主題文件。
// 簡單日數計算,負值夾零(應對時鐘偏移)
export function memoryAgeDays(mtimeMs: number): number {
return Math.max(0, Math.floor((Date.now() - mtimeMs) / 86_400_000))
}
// 人類可讀:today / yesterday / N days ago
export function memoryAge(mtimeMs: number): string { ... }
// 過期警告文字(>1 天才顯示)
export function memoryFreshnessText(mtimeMs: number): string {
// "This memory is N days old. Memories are point-in-time observations..."
}
// 包在 <system-reminder> 的過期警告
export function memoryFreshnessNote(mtimeMs: number): string { ... }設計原因:模型不擅長日期計算,「47 days ago」比原始 ISO 時間戳更能觸發過期推理。
export async function scanMemoryFiles(
memoryDir: string,
signal: AbortSignal,
): Promise<MemoryHeader[]> {
// 遞迴讀取所有 .md,排除 MEMORY.md
// 只讀前 30 行(FRONTMATTER_MAX_LINES)取 frontmatter
// 依 mtime 降序排序(最新優先),限 200 個
}
export function formatMemoryManifest(memories: MemoryHeader[]): string {
// 每行格式:- [type] filename (ISO timestamp): description
}設計亮點:單一掃描讀取 frontmatter 同時取 mtime,避免先 stat 再讀的雙倍 syscall。
export async function findRelevantMemories(
query: string,
memoryDir: string,
signal: AbortSignal,
recentTools: readonly string[] = [],
alreadySurfaced: ReadonlySet<string> = new Set(),
): Promise<RelevantMemory[]>工作流程:
scanMemoryFiles()掃描所有記憶的 frontmatter- 過濾已顯示的(
alreadySurfaced,避免重複推薦) - 呼叫 Sonnet sideQuery(
querySource: 'memdir_relevance'),請 LLM 從 manifest 選出最相關的(最多 5 個) - 回傳
{ path, mtimeMs }清單
去雜訊設計:recentTools 參數——若模型已在使用某工具,不推薦該工具的參考文件(避免噪音);但仍推薦有關該工具已知問題的記憶(活躍使用時正是需要知道 gotchas 的時候)。
export async function loadMemoryPrompt(): Promise<string | null> {
// 1. KAIROS 模式 → buildAssistantDailyLogPrompt()
// 2. TEAMMEM + 啟用 → buildCombinedMemoryPrompt()(team + auto 雙目錄)
// 3. auto 啟用 → buildMemoryLines()(單目錄)
// 4. 全部關閉 → null(記錄 telemetry)
}export const DIR_EXISTS_GUIDANCE =
'This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence).'解決「模型浪費 turn 做 ls/mkdir」的問題:harness 在 ensureMemoryDirExists() 中確保目錄存在,prompt 中告知模型可直接寫。
export async function ensureMemoryDirExists(memoryDir: string): Promise<void> {
const fs = getFsImplementation()
try {
await fs.mkdir(memoryDir)
} catch (e) {
// EEXIST 已由 fs.mkdir 處理;真正的錯誤(EACCES 等)記 debug log
// prompt building 繼續進行,Write tool 會顯示真正的 perm 錯誤
}
}Prompt 中明確說明何時用記憶,何時用其他機制:
- Plan:非瑣碎的實作任務前,與用戶對齊方法論 → 用 Plan,不用 memory
- Tasks:分解當前對話的步驟、追蹤進度 → 用 Tasks,不用 memory
- Memory:對未來對話有用的資訊
export const TRUSTING_RECALL_SECTION: readonly string[] = [
'## Before recommending from memory',
// H1 (eval 2026-03-17): 0/2 → 3/3,透過 appendSystemPrompt 實現
// 標題「Before recommending」(行動提示點)比「Trusting what you recall」(抽象)好 3/3 vs 0/3
'- If the memory names a file path: check the file exists.',
'- If the memory names a function or flag: grep for it.',
'"The memory says X exists" is not the same as "X exists now."',
]關鍵設計:標題措辭經 eval 驗證——「行動提示點」定位(在模型即將推薦之前)比「抽象標題」更能激活正確行為。