为什么 AI 代理不断忘记你的项目(以及我如何修复它)
Source: Dev.to
请提供您希望翻译的完整文本内容,我将按照要求将其翻译为简体中文并保留原始的格式、Markdown 语法以及代码块和 URL。谢谢!
PROGRESS.md 的问题
每次我启动与 AI 编码代理的新会话时,都会出现同样的情况:它根本不知道我昨天在做什么。
- 它不知道哪些任务已经完成。
- 它不知道我们决定使用 Redis 进行缓存。
- 它不记得认证模块因依赖升级而被阻塞。
于是我像大多数人一样——维护一个 PROGRESS.md 文件。每次会话结束后,我让代理更新它;在下一次会话开始时,代理读取该文件并尝试从上次中断的地方继续。
这种做法大约维持了一周。
Markdown 的问题
随着项目的扩大,文件也变得更大。
50 行变成了 200 行。三周前的状态更新和当前的阻塞事项挤在一起。代理会读取整篇内容,在上下文中消耗 3,000+ token,但仍然会错过我标记为阻塞的任务,因为它被埋在两条旧的进度记录之间。
根本问题: 我把文本文件当作数据库使用。
- 我需要查询(“有什么被阻塞?”),而不是读取整个文件。
- 我需要结构(项目 → 史诗 → 任务),而不是平铺的项目符号。
- 我需要一条不会膨胀上下文窗口的审计日志。
为 AI 代理构建项目跟踪器
于是我做了一个。Saga 是一个 MCP 服务器,为 AI 代理提供本地 SQLite 数据库用于项目跟踪——可以把它当作 Jira,但专为代理的实际工作方式设计。
如果你不熟悉 MCP(Model Context Protocol),它是 Anthropic 推出的标准,允许 AI 工具通过类型化的工具调用与外部服务交互。Claude Code、Claude Desktop、Cursor 和 Windsurf 都支持它。你的代理在启动时会发现可用的工具,并在对话过程中根据需要调用它们。
Saga 通过 MCP 暴露 23 个工具:
- 完整层级的 CRUD: 项目 → 史诗 → 任务 → 子任务
- 笔记系统: 决策、上下文、会议记录、阻塞项——类型化且可搜索
- 仪表盘: 一次调用返回整个项目状态(完成百分比、阻塞任务、最近活动)
- 活动日志: 每一次更改都会自动记录旧值/新值
- 会话差异: “显示自昨天以来的变更”——一次调用即可
所有数据都存放在单个 .tracker.db SQLite 文件中。无需服务器、API 密钥或账号。
实际工作原理
开始一个新项目
Me: "Set up tracking for the e-commerce API"
Agent calls:
tracker_init
epic_create (Auth)
epic_create (Catalog)
task_create (JWT auth)
subtask_create ([setup lib, create endpoint, add middleware])
五次工具调用。项目现在已经按照史诗(epic)、任务(task)和子任务(subtask)进行结构化,并持久化到 SQLite 中。
次日继续
Me: "What's the status?"
Agent calls: tracker_dashboard
一次调用返回:
{
"stats": {
"total_tasks": 12,
"tasks_done": 4,
"tasks_blocked": 1,
"completion_pct": 33.3
},
"blocked_tasks": [
{ "title": "Add rate limiting", "epic": "Authentication" }
],
"recent_activity": [
"Task 'JWT auth' status: in_progress → done",
"... (other entries)"
]
}
代理立刻知道:完成度 33 %,有一个任务被阻塞,认证史诗(auth epic)进度领先于目录史诗(catalog)。它可以在无需额外说明的情况下进行优先级排序。
记录决策
Me: "We're going with Redis for caching. Mark the research tasks as done."
Agent calls:
note_save (decision: Redis for caching, reasons, trade‑offs)
task_batch_update (mark tasks 8, 9 as done)
该决策被存储为结构化笔记,并关联到相应的史诗。在下一次会话中,如果代理需要了解我们为何选择 Redis,它可以直接搜索这条笔记,而无需我重新解释。
令牌数学
- Saga 的 23 个工具定义在系统提示中大约消耗 1,500 令牌。这是固定成本——不会随你的项目增长。
- 一个
tracker_dashboard调用返回约 800 令牌的结构化数据。 - 类似 “show me blocked tasks” 的过滤查询返回约 200 令牌。
相比之下,中等规模项目的 PROGRESS.md 文件占用 3,000–5,000 令牌,每次会话都会完整加载,并随时间增长。
拐点: 大约 15–20 个任务。超过此数量,结构化方法的可扩展性更好,因为代理只检索它请求的内容,而不是全部。
并且不同于 markdown 文件,数据是可查询的。 “我们对缓存的决定是什么?” 是一次 note_search 调用,而不是完整文件扫描。
引擎内部
- SQLite with WAL mode – 写入期间并发读取,锁争用时的忙等待超时为 5 秒
- Foreign keys enforced – 删除 epic 时不会出现孤立任务(级联删除)
- Append‑only activity log – 每一次创建、更新和删除都以字段粒度记录
- Parameterized queries with column allowlists – 没有 SQL 注入风险
- MCP safety annotations on every tool – 客户端可以知道每个工具是只读、破坏性还是幂等的
整个项目约有 1,400 行 TypeScript,只有两个依赖:MCP SDK 和 better-sqlite3。
会话差异:我未计划的功能
发布后,Reddit 上有人问道:“它能显示会话之间的变化吗?”
好主意。活动日志已经捕获了所有内容——只需要一个聚合层。
我添加了 tracker_session_diff。你给它一个时间戳,它会返回:
{
"total_changes": 14,
"summary": {
"created": 3,
"status_changed": 4,
"updated": 5,
"deleted": 2
},
"highlights": [
"Task 'Fix auth bug' status: in_progress → done",
"Created epic 'API v2'",
"Note 'Sprint retro' deleted"
]
}
代理可以调用它,以获得自上一次会话以来发生的事情的简明概览,从而保持对话的聚焦并提高 token 使用效率。
入门
将以下内容添加到项目的 .mcp.json 中:
{
"mcpServers": {
"saga": {
"command": "npx",
"args": ["-y", "saga-mcp"],
"env": {
"DB_PATH": "/absolute/path/to/your/project/.tracker.db"
}
}
}
}
就这样。它可在 Claude Code、Claude Desktop 或任何兼容 MCP 的客户端上使用。数据库文件会在首次使用时自动创建。
我学到的
构建 Saga 让我了解到代理实际上是如何消费信息的:它们更擅长结构化信息而非散文。
Markdown 文件针对人类浏览文档进行了优化。返回过滤后 JSON 的类型化工具调用则针对大型语言模型(LLM)决定下一步操作进行了优化。代理不需要“阅读”你的项目状态——它需要查询它。
MCP 让这变得可行。该协议处理工具发现、类型化模式和传输。我所要做的只是把一个数据库放在它后面。
如果你正在构建多会话代理工作流,并且发现自己在维护不断增长的上下文文件,考虑一下这些上下文是否应该改为使用数据库。
Saga 是开源的(MIT 许可证),并可在 npm 上获取:
- GitHub:
- Install:
npx -y saga-mcp