agentclaim

agentclaim

Enables multiple AI coding agents to safely collaborate in the same git working tree by managing file ownership, merging writes, and preventing snapshot races.

Category
访问服务器

README

<div align="center">

agentclaim

Multiple agents. One working tree. Git won't save you.

File ownership for parallel AI coding agents — so they stop silently overwriting each other.

npm node dependencies license github

npm i -g agentclaim && agentclaim init

<img src="https://raw.githubusercontent.com/volkangunay/agentclaim/main/media/agentclaim-card.png" alt="agentclaim: two agents editing one file — one edit merged, one commit blocked" width="900">

</div>


The problem

You run two, three, five coding agents at once. They share one working tree.

Git was built for people on separate clones merging later. It has no idea what to do with two writers editing the same checkout at the same second. There is no conflict marker, no warning, no merge — the second write just wins and the first one is gone.

These are three real incidents from one afternoon in one repo. All three shipped to production. None of them produced a single error message.

1. The staging race

git add snapshots a file as it is at that instant.

 session A            session B
 ─────────            ─────────
                      git add i18n.js Money.jsx   ← snapshots i18n.js v1
 write i18n.js v2
                      git commit                  ← commit contains i18n.js v1

The commit shipped a new Money.jsx alongside the old i18n.js. The screen rendered raw translation keys in production. Git reported success. The follow-up fix commit fell into the exact same race.

2. The destructive restore

git checkout HEAD -- i18n.js api.demo.js   # session A tidies its tree
git commit -a                              # session B, two seconds later

Session B's work was reverted off disk and then committed away. Silently.

3. The gate that lied

The deploy script had a dirty-tree guard. It checked the tree at deploy time, not the race at commit time. The gate went green, the commit was wrong, and the deploy faithfully published the wrong commit.


The fix

Writes get smarter. Commits stay strict.

Blocking every second writer would be a stop sign, not a solution — and a tool that blocks work people need to do gets switched off. Two agents in one file are only in real conflict when they touch the same region.

So agentclaim does not ask "who owns this file?" It asks "what changed since I last looked at it?" — the only question that separates the other agent's edits from your own. Different regions, both agents work. Same lines, one of them stops.

agentclaim: src/checkout.ts is also being edited by another agent — your edits do not overlap theirs.
  their lines: 12-19
  your lines:  84-91
Both edits are kept. You may not commit this file until they are done.

A whole-file Write is not a conflict either — it gets three-way merged with the other agent's work using git merge-file, so both edits land:

agentclaim: src/checkout.ts was merged, not overwritten.
Another agent had edited this file; your write has been combined with
their changes. Re-read the file before continuing — it now contains both.

Overlapping lines are still not a stop sign

A targeted edit is surgical: it only applies if its anchor text still exists in the file as it stands right now. That one property does all the work.

  • anchor still there → replacing it keeps the other agent's edits, because their changes are, by definition, somewhere else in the text
  • anchor gone → the tool refuses on its own and the agent re-reads

Either way the outcome is already correct, so blocking would cost a round trip and buy nothing. agentclaim adds context instead:

agentclaim: heads up — another agent just changed the same lines of src/checkout.ts.
  their lines: 12-19
  your lines:  14-16

Your edit still applies cleanly on top of their version. This is what
they changed, in case it affects what you were about to do:
  @@ line 12-19 @@
  -  const total = items.length
  +  const total = items.reduce((n, i) => n + i.qty, 0)

Nothing is blocked. You may not commit this file until they are done.

What actually stops an agent

Four things, and only these:

Stopped Why
A whole-file write that cannot be three-way merged There is no correct automatic answer, and one of the two versions would be lost.
A whole-file write to a file this session has never read Nothing to merge against; it is a blind overwrite.
Staging or committing a file another agent is actively editing Incident #1: this is how one agent ships the other's half-finished work.
A git command we cannot parse that touches git indirectly (eval, sh -c) while another session is live We will not guess about git reset --hard.

Nothing in the edit path stops. That is the point: a tool that interrupts agents doing ordinary work gets switched off, and then it protects nothing.

Commits are the strict part

Once two live sessions have touched a file, neither may stage or commit it — because that is exactly how one agent ships the other's half-finished work. Incident #1 above.

That protection would be a deadlock if it had no exit, so it has three:

Exit What it does
agentclaim release <path> "I am done here." Drops only your stake, needs no --force, and cannot be used to steal a file. The other agent can commit immediately.
do nothing A session stops blocking a file it has not edited for touchTtlMinutes (default 10). Agents run for hours; nobody edits one file for hours.
agentclaim release <path> --force Take it over outright. The blunt instrument, still there when you need it.

Sessions ending or crashing release everything they held, so the tree never stays locked.

No server. No daemon. No dependencies. The store is a directory inside .git/.


Quick start

npm i -g agentclaim
cd your-repo
agentclaim init

Prefer not to install globally? npx agentclaim init works too — it copies itself to ~/.agentclaim/lib first, because hooks must point at a path that still exists tomorrow.

That is it. init wires up Claude Code hooks and installs a git pre-commit hook (chaining any existing one). Check it any time:

$ agentclaim status
SESSION          FILES  AGE   LAST SEEN
● money screen       3  6m    2s
  ai visibility      2  22m   14s

FILE                    HELD BY        AGE
web/src/Money.jsx       (you)          6m
web/src/i18n.jsx        (you)          6m
web/src/api.demo.js     ai visibility  22m

Give your session a readable name so the other agent's error message means something:

agentclaim label "money screen"

What init changes on your machine

Three things. Nothing else.

What Where Undo
Hook entries .claude/settings.json (backed up first) agentclaim uninstall
A pre-commit hook .git/hooks/ (an existing hook is chained, never replaced) agentclaim uninstall
The claim store .git/agentclaim/ — inside .git, never committed delete the directory

No network calls. No telemetry. No background process. Not a single line of your code is touched, and nothing new appears in git status.


It does nothing when you are alone

If yours is the only live session in the tree, every gate short-circuits to allow. No claims are enforced, no commands are inspected, nothing to bypass.

This is deliberate. A gate people cannot pass is worse than no gate, because they learn to disable it and then it protects nothing. agentclaim only has teeth in the exact situation it exists for.

$ agentclaim doctor
...
1 live session(s) · 3 claim(s) · TTL 30m · mode block
single session -> gates inactive (no-op)

The four gates

# Gate When What it stops
1 Write before Write / Edit nothing in the edit path — whole-file writes get merged, and only an unmergeable one stops
2 Git before a Bash command git add -A, git commit -a, git checkout -- x, git reset --hard, git stash, git clean touching files you do not own
3 Commit truth after git commit the snapshot race — commit content that does not match disk
4 pre-commit on any git commit staged files owned by someone else, from any tool

Gate 3 is the one nothing else catches. It re-reads every path in the commit with git show <sha>:<path> and compares it byte-for-byte with the file on disk:

agentclaim: ⚠ commit 045d1f7 does NOT match what is on disk:
  web/src/api.demo.js

This is the classic `git add` snapshot race: another session rewrote these
files after you staged them, so the commit captured stale content.
DO NOT DEPLOY. Fix it with:
  git add web/src/api.demo.js && git commit --amend --no-edit

It only reports files held by another session, so ordinary partial staging (git add x, keep editing x, commit) never triggers a false alarm.


Works with any agent

Three integration layers, strongest first. Use as many as apply.

Claude Code — hooks (strongest)

agentclaim init            # project-level  (.claude/settings.json)
agentclaim init --global   # every repo     (~/.claude/settings.json)

Gates run before the write or the command. The agent gets the denial as feedback and picks a different file on its own.

Cursor · Windsurf · Codex · Zed · Cline · anything MCP

agentclaim ships an MCP server, so any agent that speaks MCP can join the same ownership protocol:

{
  "mcpServers": {
    "agentclaim": {
      "command": "agentclaim",
      "args": ["mcp"],
      "env": { "AGENTCLAIM_SESSION": "cursor-1", "AGENTCLAIM_AGENT": "cursor" }
    }
  }
}

Tools exposed: agentclaim_status, agentclaim_claim, agentclaim_release, agentclaim_check, agentclaim_verify_commit. The descriptions tell the model when to call them.

Everything else — the git hook

agentclaim init installs a pre-commit hook, so aider, a plain git commit, your IDE, or a shell script all hit the same check. Nothing to configure.

For deploy scripts and CI, use the exit-code gate:

agentclaim check --staged --quiet || exit 1   # anyone else holding staged files?
agentclaim verify HEAD                        # did the commit capture disk?

Commands

agentclaim init [--global]    wire up the hooks (Claude Code + git pre-commit)
agentclaim status             who holds what
agentclaim who <path>         owner of a single file
agentclaim claim <path...>    claim files            [--note "..."]
agentclaim release <path...>  "I am done here"       [--all] [--force to take over]
agentclaim check <path...>    gate for scripts, exit 0/1  [--staged] [--quiet]
agentclaim verify [rev]       compare commit content against disk  [--all]
agentclaim label "<name>"     give this session a readable name
agentclaim gc                 collect stale claims
agentclaim doctor             diagnose the installation
agentclaim uninstall          remove the hooks
agentclaim mcp                run as an MCP server

Configuration

Optional .agentclaim.json at the repo root:

{
  "ttlMinutes": 30,
  "touchTtlMinutes": 10,
  "mode": "block",
  "ignore": ["node_modules/**", "dist/**", "*.lock", "package-lock.json"]
}
  • ttlMinutes — a session with no activity for this long is considered gone and its claims can be taken over. Every hook invocation refreshes the heartbeat, so an active session never expires.
  • touchTtlMinutes — how long a session keeps blocking others from committing a file after its last edit there. Shorter than ttlMinutes on purpose: still being alive is not the same as still working in this file, and conflating the two is what turns protection into deadlock.
  • modeblock (default), warn (report but allow), off.
  • ignore — never claimed. Keep generated files here; if lockfiles and build output get claimed, the gate fires constantly and people start bypassing it.

How it works

.git/agentclaim/
  sessions/<id>.json   { sid, label, pid, started, seen, wt }
  claims/<hash>.json   { path, wt, sid, at, touchers }
  snap/<sid>/<hash>    what that session last saw on disk
  pending/<sid>/<hash> a merge computed before a write, applied right after it
  pass.json            short-lived identity token for the git hook
  • Store location is git rev-parse --git-common-dir, so every worktree of the repo shares one registry.

  • Claim keys include the worktree root, because the same relative path in two worktrees is two different files on disk. Separate worktrees never block each other — worktrees are a legitimate fix for this problem, not something to punish.

  • Atomicity is open(..., 'wx') — O_EXCL. Two simultaneous claims, one winner, no race.

  • Liveness is TTL-based. Hooks fire on every tool call, so seen stays fresh within seconds; a crashed agent's claims are reclaimable and never wedge the repo.

  • Region reasoning compares your session's snapshot with the file on disk using git diff --no-index -U0, and merges with git merge-file. Everything is git's own semantics — the ones you already trust — with no dependency added.

  • Merges are applied by us, not injected. The hook output schema has an updatedInput field, but nothing verifiable says it applies without also auto-approving the call, and a wrong assumption there would silently drop the other agent's work. So the merge is stashed and written right after the tool runs, using only mechanics we control.

  • Cost per tool call is one short-lived node process: ~45 ms working alone, ~83 ms when a gate actually has to reason (measured on a 200-file repo). Node startup dominates — taking a snapshot after a read adds about 1 ms.

  • Nothing to maintain. gc runs on every session start and removes the claims, snapshots and pending merges of sessions that are gone, so nothing accumulates in .git/.


Limitations

Stated plainly, because a guard you trust wrongly is worse than no guard.

  • git commit --no-verify skips the git hook layer. The Claude Code layer still catches it.
  • Agents without hooks or MCP are invisible while writing; they are caught at commit time.
  • Command parsing is deliberately not a full shell parser. For eval / sh -c / backticks that touch git, agentclaim refuses only while another session is live.
  • Region coexistence needs to know what your session last saw, so it only applies to files the agent has read or written through its tools. A file changed by some other route (a shell sed, an external editor) is invisible to that reasoning.
  • agentclaim does not understand meaning. Two edits can be textually independent and still be semantically incoherent together; it tells you the other agent was there, but the judgement is yours.
  • A clean three-way merge can still be semantically wrong, exactly as it can for humans. agentclaim tells you the file was merged so you re-read before relying on it.
  • Claims are per-machine. Nothing is synchronised across hosts.

Testing

npm test

32 end-to-end checks. The suite replays all three real incidents above, proves the tool is a complete no-op for a lone session, and asserts each gate with both a passing and a failing example — a gate that fails to catch its own bug is worse than no gate, because it inspires trust.


Links

  • Source & issues: https://github.com/volkangunay/agentclaim
  • npm: https://www.npmjs.com/package/agentclaim

License

MIT © Volkan Günay

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选