agenthold 是一个以 MCP 服务器形式提供的共享版本化状态存储,用于协调多智能体 AI 工作流中的并发操作。
用途 agenthold 解决的核心问题是多个智能体同时读写同一值时的静默覆盖。例如两个智能体各自读取 10000 美元预算并独立分配,总计承诺 15000 美元,预算对象却不会报错。这类“读-改-写”冲突中,第二个写入会无声摧毁第一个写入,不抛异常,系统继续运行但数据错误。agenthold 为智能体提供共享、带版本的状态与内置冲突检测。
主要能力
- 乐观并发控制(OCC):每个存储的值都有版本号。写入时传入读取到的版本;若存储版本已变化,写入被拒绝并返回当前值。智能体重新读取、重新计算并重试。这与 Postgres 的
UPDATE ... WHERE version = N和 DynamoDB 条件写入使用相同机制。 - 版本保证:每个键的版本从 1 开始,每次写入恰好加 1。
state_history表为 append-only,在更新活动记录前先记录每次写入,崩溃后历史保持一致。删除也会先写入 tombstone(event_type: "delete")再移除活动记录。排序保证是按键的,不是全局的。 - 协调工具(默认五种):
agenthold_register(注册并获得唯一 agent ID,调用 claim/release 前必须执行一次)、agenthold_claim(修改前声明对资源的独占访问,需已注册的 agent_id;若资源有非平凡历史,响应会带 previous_outcome、previous_holder、previous_outcome_at 和提示;对 moved 会给出 moved_to)、agenthold_release(带明确 outcome 释放声明,outcome 保存在空闲状态记录中展示给下一个声明者;outcome 包括 released、modified、created、deleted、moved)、agenthold_status(检查资源是否可用或已被声明,无需注册)、agenthold_wait(阻塞等待已声明资源变为可用,直到持有者释放或超时)。 - 高级原语(
--tools advanced):八个底层工具agenthold_get、agenthold_set、agenthold_list、agenthold_history、agenthold_delete、agenthold_watch、agenthold_clear_namespace、agenthold_export,提供对版本化状态存储的直接读写与监听访问,带完整 OCC 冲突检测。此模式不发送服务器指令。 - 资源标识:使用规范 URI。工具接受裸路径(如 "src/main.py",解析到名为 default 的工作区,或唯一配置的工作区)或显式 URI(文件用 "file://<workspace>/<path>",不透明资源用 "custom://<name>")。等价输入(./src/main.py、src\main.py、src//main.py、工作区内绝对路径)都会规范化为同一内部 URI。路径穿越(..)和点段(.)在边界被拒绝。在大小写不敏感文件系统(默认的 Windows 和 macOS)上,文件资源按大小写不敏感匹配,src/Main.py 与 src/main.py 解析为同一资源;custom:// 名称始终大小写敏感。
- 多工作区:用
--workspace name=path配置(可重复)。无此标志时在當前工作目录创建单个 default 工作区。 - 传输方式:默认 stdio,MCP 客户端为每个智能体生成一个 agenthold 子进程,这些子进程通过同机共享的 SQLite 文件协调。跨进程、容器或主机协调时,运行一个长期存活的 Streamable HTTP 服务器,所有智能体通过 URL 连接;五种标准工具和高级工具在 HTTP 下行为相同。HTTP 模式可设置
--claim-ttl使断连智能体持有的声明可自动回收。 - 认证:HTTP 模式可要求 bearer token。用
--auth-token或环境变量AGENTHOLD_AUTH_TOKEN(多个 token 用逗号分隔,token 本身不得含逗号)。无有效 token 的请求在到达存储前返回 401。token 常量时间比较。认证仅适用于--transport http(stdio 为受信任的本地传输)。这是全有或全无认证,按命名空间的作用域还在路线图中。若请求认证但所有 token 为空,服务器拒绝启动。 - 安全:bearer token 的私密性取决于承载传输,真实部署应在前方加 TLS(反向代理)。默认绑定 127.0.0.1(仅 localhost);绑定到 localhost 之外且无
--auth-token会打印启动警告。--allowed-host <host>(可重复)启用 DNS 重绑定(Host 头)保护,注意这不是认证,不替代--auth-token。 - 已知限制:agenthold 尚不回收空闲智能体记录,长期运行且智能体频繁更替的服务器会累积智能体元数据。建议设置
--claim-ttl并定期重启。自动回收在路线图中。 - 只读检查器:agenthold 命令兼作只读检查器,无需 MCP 客户端即可查看智能体活动。命令严格只读(以 query_only 打开数据库、不写 schema),不会修改存储。每个命令接受
--db PATH(默认 ./agenthold.db)和--json。无子命令时仍照常启动 MCP 服务器。 - Python 库用法:不使用 MCP 时也可作为 Python 库直接调用。导入
StateStore,用带版本检查的.get()和.set()实现冲突安全的共享状态。
使用方式 作为 MCP 服务器:添加到 MCP 客户端配置后,智能体自动协调,无需 CLAUDE.md、无需修改系统提示、无需设计命名空间。连接后智能体看到五个自描述工具,工具描述会说明何时以及如何使用。 以 Python 库使用:导入 StateStore,调用 .get()/.set() 并带版本检查。 冲突检测重试循环:读取值与其版本;写入时传 expected_version;若版本已变则捕获 ConflictError,重读当前值,重新计算并重试。版本号就是契约:若存储版本已推进,说明其他智能体先写了。重试次数受并发写入者数量约束,实践中智能体几乎不会冲突超过一次。 HTTP 协调:运行一个长期存活的 agenthold 服务器监听 Streamable HTTP,MCP 客户端按 URL 而非命令连接。
适用场景
- 多智能体本地单机运行(stdio 模式,通过共享 SQLite 文件协调)。
- 跨进程、容器或主机的智能体协调(Streamable HTTP)。
- 需要为智能体工作记忆提供带版本、冲突安全的共享状态。
- 需要在修改文件或资源前声明独占访问、并让后续声明者了解前人操作的场景。 仓库内含两个带 before 和 after 脚本的示例:订单处理(两个智能体并发更新同一订单记录)和预算分配(两个智能体从共享营销预算中取用)。
为何用 OCC 而非锁:锁需要持有者释放,系统必须处理崩溃、超时和过期持有者;冲突罕见时不值得。OCC 只在冲突真正发生时付出一次额外读取和一次重试的代价。智能体在读写之间做大量工作(LLM 推理、API 调用、工具执行),无法跨这些工作持有数据库锁。
为何用 SQLite:零依赖、Python 标准库自带、到处可运行。启用 WAL 模式使只读操作(导出、监听)不阻塞跨进程写入者。写事务用 BEGIN IMMEDIATE 预先获取写锁,确保多个 agenthold 进程共享同一数据库文件时 OCC 冲突检测正确工作。busy_timeout 设为 5 秒,第二个写入者会等待而非立即失败。存储后端位于干净的 StateStore 接口之后,需要时可换成 Postgres。
生产规模的变化:主要一项是用 Postgres 替换 SQLite,以获得更好的并发写吞吐、复制和托管。StateStore 接口已设计为使此变更是受控的。网络传输和认证已存在。生产环境应通过反向代理在前面加 TLS,按命名空间/租户的 token 作用域尚未实现。
许可与开发:MIT 许可证,见 LICENSE。CI 在每次推送到 main 时于 Python 3.11 和 3.12 上运行。覆盖率检查、lint 与类型检查的具体命令及详细指南见 CONTRIBUTING.md。