[控场AI]
· 7 分钟阅读· 3,544 字

AI代理日志撑爆上下文?用Rust数据库替代Markdown

AI代理日志撑爆上下文?用Rust数据库替代Markdown

AppSec工程师用Rust写MCP服务器PentaCore,以加密SQLite替代Markdown存储代理状态。

文章介绍AppSec工程师构建的PentaCore:一个用Rust编写、以加密SQLite替代Markdown作为AI代理状态存储的MCP服务器。作者指出Markdown日志在长任务中会撑爆256k上下文窗口,导致压缩后进度丢失、重复失败尝试、子代理与主代理决策脱节,且“还剩什么”无法快速回答。PentaCore采用双存储设计——笔记树承载叙事与决策,实体表承载结构化查询——并通过resume、summarize、task_summary等工具实现压缩后低成本恢复、子代理按需取上下文和摘要替代累积历史。安全上强调记忆即提示注入通道,采取默认stdio、HTTP严格限制、输入验证、参数化查询与SQLCipher加密,同时坦承密钥内嵌等局限。

AI代理在执行长任务时,日志文件越积越多,最终连256k token的上下文窗口都装不下。一位AppSec工程师为此构建了PentaCore——一个用Rust编写的MCP服务器,用加密SQLite替代Markdown作为代理的状态存储。

AI代理日志管理

问题:Markdown作为状态存储的局限

这位工程师在安全工作中使用AI代理进行侦察、攻击面映射和假设测试。每个项目运行几周后,日志文件夹里塞满了recon.md、endpoints.md、attempts-auth.md、progress-subagent-3.md等文件。虽然模型拥有256k token的上下文窗口,但累积的日志最终无法全部装入。

代理为了回忆进度,不得不读取整个档案,结果没有剩余空间执行实际工作。具体症状包括:

  • 压缩擦除进度:上下文压缩后,代理重读progress.md,看到“invoices模块 - 几乎完成”,然后从头开始。“几乎”到底意味着什么,谁也说不清。
  • 重复失败尝试:某些假设代理尝试了三次,每次都像第一次一样。
  • 子代理不了解主代理决策:范围约束存在于主代理的上下文中,子代理毫不知情。
  • “还剩什么?”没有快速答案:必须阅读每个文件并手动重建全局图景。

Markdown文件适合人类阅读,但作为运行中代理的状态存储则不合格:没有结构,状态含义不一致,且无法查询。

上下文压缩(context compaction) 是长会话代理系统常用的机制:当对话历史接近模型上下文窗口上限时,系统自动将较早的内容摘要或丢弃,以腾出空间继续推理。问题在于这种压缩是有损且不可逆的——原始细节消失,摘要往往丢失关键限定词。对于把状态写在Markdown里的代理,压缩意味着它下次读取progress.md时只看到模糊描述,无法还原“几乎完成”具体指哪一步、还差哪些检查项,于是产生重复劳动。压缩本身不是缺陷,缺陷是状态存储依赖不可查询的自然语言文本,使压缩后的恢复只能靠重读全文,进一步挤占本就紧张的token预算。

需求:六个关键要求

作者列出了明确的需求清单:

  1. 用最少的token恢复完整上下文——在新会话开始或生成子代理时。
  2. “还剩什么?”必须是一次查询,而非读取历史。
  3. 失败的尝试被记录,并在代理重复之前被发现。
  4. 顶层做出的决策能传达给每个子代理,无需重述整个对话。
  5. 完成的工作折叠为摘要——下一个会话读取摘要,而非上百条笔记。
  6. 一切本地运行,无外部服务,项目数据留在机器上。

由于没找到合适的工具,作者构建了PentaCore:一个Rust编写的MCP服务器,单一二进制文件,一个加密SQLite文件。它不规划任务或决定下一步,只存储给定内容并返回请求内容。

设计:双存储与金字塔结构

PentaCore有两个存储:

笔记(Notes) 是叙事性内容:目标、步骤、尝试及其结果、事实、决策、问题、经验教训。笔记通过parent形成树,通过类型化链接形成图。搜索支持精确词和语义(内置all-MiniLM-L6-v2嵌入)。

实体(Entities) 是同一类型的多个对象:主机、端点、文件等。每个实体有状态、属性和命名检查。

这种分离很重要。“显示所有尚未通过检查X的内容”是字段过滤,其结果不应取决于代理如何表述笔记。

顶层是目标,下面是步骤,每个步骤有检查清单。每个端点是一个实体,检查完成后标记。现在,“尚未检查什么”只需一次调用query_entities,整体视图也只需另一次调用。

压缩后恢复上下文

新会话开始时,一次调用resume返回:最后检查点、进行中的工作、阻塞项、失败尝试、已做决策以及所有未完成的检查清单项。响应适配可配置的token预算,不会静默截断。

子代理只获得所需上下文

主代理为子代理创建步骤,并通过uses链接相关决策。子代理只获得步骤ID,调用resume task=<id>即可获得任务分配、范围决策以及已检查相邻区域的摘要。无需重述主代理的整个对话。

摘要替代累积历史

步骤完成后,调用summarize记录工作和结果。task_summary按树顺序返回整个任务的摘要——无论下面有多少笔记,一次调用即可。层级中任何级别的代理都能获取整个周界某一区域的聚焦摘要。

MCP(Model Context Protocol) 是让AI应用以统一方式连接外部工具和数据源的开放协议。MCP服务器暴露一组工具(tools)、资源(resources)和提示(prompts),客户端(如Claude Code或其他代理运行时)在需要时调用。PentaCore作为MCP服务器,意味着它不嵌入代理推理循环,而是以标准接口提供resume、summarize、query_entities等能力,代理按需调用、按token预算取回结构化状态。这种解耦让同一状态存储可被不同客户端和子代理复用,也让安全边界更清晰:服务器只负责存取,不参与决策。

all-MiniLM-L6-v2 是一个轻量级句子嵌入模型,约22M参数,将文本映射为384维向量,常用于语义搜索和相似度匹配。PentaCore内置该模型,使笔记搜索同时支持精确词匹配(如FTS5全文索引)和语义近邻检索:代理不必记得当初用了哪个词,只要意思相近就能找回相关笔记。本地运行嵌入模型也契合“数据不出机器”的要求,无需调用外部向量化API,代价是首次加载模型和建索引带来一定启动开销与二进制体积增长。

安全:记忆是提示注入通道

作者强调:记忆是提示注入通道。写入记忆的所有内容稍后都会被作为上下文读取。未经授权的写入会变成注入的指令。

因此采取以下措施:

  • 默认仅stdio:服务器只能由启动它的进程调用。
  • HTTP不会意外暴露:仅回环地址,没有至少32字符的令牌则拒绝启动,要求精确的Host头以阻止DNS重绑定,拒绝任何带有Origin头的请求。
  • 边界输入验证:每个参数解析为带有长度和字符集约束的类型,拒绝未知字段。
  • 用户文本永不成为SQL:仅使用绑定参数,转义FTS5操作符,实体查询由字段和操作符的白名单组装。
  • 静态数据加密:SQLCipher,secure_delete,清除后清理WAL。

作者坦承局限:SQLCipher密钥编译在二进制文件中。它保护文件免受仅拥有文件的人访问,但不保护拥有二进制或源代码的人。所有已知限制都在README中列出。

DNS重绑定(DNS rebinding) 是一种针对本地HTTP服务的攻击:恶意网页先让浏览器解析攻击者域名到其服务器,再快速切换DNS使其解析到127.0.0.1,从而绕过同源策略向本机端口发起请求。PentaCore的防御是校验精确的Host头,确保请求确实指向预期主机名而非被重绑定的域名,同时拒绝带Origin头的请求以阻断浏览器发起的跨站调用。这类加固对任何监听回环地址的本地代理服务都必要,因为“只绑127.0.0.1”并不足以阻止浏览器作为跳板。

何时不适合使用PentaCore

  • 需要依赖关系的软件开发任务跟踪器:考虑Beads或Claude Code内置任务。
  • 单个会话中的短任务:模型上下文足够。
  • 关于用户或其偏好的长期记忆:不同问题,不同工具。

PentaCore适用于代理在多个会话中处理大量类似目标、经历压缩,并且需要精确回答“完成了什么、发现了什么、还剩什么”的场景。

试用

Linux、macOS和Windows的二进制文件在Releases中提供,包含SHA256SUMS、SBOM和构建来源证明。可通过claude mcp add pentacore -- /path/to/pentacore或任何MCP客户端配置使用。Docker镜像可用,具有只读根文件系统和丢弃的能力。

仓库地址:https://github.com/DroBov1Tya/PentaCore

如果发现漏洞,请使用仓库中的Security -> Report a vulnerability,而不是公开issue。

分享:

相关推荐