关于 Agent Lease
Agent Lease 是供 Claude Code 和 Codex 在同一代码库协作的 MCP 服务与协调系统。它通过带时效的路径租约和 Hook 阻止冲突编辑,并提供在线状态、消息、任务跟踪和审计记录。
主要功能
- 01由 Hook 强制执行路径租约
- 02首次编辑时自动认领文件
- 03Agent 在线状态与共享消息
- 04带投递状态的定向任务
- 05租约及操作审计记录
- 06命令行与本地 Web 房间
适用场景
- 01避免多人同时修改同一文件
- 02协调 Claude Code 与 Codex 的工作
- 03在 Agent 会话间分派并跟踪任务
- 04查看路径由谁持有或修改
MCP 相关
stdio MCP 服务提供路径认领与释放、所有者和房间状态查询、消息、任务处理及历史记录等工具。命令行也提供相同操作。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx agent-lease-mcpAGENT_NAMEThis agent's name in the room, e.g. claude-code or codex. Must match the hooks.AGENT_LEASE_DBPath to the shared SQLite room (default ~/.agent-lease/room.db).本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
15收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| paths | array | 是 | |
| purpose | string | 否 | |
| ttl_seconds | any | 否 |
详细介绍
agent-lease-mcp
防止两个编码 Agent(Claude Code 和 Codex,或同一工具的两个会话)在同一个代码仓库中互相覆盖工作。
agent-lease 为它们提供针对路径的租约、在线状态和共享消息日志,并且租约由 PreToolUse 钩子强制执行,而非寄望于自觉遵守。Agent 如果尝试编辑另一个 Agent 正在处理的文件,会在编辑发生前被拒绝。
存在的原因
2026 年 9 月 9 日。协调文件 docs/AGENT-HANDOFF.md 已经存在,两个 Agent 也都知道它。22:28 时,它们仍然都动了 deploy/make-work-package.sh:一个在编辑,另一个在运行。Bash 会边读边执行脚本,运行中的进程读到的文本发生了变化,最终生成的不是 3.7 GB 的安装程序,而是一个毫无用处的 198 MB 安装程序。
整个项目立足于这样一条经验:**可以被忽略的协调机制终将被忽略。**这并非出于恶意——Agent 工作时就是不会去看公告栏。
之后,我们又特意重演了同样的冲突:Codex 运行脚本时,Claude Code 试图在执行过程中编辑它,钩子拒绝了这次编辑。文件完好无损。
有何不同
| 锁 | 强制执行 | TTL | 在线状态 | 消息 | 审计 | |
|---|---|---|---|---|---|---|
| AgentRoom(论文) | ✅ | 仅提示词 | ? | ✅ | ✅ | ✅ |
| Agent Claim MCP | ✅ | 仅提示词 | ✅ | ❌ | ❌ | ❌ |
| agent-orchestration | ✅ | 仅提示词 | ❌ | ✅ | ✅ | ❌ |
| agent-lease-mcp | ✅ | PreToolUse 钩子 | ✅ | ✅ | ✅ | ✅ |
建议式锁定会被绕过;AgentRoom 自己也记录了这一点。Claude Code 和 Codex 都支持语义相同的 PreToolUse 钩子(退出码 2 = 阻止,原因写入 stderr),因此可以在工具调用层面确定性地强制执行租约。一个脚本即可同时服务两个客户端。
基于哪些规则构建
- 租约,而非锁。 TTL 为 30 分钟。崩溃的 Agent 不会永远阻塞代码仓库。
- 要么全部成功,要么全部失败。 对多个路径的声明要么全部成功,要么全部不成功。
- 目录租约涵盖目录下的所有内容。
- 空闲文件会在首次编辑时自动被声明,因此无需记得调用
claim。 - 脚本在运行期间会被租用——这正是导致构建失败的组合。
- 默认故障时放行。 协调机制故障不能阻止工作,否则任何人第一件事就是关闭钩子。设置
AGENT_LEASE_FAIL_CLOSED=1可在无人值守运行时切换为故障时阻止。
自动运行的功能
| 钩子事件 | 执行内容 |
|---|---|
| SessionStart | 将房间状态注入上下文:有哪些 Agent、它们持有哪些租约、有哪些消息发给你 |
| UserPromptSubmit | 通过游标投递新消息,每条只投递一次 |
| PreToolUse(Edit/Write/apply_patch…) | 拒绝编辑由其他人持有租约的文件 |
| PreToolUse(Bash) | 对正在运行的脚本加租约;若明显的写入操作(>、tee、sed -i、cp/mv 的目标)会写入由其他人持有租约的文件,则予以拒绝 |
| Stop / SessionEnd | Agent 完成本轮工作时归还所有租约 |
没有内容需要提示时,注入操作会保持静默。
唤醒休眠中的 Agent
只有 Agent 执行操作时才会触发钩子,因此空闲会话需要从外部唤醒,无需消耗模型令牌:
- Claude Code:
agent-lease init claude-code --write --rewake会在SessionStart和Stop上添加asyncRewake钩子。它会等待信号文件,并在收到其他人发来的定向消息时唤醒会话。广播消息和自动回执不会唤醒会话。 - Codex:
agent-lease-codex-wake服务会通过codex queue提醒指定会话(只读取消息编号,不读取消息内容)。提供方限额耗尽后,它会针对未完成的工作提醒一次。设置方法:在 Codex 中运行/rename nox,在~/.config/agent-lease/environment中设置AGENT_LEASE_CODEX_THREAD=nox,然后启用deploy/systemd/agent-lease-codex-wake.service。
来自其他 Agent 的消息是信息,不是人类指令。若要让 Agent 在无人值守时相互交接工作,人类需要将允许的范围(以及哪些情况始终需要询问)写入 Agent 自身的指令中,例如 ~/.codex/AGENTS.md。模板和详情(捷克语):
docs/ZPRAVY-A-POTVRZENI.md。
工具
| MCP 工具 | 用途 |
|---|---|
claim(paths, purpose, ttl_seconds) | 提前预留——通常是在运行脚本前预留一个目录 |
release(paths?) | 释放;不带参数时会释放你持有的所有资源 |
owner(path) | 查看谁持有此路径(包括通过目录租约持有的情况) |
room(status?) | 每个检查点调用一次:查看当前有哪些成员、持有哪些资源、有哪些新内容 |
say(text) / inbox(since_id) | 向房间发送消息 |
send / jobs / next_task / ack / retry / cancel | 定向消息和带有投递状态的持久任务 |
accept(message_id, note) | 确认你正在处理收到的定向消息——发送者会看到此确认 |
report_limit(minutes, reason) | 配额用尽:尚未开始的任务会转交给备用 Agent |
history(path?) | 查看谁处理了什么以及最终结果——回答“我为什么被阻塞” |
相同的操作也可以通过 CLI 执行(agent-lease room|claim|release|say|send|…),因为 Codex 在非交互模式下无法批准 MCP 调用,但可以运行 Bash。
agent-lease wait --for <agent> 会阻塞等待,不消耗模型令牌,并在收到消息时唤醒后台会话。
say/inbox 不是实时聊天:MCP 采用拉取方式,对方下次查看时才会读取消息。如需实时查看,可使用本地 Web 房间:
agent-lease web --open(仅限 loopback,参见安全性)。
快速开始(两分钟)
uv tool install agent-lease-mcp
agent-lease init claude-code --write --rewake # writes all hooks, keeps a .bak backup
agent-lease init codex --write
agent-lease simulate # 2 s: replays a full scenario in a temp DB
agent-lease doctor # checks it really works
agent-lease web --open # live room in the browser
不带 --write 的 init 只会显示将要写入的内容。手动设置方法见下文。
安装
uv tool install agent-lease-mcp
此操作会将 agent-lease-mcp、agent-lease、agent-lease-guard、agent-lease-context 和 agent-lease-release 添加到你的 PATH 中。
MCP 服务(stdio;每个客户端各自运行一个进程,共享状态存储在 SQLite 文件中):
# Claude Code
claude mcp add agent-lease -e AGENT_NAME=claude-code -- agent-lease-mcp
# Codex — ~/.codex/config.toml
[mcp_servers.agent-lease]
command = "agent-lease-mcp"
env = { AGENT_NAME = "codex" }
# Codex writes to ~/.agent-lease, outside the workspace — allow it:
[sandbox_workspace_write]
writable_roots = ["/home/YOU/.agent-lease"]
钩子——没有钩子,它就只是一个礼貌的公告板:
// ~/.claude/settings.json
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit|Write|MultiEdit|NotebookEdit|Bash",
"hooks": [{ "type": "command", "command": "AGENT_NAME=claude-code agent-lease-guard" }]
}],
"SessionStart": [{ "hooks": [{ "type": "command", "command": "AGENT_NAME=claude-code agent-lease-context" }] }],
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "AGENT_NAME=claude-code agent-lease-context" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "AGENT_NAME=claude-code agent-lease-release" }] }]
}
}
// ~/.codex/hooks.json — same events, AGENT_NAME=codex
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{ "type": "command", "command": "AGENT_NAME=codex agent-lease-guard" }]
}]
}
}
AGENT_NAME 必须在服务和钩子中保持一致,否则 Agent 会阻塞自己的文件。room 中名为 unconfigured-* 的 Agent 就表示尚未配置。
| 变量 | 默认值 | 用途 |
|---|---|---|
AGENT_NAME | unconfigured-<host>-<pid> | 房间中的名称 |
AGENT_LEASE_DB | ~/.agent-lease/room.db | 状态文件的存放位置 |
AGENT_LEASE_TTL | 1800 | 默认租约时长(秒) |
AGENT_LEASE_RETENTION_DAYS | 30 | 超过此时长的消息和审计记录会被删除(0 = 保留) |
AGENT_LEASE_FAIL_CLOSED | 关闭 | 1 = 钩子本身失败时阻止工具调用 |
AGENT_LEASE_HUMAN | human | 房间中人类的名称(由 Web UI 存储在 DB 旁边) |
AGENT_LEASE_TASK_SENDERS | 人类 + 已配置的 Agent | 可创建任务的成员(以逗号分隔) |
AGENT_LEASE_FALLBACKS | — | 故障转移链,例如 codex=claude-code;claude-code=codex |
可选:Bash 安全门控
用于另一个问题的独立 PreToolUse 钩子(agent-lease-jev-guard)——
不是“谁持有此文件”,而是“此命令是否具有破坏性”。硬性规则
(rm -rf /、强制推送、git reset --hard、curl | sh、mkfs……)会在接入后立即生效。可选的外部分类器(TypeSafe Jev)默认关闭,启动时处于影子模式,只接收经过脱敏的命令,且仅提供建议——
绝不会是唯一的防护措施。详情见 ADR 0003。
回执、故障转移、桥接和代理
- 每条定向消息都有可见状态 ⏳ → 👁 已查看 → ✔ 正在处理 → 已完成。
“已查看”由钩子自动写入;“正在处理”必须由 Agent 确认
(
accept),并会在每次提示词中提醒。不会有任何内容悄无声息地丢失。agent-lease overdue会列出无人确认的事项。 - 限额与故障转移。 Codex 工作器会读取来自本地 App Server 的显式类型化可用性
信号。其他工作器只有在出现实际的服务商/CLI 错误后才会记录限额;如果没有提供重置时间,
agent-lease 不会凭空编造。仍可手动使用
agent-lease limit/MCPreport_limit。 未接受、未开始的任务会在宽限期后转给AGENT_LEASE_FALLBACKS中的下一个 Agent(例如codex=claude-code;claude-code=codex),并通知发送者。 - 桥接(
agent-lease bridge --agent codex)——自主工作器:接收任务、确认任务, 以只读方式运行codex exec/claude -p并返回结果。使用--provider openrouter --model <model>时,会通过 OpenRouter 将任务发送给任意模型, 以不带工具的纯文本形式处理(只读;适合审查和征求第二意见)。密钥只从环境中读取。 工作器的在线状态/服务商状态使用独立标识(codex-bridge、claude-code-bridge), 因此无头模式下的配额状态不会覆盖交互式 Agent 的状态。 - 任务绝不会运行两次。 由工作器持有(
leased/started)的任务只能凭其租约令牌接受; 桥接会跳过已被交互式会话接受的任务,并且启用--interactive-grace时, 会优先将新任务留给在线的交互式 Agent。 - 代理(
agent-lease broker)——通过权限为0600的 Unix 套接字运行的单一 SQLite 写入器; 运行时,其他工具会自动使用它,因此沙盒中的 Codex 无需直接访问数据库。客户端可在代理 重启后继续运行:连接会重试(此时尚未发送任何内容),被中断的调用绝不会重复执行, 长时间运行的工作器会继续处理下一次调用。 - 实时房间中的概览面板:每个 Agent 的当前任务、队列、未确认事项、类型化服务商状态、
等待原因、限额和待审查事项。任务可以声明可重复使用的
--path工作集和--priority;调度器会跳过被其他 Agent 占用的路径,面板会显示阻塞路径、所有者和 TTL。agent-lease notify --loop只会向 Telegram 发送需要人工处理的事项,每个事件最多发送一次 (从环境中读取TELEGRAM_BOT_TOKEN、TELEGRAM_CHAT_ID;使用--dry进行测试)。 - 安全地运行工作器。
agent-lease worker-activate和agent-lease failover-activate <agents…>会设置安全的起始状态,但不会运行或转移任何任务: 只有在激活后到达的工作才会被接收或重新路由(使用--include-existing可选择处理较早的事项)。agent-lease provider-sync --agent codex会读取并保存 Codex 的显式可用性;agent-lease provider-clear用于确认服务商已恢复并移除过期的限额;agent-lease limits会显示有效限额和故障转移链。agent-lease doctor --json会分别报告投递、 回执、工作器健康状况和故障转移就绪情况。 - systemd 单元文件位于
deploy/systemd/,Windows/WSL 启动程序位于deploy/windows/。
安全
agent-lease 是一款协调工具,供同一本地用户账户下运行的 Agent 使用。实际含义如下:
- 本地信任模型。 Agent 身份由
AGENT_NAME环境变量指定。任何以你的用户身份运行的进程都可以冒充任何 Agent。唯一保留的身份是人类用户(此版本中为michal),只有经过身份验证的 Web UI 才能使用。此机制用于防止 Agent 彼此冲突,而非防范你账户下的恶意进程。 - 状态仅对所有者开放。
room.db(及其 WAL/SHM 文件)在权限为0700的目录中,权限设为0600;启动时会收紧现有安装的权限。 - 对等消息是不可信数据。 在模型上下文中,每条消息都会标明其类型、发送者和接收者,折叠为单行,并附注说明这是来自对等方的信息,而非人类指令。其他 Agent 发来的任务是请求,不是命令。
- 任务与调用者绑定。 对等方只能查看自己发送或接收的任务;只能查看自己投递任务对应的租约令牌;重试或取消任务需要发送者或接收者的身份。
- Web UI 仅绑定到回环地址,通过一次性 URL 片段令牌换取 HttpOnly、SameSite=Strict 会话 Cookie,检查 Origin 和 Host,并限制并发事件流数量。
- 限制与保留期限。 消息文本、短字段、列表数量和任务租约时长都有上限;旧消息和审计记录会过期,未完成的任务则不会过期。
已知限制,如实说明:
-
不同提供商的信号有所差异。 已安装的 Codex App Server 提供明确的
account/rateLimits/read信号。Claude Code 有文档说明的 CLI 不提供等效的状态读取,因此只有在提供商给出重置时间后,才会通过有限次数的普通任务重试确认恢复。如果没有重置时间,自动探测会停止,直到通过agent-lease provider-clear明确确认已恢复。unknown绝不会被视为提供商已恢复的证明。 -
Shell 命令检查仅尽力而为。 重定向、
tee、sed -i、truncate、cp/mv的目标,以及sudo/env这类包装器和sh -c都能被识别。eval、路径中的变量和任意程序则无法识别。这能发现错误,但无法防范试图绕过钩子的 Agent。 -
检查与使用之间的间隙。 钩子会在客户端打开路径前检查它。如果期间替换了符号链接,钩子无法发现;钩子也无法让之后打开的文件绑定到已检查的 inode。
-
单机运行。 状态存储在磁盘文件中。
发现问题?请通过 GitHub Security Advisories 私下报告。
开发
uv sync --extra dev
.venv/bin/python -m pytest -q
.venv/bin/ruff check .
设计决策:ADR 0001(钩子强制执行)、 ADR 0002(SQLite 和标准输入输出)、 ADR 0003(独立的 Bash 安全门)——内容为捷克语。
由 Michal 使用 Claude Code 和 Codex 构建——这两个 Agent 正是它协调的对象。
MIT 许可证。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。