Skip to content
Back to skills

Agent Sync

ASecurity

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...

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 3, 2026
ai-agentspythongoshellbashexpressdebugginggitapibackendsecurity

Works with

  • claude code
  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 12 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add ssheleg/agent-sync --skill agent-sync --agent claude-code

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.

Security grade badge for Agent Sync
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ssheleg-agent-sync/badge)](https://www.skillsdirectory.com/skills/ssheleg-agent-sync)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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>`.

Files in this skill

  • SKILL.md19.7 KB
  • references/adapter-contract.md4.6 KB
  • references/backend-fs.md2.1 KB
  • references/backend-notion.md7.5 KB
  • references/backend-outline.md4.4 KB
  • references/branching.md6.3 KB
  • references/earned-rules.md2.7 KB
  • references/hooks.md4.7 KB
  • references/lease-protocol.md16.1 KB
  • references/pipeline-binding.md8.3 KB
  • references/roadmap.md5.2 KB
  • references/two-sources.md7.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.