add memory v1,只支持手动记忆
This commit is contained in:
parent
192e6a56da
commit
e9cf2e3533
@ -29,6 +29,8 @@ REPL 内的会话管理命令优先使用 slash command。
|
|||||||
|
|
||||||
`/history`、`/resume`、`/new` 由程序直接处理,不进入 agent loop。
|
`/history`、`/resume`、`/new` 由程序直接处理,不进入 agent loop。
|
||||||
|
|
||||||
|
当前项目支持最小 memory:使用 `/remember` 保存长期信息,使用 `/memory` 查看。
|
||||||
|
|
||||||
未明确说明时,使用以下默认值:
|
未明确说明时,使用以下默认值:
|
||||||
|
|
||||||
- 工作目录:当前进程目录
|
- 工作目录:当前进程目录
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
- 修改已有文件时,优先使用 `Edit`,而不是 `Bash` 或 `Write`
|
- 修改已有文件时,优先使用 `Edit`,而不是 `Bash` 或 `Write`
|
||||||
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
|
- 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash`
|
||||||
- 会话管理优先使用 slash command,而不是自然语言或 `Bash` 探查 session 文件
|
- 会话管理优先使用 slash command,而不是自然语言或 `Bash` 探查 session 文件
|
||||||
|
- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存
|
||||||
- Windows 下先验证 shell 兼容性,再选择命令写法
|
- Windows 下先验证 shell 兼容性,再选择命令写法
|
||||||
|
|
||||||
## 沟通风格
|
## 沟通风格
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from typing import Any, Iterator
|
|||||||
from anthropic import Anthropic
|
from anthropic import Anthropic
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
||||||
|
from cc_slim.memory import MemoryStore
|
||||||
from cc_slim.session import SessionStore
|
from cc_slim.session import SessionStore
|
||||||
from cc_slim.tools import Tool
|
from cc_slim.tools import Tool
|
||||||
|
|
||||||
@ -148,6 +149,10 @@ class Agent:
|
|||||||
for path in sorted(skills_dir.glob("*.md"), key=lambda p: p.name):
|
for path in sorted(skills_dir.glob("*.md"), key=lambda p: p.name):
|
||||||
parts.append(path.read_text(encoding="utf-8"))
|
parts.append(path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
memory = MemoryStore(workspace).read()
|
||||||
|
if memory:
|
||||||
|
parts.append(f"# Memory\n\n{memory}")
|
||||||
|
|
||||||
return "\n\n".join(part.strip() for part in parts if part.strip())
|
return "\n\n".join(part.strip() for part in parts if part.strip())
|
||||||
|
|
||||||
def _build_runtime_summary(self, workspace: Path) -> str:
|
def _build_runtime_summary(self, workspace: Path) -> str:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from rich.console import Console
|
|||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
from cc_slim.engine import Agent, resolve_config
|
from cc_slim.engine import Agent, resolve_config
|
||||||
|
from cc_slim.memory import MemoryStore
|
||||||
from cc_slim.session import SessionStore
|
from cc_slim.session import SessionStore
|
||||||
from cc_slim.tools import build_default_tools
|
from cc_slim.tools import build_default_tools
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ def build_agent(
|
|||||||
def handle_repl_command(
|
def handle_repl_command(
|
||||||
user_input: str,
|
user_input: str,
|
||||||
store: SessionStore,
|
store: SessionStore,
|
||||||
|
memory: MemoryStore,
|
||||||
config: object,
|
config: object,
|
||||||
root: Path,
|
root: Path,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
@ -95,6 +97,20 @@ def handle_repl_command(
|
|||||||
console.print(f"已创建新 session: {session_meta.get('session_id')}")
|
console.print(f"已创建新 session: {session_meta.get('session_id')}")
|
||||||
return build_agent(root, config, store, session_meta, [])
|
return build_agent(root, config, store, session_meta, [])
|
||||||
|
|
||||||
|
if command == "/memory":
|
||||||
|
content = memory.read()
|
||||||
|
console.print(content or "当前项目还没有 memory。")
|
||||||
|
return agent
|
||||||
|
|
||||||
|
if command.startswith("/remember "):
|
||||||
|
text = user_input.split(" ", 1)[1].strip()
|
||||||
|
if not text:
|
||||||
|
console.print("[red]error:[/red] 缺少需要保存的 memory 内容")
|
||||||
|
return agent
|
||||||
|
path = memory.append(text)
|
||||||
|
console.print(f"已写入 memory: {path}")
|
||||||
|
return agent
|
||||||
|
|
||||||
if command.startswith("--resume ") or command.startswith("/resume "):
|
if command.startswith("--resume ") or command.startswith("/resume "):
|
||||||
target = command.split(maxsplit=1)[1].strip()
|
target = command.split(maxsplit=1)[1].strip()
|
||||||
if not target:
|
if not target:
|
||||||
@ -123,6 +139,7 @@ def run(
|
|||||||
) -> None:
|
) -> None:
|
||||||
root = cwd.resolve()
|
root = cwd.resolve()
|
||||||
store = SessionStore(root)
|
store = SessionStore(root)
|
||||||
|
memory = MemoryStore(root)
|
||||||
if history:
|
if history:
|
||||||
render_history(store)
|
render_history(store)
|
||||||
return
|
return
|
||||||
@ -168,7 +185,7 @@ def run(
|
|||||||
|
|
||||||
if user_input.strip().startswith("/") or user_input.strip().startswith("--history") or user_input.strip().startswith("--resume"):
|
if user_input.strip().startswith("/") or user_input.strip().startswith("--history") or user_input.strip().startswith("--resume"):
|
||||||
try:
|
try:
|
||||||
agent = handle_repl_command(user_input, store, config, root, agent)
|
agent = handle_repl_command(user_input, store, memory, config, root, agent)
|
||||||
except Exception as exc: # pragma: no cover
|
except Exception as exc: # pragma: no cover
|
||||||
console.print(f"[red]error:[/red] {exc}")
|
console.print(f"[red]error:[/red] {exc}")
|
||||||
continue
|
continue
|
||||||
|
|||||||
33
src/cc_slim/memory.py
Normal file
33
src/cc_slim/memory.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryStore:
|
||||||
|
def __init__(self, cwd: Path) -> None:
|
||||||
|
self.cwd = cwd.resolve()
|
||||||
|
self.root = Path.home() / ".config" / "cc-slim" / "memory" / self._sanitize_cwd(self.cwd)
|
||||||
|
self.root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def append(self, text: str) -> Path:
|
||||||
|
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()
|
||||||
|
if not path.exists():
|
||||||
|
return ""
|
||||||
|
return path.read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
|
def path(self) -> Path:
|
||||||
|
return self.root / "MEMORY.md"
|
||||||
|
|
||||||
|
def _sanitize_cwd(self, cwd: Path) -> str:
|
||||||
|
text = re.sub(r"[^A-Za-z0-9._-]+", "_", str(cwd))
|
||||||
|
digest = hashlib.sha1(str(cwd).encode("utf-8")).hexdigest()[:8]
|
||||||
|
return f"{text[:48]}-{digest}"
|
||||||
Loading…
Reference in New Issue
Block a user