Use when several coding agents work one repository at the same time and must not collide - claiming a task, reserving the next decision/question/ticket id, journaling a run, moving a cross-repo dependency, regenerating the board, or finishing work across a parent repo and its submodules - clean, pushed, and pointed at. Triggers - 'claim this task' / 'возьми задачу', 'who is working on' / 'кто сейчас делает', 'reserve an id' / 'зарезервируй id', 'sync the board' / 'обнови доску', 'set up agent...
Installs into .claude/skills of the current project.
Are you the author of Agent Sync?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ssheleg-agent-sync)
---
name: agent-sync
description: "Use when several coding agents work one repository at the same time and must not collide - claiming a task, reserving the next decision/question/ticket id, journaling a run, moving a cross-repo dependency, regenerating the board, or finishing work across a parent repo and its submodules - clean, pushed, and pointed at. Triggers - 'claim this task' / 'возьми задачу', 'who is working on' / 'кто сейчас делает', 'reserve an id' / 'зарезервируй id', 'sync the board' / 'обнови доску', 'set up agent coordination' / 'настрой координацию агентов', 'the submodule is pushed, the parent points at the old commit' / 'сабмодуль запушен, родитель на старом коммите', /agent-sync. Use it BEFORE editing any shared registry file (decisions, open questions, roadmap, workstreams, dependencies) in a project that has .claude/agent-sync.json, even when the user never mentions coordination - an unclaimed edit to those files is how two agents overwrite each other."
compatibility: "Requires the task-pipeline skill for its stages (npx sshlg-skills install). Needs python3 3.9+ (stdlib only, HTTP included - nothing to pip install) and bash for the hooks. The knowledge backend is configured per project; with none configured it degrades to git-file leases. Enforcement hooks are Claude Code only - on other agents the same checks run as a self-check."
license: MIT
metadata:
version: "1.21.4"
author: ssheleg
---
# agent-sync — one project, many agents, no collisions
Two planes, and one rule between them:
> **Git is the record plane. The cloud is the coordination plane.**
> A fact that must survive is written to git first and referenced from the cloud.
> A fact about *who is doing what right now* lives in the cloud and expires.
No cloud object is ever the only home of a durable fact. Everything below exists to
keep that true while several agents write at once.
## Four traps — read these before anything else
**1. The knowledge base never decides a lease.** It cannot: twelve concurrent appends to
one Outline document returned twelve successes and left **three** lines. Exclusion comes
from something with real compare-and-swap. The plane carries the record and nothing else.
**2. Know which lease you have, and say so.** `leaseBackend: "local"` is an atomic file
create — exclusive between processes on one filesystem, **advisory across machines**.
`leaseBackend: "git"` pushes a ref, and the remote's non-fast-forward rejection **is** a
compare-and-swap — exclusive across machines. `acquire` prints which. A pretended lease is
worse than no lease: the other agent stops checking.
**`acquire` also writes the claim through to the roadmap**, and `release` restores exactly
what was there — one row, one cell, refused on ambiguity, `git diff` empty after a
round-trip. **Read `references/roadmap.md`** before configuring `claimTags` or closing a
task; closing is a statement about the work and stays yours.
**Work on a branch; the integration branch is somebody else's stable base.** `acquire`
writes the claim through **only** there — on any other branch the holder stays in the
coordination plane, where `status` shows it to every agent. Committed to a branch, a claim
is invisible until the merge and turns the shared roadmap into a file two branches both
edit. Land work with `merge`: conflicts computed **before** anything is touched, the merge
recorded in `docs/MERGES.md`, the `--key` lease released; `merges` says what landed while
you were away. **Read `references/branching.md`** before merging.
**3. Hooks exist only in Claude Code.** Elsewhere nothing blocks a guarded edit: run
`guard` yourself and record the run as `ungated`. A mode's five capability fields
live in ONE place — `references/adapter-contract.md` → *The status capability
contract*, never restated here. Do not describe a project as protected when it is not.
**4. Parse liberally, and never call an unreadable log a lost race.** The store rewrites
what you wrote — Outline turns a `- ` bullet into `* `. Emit `- `, accept `-`/`*`/`+`,
count anything entry-shaped that fails, and **fail loudly** past 2% unparseable. Reporting
`lost` when the truth is *unreadable* names a holder who does not exist. Watch for a
silent pre-filter: a `continue` before the regex hides bad lines from the counter.
## Bringing this into ANY project — the whole chain
```
scaffold → create the documentation architecture, only where it is absent
adopt → read the repository, propose a config, write nothing
init → write the approved config + the gitignored env file
(operator pastes the token — never you)
reconcile --set-baseline → make history a counted backlog, once
setup → generate the snapshot that describes this project's wiring
check → validate the whole thing; non-zero if it is not healthy
```
**`check` is what makes the skill self-sufficient.** It refuses to call a setup healthy on a
rule that protects nothing (a register, guard glob, claim pattern, gate or mirror source
pointing at what is not there), on missing credentials, on an env file **tracked by git** —
the one unrecoverable mistake here — on a stale snapshot, or on a register with no baseline.
It names each one; every one failed for real during this tool's own adoption. Run it after
adopting, after changing the config, and in CI.
**`scaffold` never overwrites.** It seeds a decision register and an `AGENTS.md` pointing at
the snapshot, and leaves every existing file untouched.
## Existing project: start with `adopt`
Run `adopt` before `init`. It reads the repository and prints what it found — id
registers, registry files, gates — plus the decisions it **refuses to make for you** —
then proposes a config.
```bash
python3 "$SKILL_DIR/scripts/agent_sync.py" adopt
```
Confirm the registers and guarded files with the operator first: a register pointed at the
wrong file makes every later check confidently wrong, and a guarded list that misses a shared
file leaves the one place collisions happen unprotected. In a submodule it declares no
registers — decisions belong to the parent. Then take the chain above from `init`.
## First command in a project: `init`
**Never run anything else against an uninitialised project.** `init` asks and answers
the storage question once, and writes it down.
**Ask the operator these two things in chat — do not guess, do not pick a default:**
1. **Where should coordination state live?**
- a knowledge cloud — `outline`, hosted or self-hosted, or `notion` — the shared
record, awareness and board across machines — **neither decides leases** (trap 1);
- or local files (`fs`) — no credentials, and no visibility to an agent on another
machine — no shared awareness, cross-repo signal or board.
The lease is decided separately by `leaseBackend` (trap 2), and **`gated` follows that
choice, never the record plane** — report the guarantee you actually have. Ids too:
under `leaseBackend: "git"` registers reserve from git refs even on `fs`.
2. **If cloud: where.** Outline needs its instance URL; Notion needs the id of the page
the container goes under. Both are configuration, not secrets, so you may write them.
The **token is not** — you never ask for it in chat, never read it back, and never
place it yourself.
Then run it with their answers:
```bash
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend outline --url https://<their-instance>
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend notion
python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend fs
```
`init` writes `.claude/agent-sync.json` (shape, committed) and `.env.agent-sync` with an
**empty** token line (identity, mode 600), gitignores both, and prints the one step that is
the operator's — creating the token and pasting it into that line. Never overwrites either
file without `--force`. **Relay those closing instructions verbatim**; the design depends on
the token staying theirs.
## Then, before every session
```bash
python3 "$SKILL_DIR/scripts/agent_sync.py" status
```
Idempotent. Inspects, repairs, reports, names exactly ONE next action — and carries
`check`'s verdict, so the command every session runs and the one that validates the setup
cannot give two answers about one project.
**Read the two awareness sections it prints — they are the point, not decoration.**
- **Other runs working this project right now.** Who holds what, this minute. Do not take
those on, and do not "just look at" the files they cover.
- **New since you last looked.** Cross-repo dependency moves that landed while you were
away — watermarked per run, so it stays quiet until something changes. A dependency that
moved may unblock what you planned, or invalidate it.
`status` also decides: no credentials → degraded mode; `task-pipeline` absent → it
prints the install line and stops. Absence is checked across every host layout or an
explicit `pipelinePath` — no host's copy masks or is missed. The lease core
(`acquire`/`renew`/`release`) needs a backend + lease, not the binding.
```bash
npx sshlg-skills install
```
## The commands
| Command | Does |
|---|---|
| `init` | **Run first.** Ask where state lives, write config + gitignored env file, print the operator's step |
| `status` | Inspect, repair, report, name one next action |
| `bootstrap` | Create the cloud container and print the id to paste into the env file |
| `acquire <KEY>` | Take the lease on a task id. Prints `won` or `lost <holder>` |
| `renew <KEY>` | Extend the lease. The `PostToolUse` hook does this for you |
| `release <KEY>` | Give the lease back on every path; `released` means the remote ref is gone |
| `release --held` | Give back everything this run holds, in one process — what SessionEnd runs |
| `reserve <REG> [--key K] [--offline]` | Reserve the next id in a register (`DEC`, `OQ`, `DEP`, …); prints it. `--key` makes a retry idempotent (one key, one number); `--offline` issues a namespaced `REG-o-…` id with no global authority |
| `map-offline <REG> <ID> <N>` | Bind an offline id to a properly reserved number — append-only, never rebound |
| `release-id <REG> <ID>` | Return an id you did not end up writing to git |
| `journal <text>` | Append one line to this run's journal |
| `record <text>` | Append what you **actually built** — `--decision DEC-…`, `--files a,b` |
| `reconcile` | Intent (git) vs as-built (cloud). `--set-baseline` once per project |
| `signal <DEP-ID> <state>` | Move a cross-repo dependency: `filed`/`accepted`/`delivered`/`closed`/`refused` |
| `guard <path>` | Answer whether this run may write that path. Exit 0 = yes, 2 = no |
| `board` | Regenerate the shared board and this repo's page. `--mirror` also renders the configured git docs into the plane |
| `whoami` | Print this run's id and its held leases |
| `residue` | What a run left behind in BOTH planes: expired locks on disk, and claim tags with no live lease behind them |
| `reap [KEY…]` | Clear only what this run **proves** it owns and has spent, then re-read the directory to confirm it went |
| `setup` | Write the generated snapshot of how **this** project is wired, for agents to read |
| `adopt` | Inspect an existing project and **propose** a config — writes nothing |
| `merge` | Land this branch: conflicts checked **before** anything is touched, merge log written, the `--key` lease released after the push. `--summary`, `--dry-run`, `--push` |
| `merges` | What landed while you were on your branch. `--all` includes the compacted tail |
| `check` | Validate the whole setup end to end. Non-zero when it is not healthy |
| `scaffold [--full]` | Create only what is missing, never a line over what exists. `--full` also seeds the question register, index, dependency board, data model and docs gate |
| `finish [--gates]` | Is the **work** finished — every repository clean, pushed and pointed at, nothing left held. `check` answers whether the project is wired right; this, whether you are done |
`$SKILL_DIR` is this skill's own directory: `${CLAUDE_PLUGIN_ROOT}/skills/agent-sync` under
the Claude Code plugin, `~/.agents/skills/agent-sync` elsewhere. Resolve it once per session
and reuse it. Every command reads `.claude/agent-sync.json` from the project
root and needs no arguments beyond those listed.
## One identity per session, and how it is decided
A lease is only a lease if two agents get two identities, and both ends have bitten:
`CLAUDE_SESSION_ID` alone gave **one session two identities** — it acquired as one and was denied by
its own guard as the other — and one id per checkout gave **two sessions one identity**, silently,
so `release` took a lease the caller never had.
The order is: `AGENT_SYNC_RUN_ID` · `CLAUDE_SESSION_ID` · **the session that started this shell** ·
shared. The third exists because a plain shell command has no session id and a hook does, so
`SessionStart` stamps `.agent-sync/sessions/<CLI pid>` and a later command walks its own process
ancestry to it. Why that and not command-line parsing: `references/earned-rules.md`.
When none of the four can be established the run says so — *"this identity is shared with any other
session in this checkout"* — rather than presenting a shared entry as separation.
## Claiming — the shape that matters
```
acquire → do the work → release
```
Never skip `release`, including on failure: an abandoned lease blocks the task until its
TTL expires, and the next agent cannot tell "in progress" from "crashed an hour ago".
**The lease is not the claim.** The lease says who holds it *now* and expires; the durable
claim is the tag in git, written through by `acquire` and cleared by `release`. One fact,
one home — do not invent a third place that records ownership.
**One notion of held, consulted by both planes.** The TTL ends the lease, so a tag naming a
run whose lease has ended is residue: reported by `status`, `residue` and `reconcile`, and
cleared by `release <KEY>`, which says whose it was. A tag whose lease is still live is
never touched by another run.
**Read `references/lease-protocol.md`** before changing acquisition, expiry, stealing or
id allocation.
## Guarded files
The config lists registry files several agents write. Before editing one:
```bash
python3 "$SKILL_DIR/scripts/agent_sync.py" guard docs/DECISIONS.md
```
**Exit 2 is about *this run*: it holds no lease** — not that somebody else holds that file.
A denial names the other run **and its key**, because "r-x holds a lease" beside a path gets
repeated as "r-x holds this file". Do not edit anyway, and do not "just fix one line" — a
clobbered decision looks exactly like a decision.
**Two write modes, and the tool does not promise the wrong one.** A task lease authorizes
the TASK, never the file — so a guarded write also takes the FILE's own **resource claim**
(`res--<repo>--<canonical path>`, SY-04). That is the **short transaction lock**: two agents
on one shared registry serialize on it, independent files never serialize, honest
cross-machine only under `leaseBackend: "git"` (advisory otherwise). The other mode is
**isolated worktree + merge** — each agent a private checkout, a merge policy reconciling
them — for when writes overlap so heavily a lock would just queue everyone. What the guard
does NOT promise is enforcement from a single task owner: holding *a* lease was never
holding *this* file.
Claude Code's `PreToolUse` hook runs this in the repo that owns the file. Elsewhere nothing does.
## Reserving an id
Reading a "Next free ID" line is not reserving it — two agents read the same number and both
use it.
```bash
python3 "$SKILL_DIR/scripts/agent_sync.py" reserve DEC # → DEC-0216
```
Allocation is positional over the **merged** log — every shard, never just this run's — so
every agent computes the same answer. Reserved and not written to git? `release-id` it, or the
number is a hole the board reports as a leak.
## Nothing in a log is ever edited or deleted
Logs are **replayed in order**, so an edit silently rewrites a conclusion other agents already
acted on. Correct by **appending**: release a lease, `release-id` an unused id, supersede a
wrong as-built entry with a later one. Generated pages are the only exception. Lifetimes and
the generated-object contract: `references/two-sources.md`.
## Two documentation sources, and the duty to reconcile them
Git docs answer **how it should be**; the as-built record answers **how it actually is**.
Neither outranks the other, and **the gap between them is the finding**, not a defect. The
duty runs at both ends of a task: `reconcile` before starting, `record` and `reconcile`
after finishing — and `reconcile` is mechanical, so treating its green as agreement is how
a divergence survives both ends.
**Read `references/two-sources.md`** before the first reconcile: it holds the full duty,
what `reconcile` refuses to judge, the generated `setup` snapshot, where a document
belongs, and why nothing is deleted.
## Binding to task-pipeline
This skill supplies stages; the names are `task-pipeline`'s own.
Five of the eleven stages carry an ordering rule: **0** `acquire` before the brief
commits; **1** `reconcile` before code; **3** `reserve` ids before git; **9** the main
write; **10** `merge`/`release` every lease.
**Read `references/pipeline-binding.md`** when wiring `pipeline.json` — per-stage
reasoning, `skills[]` entries, what to guard, and the gate expressions.
## Configuration
Two files, and the split between them is the whole security model.
**`.claude/agent-sync.json`** — *shape*, committed: which backend, TTLs, which files
are guarded, which registers exist, which gates to run.
**`.env.agent-sync`** — *identity*, written by `init` with the keys already in it, mode
600, gitignored. The operator fills the token line and nobody else; `bootstrap` prints
the collection id to paste beside it. Found here, or in a superproject, or wherever
`AGENT_SYNC_ENV` points — `check` prints which. Load it before running agents:
```bash
set -a && . ./.env.agent-sync && set +a
```
Never write a host name or token into the config, a test, an example or a commit; never
handle, echo or `argv`-pass a token value. Offered one in chat, point at that file instead.
**A submodule's config declares only its own registers.** Cross-repository facts belong to
the parent; a service repo listing the parent's decision register is a config defect.
## Non-negotiables
- Append, read back, then act. Never rewrite a coordination document.
- A generated object without its `agent-sync:generated` marker is **refused**, not
overwritten — a human took it over. Contract: `references/two-sources.md`.
- `release` what you `acquire`, on every path including failure.
- A run reports what it leaves behind: expiry ends a lease, not the file — `residue`.
- Credentials never reach `argv`, a log line, or the repository.
- Degrade out loud. `ungated` is an acceptable state; a false claim of enforcement is not.
- Two agents in one checkout are two identities, or the lease is decoration.
- A submodule commit is unfinished until the parent points at it — run `finish`.
- Everything the cloud holds about a durable fact is a link to git, never a substitute.
- Boundary: `.claude/agent-sync.json`. None, or one agent alone — route past. The
operator's waiver: "no coordination" / «без координации».
## References
Each file is loaded on its own trigger, not by default.
| File | Read it when |
|---|---|
| `references/adapter-contract.md` | adding or auditing a knowledge backend — six primitives, the capability flags, an honest degradation path |
| `references/lease-protocol.md` | changing acquisition, expiry, stealing or id allocation |
| `references/backend-outline.md` | making any Outline API call, or debugging one |
| `references/backend-notion.md` | making any Notion API call, or debugging one |
| `references/backend-fs.md` | running without a cloud backend, or explaining degraded mode |
| `references/pipeline-binding.md` | wiring `pipeline.json`, or adding a stage hook |
| `references/hooks.md` | installing, debugging or removing the Claude Code hooks |
| `references/two-sources.md` | before the first reconcile, deciding where a document belongs, or writing a generated object |
| `references/roadmap.md` | configuring `claimTags`, taking or closing a task, or re-planning a board |
| `references/branching.md` | starting work that will produce commits, merging a branch, or asking what landed while you were away |
| `references/earned-rules.md` | asking why identity resolves the way it does, or why `finish` exists |
Missing `references/`? Fetch from `https://raw.githubusercontent.com/ssheleg/agent-sync/main/plugins/agent-sync/skills/agent-sync/references/<file>`.