memory v2,结构化记忆memory md文件

This commit is contained in:
hc 2026-04-13 16:48:22 +08:00
parent 18ab3cabd5
commit 4efc3d267c
4 changed files with 59 additions and 13 deletions

View File

@ -24,7 +24,8 @@
## Memory 与验证 ## Memory 与验证
- 当前项目支持最小 memory使用 `/remember` 保存长期信息,使用 `/memory` 查看。 - `AGENTS.md` 是项目规则文件,不用于保存长期项目知识或用户偏好。
- 当前项目支持结构化 memory使用 `/remember` 保存长期知识,使用 `/memory` 查看。
- session 是原始对话历史,不直接拼进 system promptmemory 才作为长期补充进入 prompt。 - session 是原始对话历史,不直接拼进 system promptmemory 才作为长期补充进入 prompt。
- 默认语言:中文优先。 - 默认语言:中文优先。
- 默认验证:优先做最小可验证检查,不夸大成功状态。 - 默认验证:优先做最小可验证检查,不夸大成功状态。

View File

@ -26,7 +26,8 @@
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash` - 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
- 会话与 memory 管理优先使用 slash command而不是自然语言或 `Bash` 探查对应文件 - 会话与 memory 管理优先使用 slash command而不是自然语言或 `Bash` 探查对应文件
- 优先通过 `/help` 查看当前命令,而不是猜测命令格式 - 优先通过 `/help` 查看当前命令,而不是猜测命令格式
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存 - 长期有效的项目知识或用户偏好,优先使用 `/remember` 保存
- 项目规则和工作方式仍应写在 `AGENTS.md`
- 高风险操作会触发确认,优先先读再改,减少无意义审批 - 高风险操作会触发确认,优先先读再改,减少无意义审批
- 复杂任务可先切换到 `/mode plan` 进行规划,再切换回 `/mode build` 执行 - 复杂任务可先切换到 `/mode plan` 进行规划,再切换回 `/mode build` 执行
- workspace 是当前默认操作边界,先在 workspace 内定位和操作,避免无关路径探索 - workspace 是当前默认操作边界,先在 workspace 内定位和操作,避免无关路径探索

View File

@ -216,7 +216,7 @@ class Agent:
memory = MemoryStore(workspace).read() memory = MemoryStore(workspace).read()
if not memory: if not memory:
return "" return ""
return f"# Memory\n\n{memory}" return memory
def _build_runtime_summary(self, workspace: Path) -> str: def _build_runtime_summary(self, workspace: Path) -> str:
tool_names = ", ".join(self.tools.keys()) or "(none)" tool_names = ", ".join(self.tools.keys()) or "(none)"

View File

@ -10,23 +10,67 @@ class MemoryStore:
self.cwd = cwd.resolve() self.cwd = cwd.resolve()
self.root = Path.home() / ".config" / "cc-slim" / "memory" / self._sanitize_cwd(self.cwd) self.root = Path.home() / ".config" / "cc-slim" / "memory" / self._sanitize_cwd(self.cwd)
self.root.mkdir(parents=True, exist_ok=True) self.root.mkdir(parents=True, exist_ok=True)
self.ensure_structure()
def append(self, text: str) -> Path: def ensure_structure(self) -> None:
path = self.path()
existing = self.read()
content = f"{existing}\n\n{text.strip()}" if existing else text.strip()
path.write_text(content + "\n", encoding="utf-8")
return path
def read(self) -> str:
path = self.path() path = self.path()
if not path.exists(): if not path.exists():
return "" path.write_text(self._default_template(), encoding="utf-8")
return path.read_text(encoding="utf-8").strip() return
existing = path.read_text(encoding="utf-8").strip()
if self._is_structured(existing):
return
migrated = self._default_template(existing)
path.write_text(migrated, encoding="utf-8")
def append(self, text: str) -> str:
content = self.read().rstrip()
entry = self._format_scratch_entry(text)
updated = f"{content}\n\n{entry}\n"
self.path().write_text(updated, encoding="utf-8")
return "Scratch Notes"
def read(self) -> str:
self.ensure_structure()
return self.path().read_text(encoding="utf-8").strip()
def path(self) -> Path: def path(self) -> Path:
return self.root / "MEMORY.md" return self.root / "MEMORY.md"
def _default_template(self, scratch_notes: str = "") -> str:
scratch = self._format_scratch_entry(scratch_notes) if scratch_notes.strip() else ""
template = (
"# Memory\n\n"
"## User Memory\n\n"
"## Project Memory\n\n"
"## Constraints\n\n"
"## Consolidated Facts\n\n"
"## Scratch Notes\n"
)
return f"{template}\n{scratch}\n" if scratch else f"{template}\n"
def _format_scratch_entry(self, text: str) -> str:
lines = [line.rstrip() for line in text.strip().splitlines() if line.strip()]
if not lines:
return ""
if len(lines) == 1:
return f"- {lines[0]}"
tail = "\n".join(f" {line}" for line in lines[1:])
return f"- {lines[0]}\n{tail}"
def _is_structured(self, text: str) -> bool:
required_sections = [
"# Memory",
"## User Memory",
"## Project Memory",
"## Constraints",
"## Consolidated Facts",
"## Scratch Notes",
]
return all(section in text for section in required_sections)
def _sanitize_cwd(self, cwd: Path) -> str: def _sanitize_cwd(self, cwd: Path) -> str:
text = re.sub(r"[^A-Za-z0-9._-]+", "_", str(cwd)) text = re.sub(r"[^A-Za-z0-9._-]+", "_", str(cwd))
digest = hashlib.sha1(str(cwd).encode("utf-8")).hexdigest()[:8] digest = hashlib.sha1(str(cwd).encode("utf-8")).hexdigest()[:8]