agent-sync

agent
Guvenlik Denetimi
Basarisiz
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Basarisiz
  • network request — Outbound network request in .github/workflows/release.yml
  • child_process — Shell command execution capability in bin/agent-sync.js
  • spawnSync — Synchronous process spawning in bin/agent-sync.js
  • fs.rmSync — Destructive file system operation in bin/agent-sync.js
  • os.homedir — User home directory access in bin/agent-sync.js
  • fs module — File system access in bin/agent-sync.js
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Coordinate coding agents with task leases, shared-file claims and Git-backed handoffs.

README.md

agent-sync

CI
npm
license
site
skills.sh

Coordinate concurrent coding agents with expiring claims, race-free ids and a durable run journal.

npx skills add ssheleg/agent-sync

Ask: Set up leases before three agents edit this repository.

Detailed docs →

Docs, and every skill → · this skill's page · follow @sshlg93 on X

Loads in DeepSeek Harness (dsh) with no plugin to write: it reads the
Agent Skills standard directly, scanning ~/.agents/skills — where npx skills add puts this pack — at rank 500.

Several coding agents, one repository, no collisions — and each one can see what the
others are doing.

agent-sync is an agent skill (plus a Claude Code plugin) that gives concurrent
coding agents a coordination plane: leases with a TTL, race-free id reservation, a run
journal, a cross-repo signal feed and a generated board.

The problem

When more than one agent works a project at the same time, the coordination substrate
most teams already have — a decisions log, a roadmap, a board, per-repo task files —
stops being enough. Every one of those is a file edited by hand. That works for people
taking turns and fails for agents working at once:

What goes wrong Why
Two agents mint the same decision id "Next free id" is a line in a file; reading it is not reserving it
A claim blocks a task forever A role name is not a holder, and it has no expiry
Two agents start the same task Git shows what was committed, never what is in flight
An agent is blocked but not informed Knowing a task is taken is not knowing who has it or what they touch
Merge conflicts on every shared register Everyone writes the same three files
A cross-repo dependency is never noticed Filing one notifies nobody

agent-sync closes exactly those, and nothing else.

The idea

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, so your single-source-of-truth rules stay intact.

The knowledge base never decides a lease. It cannot: measured against a real
instance, twelve concurrent appends to one document returned twelve successes and left
three lines. Exclusion comes from something that genuinely has compare-and-swap — an
atomic file create on one machine (leaseBackend: "local"), or a pushed git ref across
machines (leaseBackend: "git"), where the remote's non-fast-forward rejection is the
CAS. Id reservation still replays the log, which is safe because allocation is positional
and every reader computes the same answer.

What you get

  • Leases with a TTL — claim a task, renew automatically, steal an expired one.
    Exclusive across machines with leaseBackend: "git"; the tool always states which
    guarantee you have rather than implying the stronger one.
  • The claim written through to the roadmap — one row, one cell, refused on ambiguity,
    and restored verbatim on release. Closing a task stays yours.
  • Awareness, not just exclusion — status lists every other run's live holdings,
    so an agent learns who holds a task and what they are touching, instead of only that
    it is taken.
  • Race-free id reservation — positional allocation over the log, so two agents
    cannot be handed one number.
  • A run journal — what each run did, with commits, gate results and evidence.
  • A cross-repo signal feed — filed → accepted → delivered → closed. status
    surfaces what landed since this run last looked, watermarked per run so it stays quiet
    until something actually changes.
  • A generated board — machine-written, commit-stamped, and it refuses to overwrite a
    page a human took over.
  • Enforcement hooks for Claude Code that deny an edit to a guarded register file, or
    a commit staging one, without a live lease.

Requirements

Requirement Why Check
python3 ≥ 3.9 the coordinator is one stdlib-only script — HTTP included, nothing to pip install python3 --version
git the record plane, and the cross-machine lease store git --version
bash the four Claude Code hook scripts bash --version
Node ≥ 18 only for the npx @ssheleg/agent-sync installer node --version
task-pipeline agent-sync supplies stages, it does not define them — without it status prints one line and stops npx sshlg-skills install
A knowledge-base instance (optional) the shared record, awareness and board; without one the fs backend keeps leases but loses cross-agent visibility —

Install

npx @ssheleg/agent-sync install

Claude Code gets the plugin; every other agent gets the skill through the
skills CLI. The duplicate plain copy in
~/.claude/skills/ is pruned afterwards, because that shadow silently serves a stale
skill over the installed plugin — one channel per agent is the rule.

Restart Claude Code after installing, so it picks the plugin up.

Other install routes

Track main from GitHub instead of the npm release:

npx github:ssheleg/agent-sync install

Claude Code only, no skills CLI:

npx @ssheleg/agent-sync install --claude-only

Pick which agents the skills CLI installs for:

npx @ssheleg/agent-sync install --agent cursor,codex

Or add the plugin by hand — the full <name>@<name> form is required:

claude plugin marketplace add ssheleg/agent-sync && claude plugin install agent-sync@agent-sync

Update

Update the whole family — one package, every agent. A bundle with one member current and the
rest stale is a combination nobody tested:

npx sshlg-skills update               # installed but behind — updates everything
npx sshlg-skills install              # nothing installed yet
npx --yes sshlg-skills@latest list    # what the current release of each member is

Restart your agent afterwards: skills and hooks load at session start, so the session that updates
is not the session that gets the new ones.

Updating this one member only

agent-sync itself — update every channel you installed, then restart Claude Code:

claude plugin marketplace update agent-sync && claude plugin update agent-sync@agent-sync && npx --yes skills update agent-sync --global --yes

Re-running the installer works too, but pin @latest or npx may serve you its cache:

npx @ssheleg/agent-sync@latest install

Check what you are actually running — the plugin and the skill must report the same
version, and a mismatch means one channel is stale:

claude plugin list | grep agent-sync
python3 ~/.claude/plugins/cache/*/agent-sync/*/skills/agent-sync/scripts/agent_sync.py --version

Its dependencies — task-pipeline (and the rest of the same family) come from one
installer, which also prunes the shadow copies:

npx sshlg-skills install

Nothing else to update: the coordinator is stdlib-only python, and the npm package has
zero runtime dependencies.

Set up a project

Initialisation is the first command, and it asks a question rather than guessing.

/agent-sync init

The agent asks where coordination state should live — a knowledge cloud, or local files
— and, for the cloud, the instance URL. Then it writes two files:

File Holds Committed?
.claude/agent-sync.json shape — backend, TTLs, guarded files, registers, gates yes
.env.agent-sync identity — instance URL, token, collection id no — mode 600, added to .gitignore

The token line is written empty. Creating the API token in your own instance and
placing it is your step, and it stays yours: the tool never asks for a token in chat,
never echoes one, and never passes one as a command-line argument.

Where the token goes depends on the backend (1.22.0).

  • notion — the Observatory vault first. Write only the token (one line, no NAME=)
    into a mode-600 file outside the repository, then

    python3 "$(project-observatory full-path)/tools/vault.py" put <project> local AGENT_SYNC_NOTION_TOKEN < that-file
    

    and delete that file and the AGENT_SYNC_NOTION_TOKEN line of .env.agent-sync.
    In a submodule tree, put EVERY repository's slot first, then delete the line. One
    .env.agent-sync in the superproject serves each submodule below it, but the vault is
    asked per repository (each one's own project name): delete the shared line before each
    submodule has its slot and those submodules lose the token, falling back to fs.
    <project> is the folder name of the repository's main checkout (a linked worktree's
    folder is not a project); init and status print the exact command. Every command
    that touches the record plane then re-runs itself under
    use_secret.py run --vault-only, so the value reaches the process environment and
    never argv. The guard, renew and SessionEnd's release --held never ask the vault (the
    lease is a local lock or a git ref; the token would only publish a visibility line). Do not source .env.agent-sync: a
    token in your shell's environment wins over the vault. Without Observatory, the file
    line still works and status reports it as degraded: plaintext token file.

  • outline — .env.agent-sync in 1.22.0; vault-first for it is an open board row.
    Every command and hook reads the file itself, so nothing needs to be sourced.

Then create the container the coordination log lives in, once per project, and paste the
id it prints into AGENT_SYNC_OUTLINE_COLLECTION:

/agent-sync bootstrap

Verify the setup — idempotent, repairs what is missing, and names exactly one next
action:

/agent-sync status

Everyday use

In an agent session you use the slash command (/agent-sync acquire ASC-072 — the verb is
acquire, the same one the CLI takes); the same commands run directly against the
coordinator script, which is what the hooks and CI do:

python3 "$SKILL_DIR/scripts/agent_sync.py" <command>
Command Does
init Run first. Ask where state lives, write config + gitignored env file, print your step
status Inspect, repair, report — other runs' leases, signals new since you last looked, and check's verdict on the setup
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>. The key is kept verbatim — T-1.2 is reported, released and stored on the remote as T-1.2; a key with whitespace, a backtick or `
renew <KEY> Extend the lease — moves the timestamp expiry is computed from. In Claude Code the PostToolUse hook does this for you
release <KEY> Give the lease back. Always, including on failure. Says released only once the remote no longer has the ref; otherwise NOT released, exit 1
reserve <REG> Reserve the next id in a register (DEC, OQ, DEP, …). Prints the id
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
signal <DEP-ID> <state> Move a cross-repo dependency: filed/accepted/delivered/closed/refused
guard <path> May this run write that path? Exit 0 = yes, 2 = no
board Regenerate the read-only board and the mirror from git
whoami Print this run's id and its held leases
residue Expired locks still on disk, classified — this run's spent ones, and the foreign or ambiguously owned ones it reports and leaves alone
reap [KEY…] Clear only the locks this run can prove it owns and has spent, then re-read the directory to confirm the teardown
reap --long-expired [--older-than-days N] [--apply] List locks on both planes expired at least N days ago (default 7), each with the classifier's verdict; --apply clears them. A sweep by age alone — the operator's decision, never an agent's (lease protocol)
merge Land this branch on the integration branch: conflicts checked first, merge log written, lease released
merges What landed while you were on your branch

The shape that matters:

acquire → work on a branch → merge

Never skip the last step, including when the work failed. An abandoned lease blocks the
task until its TTL expires, and the next agent cannot tell "in progress" from "crashed an
hour ago". A lease is a promise to come back. merge releases for you; without a branch,
release by hand.

Work on a branch, land it with merge

The integration branch is somebody else's stable base, so nothing about work in flight is
committed there. acquire writes the claim through to the roadmap only on the
integration branch; on any other branch it says so and keeps the holder in the coordination
plane, where status already shows it to every agent — no one has to fetch your branch to
see who has what, and the shared roadmap does not become the file every branch edits.

python3 "$SKILL_DIR/scripts/agent_sync.py" merge --key ASC-072 --summary "what landed"

The local integration branch is fast-forwarded to origin/<target> first, so the
preflight and the merge share a base; one that has genuinely diverged is refused with both
counts. Conflicts are then computed with git merge-tree before anything is touched —
on conflict it names the files, changes nothing and exits non-zero, so a resolution nobody
reviewed never reaches the integration branch. Then it merges --no-ff, records the merge,
and releases the lease named by --key. --dry-run stops after the checks; --push
pushes afterwards.

The merge log — docs/MERGES.md, configurable via mergeLog — answers the question an
agent coming back from a branch cannot answer from git log alone: what landed while I
was away, and was any of it near my work.
Entries inside retentionDays (7) keep their
detail; older ones are folded to one line each on the next write, so the file stays
readable without a cron job.

python3 "$SKILL_DIR/scripts/agent_sync.py" merges          # recent detail
python3 "$SKILL_DIR/scripts/agent_sync.py" merges --all    # plus the compacted tail

This is the flow for agents sharing one repository. A project that reviews through pull
requests keeps doing that — merge is then the wrong command, and finish is still the
right one. Full doctrine:
references/branching.md.

The lease is not the claim. The lease says who holds the task now and expires; the
durable claim is the tag in git — todo (claimed: r-7f3a91), rendered from the
claimTags.held template, naming the run, not a role. acquire writes that tag
through and release restores exactly what was there, so one fact keeps one home.

Configuration

.claude/agent-sync.json — committed, validated against
agent-sync.schema.json, starting point in
agent-sync.example.json:

Key Meaning
backend outline, notion or fs (required)
leaseTtlSeconds how long a lease survives without a renew (default 2700)
renewIntervalSeconds how often a live run renews (default 300)
gated whether runs may be recorded as enforced at all
idRegisters register → the git file that owns it, and its "next free id" pattern. Needs an allocator: leaseBackend: "git" (any backend, fs included) or a cloud backend
guardedFiles registry files no run may edit without a live lease
claimTags file → the durable claim tag acquire/release writes through
gates commands the pipeline stages run as gates
mirror which git files are rendered into the read-only mirror
integrationBranch where work lands and the only branch a claim is written on (default: the repo's own)
mergeLog file and retentionDays for the merge log (default docs/MERGES.md, 7)
scheduledIntervalSeconds the interval (integer, ≥ 60 — check enforces it) of a scheduled job that stands aside while a lease is held (Observatory's tick); status, and the guard once per lease as one JSON object (systemMessage for the person, hookSpecificOutput.additionalContext for the model), warn when this run has held one longer. Absent: no warning

.env.agent-sync — gitignored, mode 600. It is looked for in this repository, then in
each superproject above it (so one credentials file serves a tree of submodules), and
nowhere else. AGENT_SYNC_ENV=/path/to/file names one explicitly and wins over both.
Until 1.7.0 the search continued into any parent directory, so a stray file in a home or
work directory silently pointed every project beneath it at one collection — check
now prints which file is in force, and says when it comes from outside the repository.

AGENT_SYNC_BACKEND=outline
AGENT_SYNC_OUTLINE_URL=https://<your-instance>
AGENT_SYNC_OUTLINE_TOKEN=          # you fill this line, nobody else
AGENT_SYNC_OUTLINE_COLLECTION=     # printed by `bootstrap`

Never write a host name or a token into the config, a test, an example or a commit. If
an agent offers to handle a token for you, that is the wrong answer.

A submodule's config declares only its own registers. Cross-repository facts belong
to the parent repository; a service repo listing the parent's decision register is a
configuration defect.

Backends

Two settings, two jobs. backend chooses the record plane — where the log, the
signals and the board live. leaseBackend chooses what actually decides a lease. The
knowledge base is never the second one: measured against a real instance, twelve
concurrent appends to one document returned twelve successes and left three lines.

backend — the record plane What it gives
outline Outline, hosted or self-hosted. Every repository and machine reads one plane: shared awareness, cross-repo signals, the board
fs Local files. No credentials, and no visibility to an agent on another machine
leaseBackend — the lease Guarantee
git Exclusive across machines. The remote's non-fast-forward rejection is a real compare-and-swap
local (default) Exclusive on this machine, advisory across machines. An atomic file create

status, acquire and check all state which of the two you have, in the same words,
because a lease that is not actually exclusive is worse than none: the other agent has
stopped checking. runs recorded: gated follows the lease mode — never the record plane.

Adding one: read
references/adapter-contract.md.

Enforcement hooks

This is the only part of the plugin that executes code on your machine. Everything
else — the skill, its references — is text an agent reads. Four bash scripts, bundled in
the plugin and run by Claude Code on the events below, each with a timeout (15–20s) so a
hung script cannot stall a session. Read them before installing: they are short, and
SECURITY.md lists every path the install touches and why.

Installing globally changes nothing in repositories without .claude/agent-sync.json.
The lifecycle hooks (SessionStart, PostToolUse, SessionEnd) act on the session's
project and exit immediately when it has no config. The PreToolUse guard asks a
different question — the repository that owns the write decides: for an edit, the
git repository containing the file; for a Bash write, the repository of each target; for a
commit, the one named by -C or cd. Where a file's claim needs a remote (git leases, a
cloud record plane), the guard waits for it only AGENT_SYNC_GUARD_BUDGET seconds (1.5 by
default, 1.23.0) and lets the write proceed under the task lease while the claim lands. No
config there → allowed; config there → checked from that repository's root, against its
guardedFiles and its leases, whatever the session's own project is. A path inside no
git repository is allowed. The guard can deny a tool call and never grants one that
would otherwise be denied.

Hook Runs Effect
SessionStart startup, resume, clear Stamps the session. On startup only, status --brief: one line naming what this run must act on (the leases it holds, its own expired locks, a claim tag out of step), or nothing. AGENT_SYNC_SESSION_VERBOSE=1 prints the full status instead
PreToolUse Edit/Write/MultiEdit/NotebookEdit, and every Bash call — a parsed git commit, and (1.22.0) a Bash write by >/>>/>|/&>, tee, sed -i, cp or mv Denies the call (exit 2) when the path is guarded in the repository that owns it and this run holds no lease there; a git commit is checked against every staged path of the repository it commits to and every file the same command stages (git add X && git commit, git commit -a, AS-16). Bash writes are parsed in every session and checked in the repository that owns each target; what the parse cannot resolve ($VAR targets, the arguments xargs reads from stdin, perl -i, a runtime's own write) it skips — the capability matrix in guard.sh lists each shape
PostToolUse every tool call Throttled renew — touches the network at most once per renewIntervalSeconds; also in every other repository this session took a lease in
SessionEnd session end Releases every lease this run holds — here, and (1.22.0) in every other repository this session took a lease in, each by its own process inside the same 2 s

Details and removal:
references/hooks.md.

Where it plugs into task-pipeline

agent-sync supplies stages; it does not define them. It binds to task-pipeline's
stages 0, 1, 3, 4, 5, 9 and 10 — lease before the brief is committed, reconcile before
code is written, reserve ids before they reach git, register file ownership for parallel
groups, signal and regenerate the board at docs, release everything at acceptance. The
stage numbers are stated once, in the marker at the top of the binding reference, and a
check fails when a document stops agreeing with it. Wiring:
references/pipeline-binding.md.

Limits, stated plainly

  • Hooks are Claude Code only. On Cursor, Codex and the rest there is no PreToolUse,
    so nothing blocks a guarded edit; the same checks run as a self-check and the run is
    recorded ungated. Read the board's column rather than assuming.
  • Ordering, not clocks. Document order decides who holds a lease; timestamps only
    expire one. Agents' clocks differ and the protocol does not depend on them.
  • A reserved id that never reaches git is reported, not reclaimed. A half-written
    decision on a branch is not an unused number.
  • fs is not cross-machine. As a record plane it is invisible to agents on another
    host, and the default local lease is a mutex on this one. Set leaseBackend: "git"
    when a fleet spans machines; the tool says which guarantee you have rather than
    implying the stronger one.

Troubleshooting

Symptom Cause and fix
task-pipeline is not installed and status stops Intentional — there are no stages to bind to. npx sshlg-skills install
⚠ this lease is advisory, not enforced gated: false in the config, or a leaseBackend that is neither local nor git. Fix the mode — an unknown one claims nothing on purpose
lease: local — advisory across machines Expected on the default. Set leaseBackend: "git" (and a reachable leaseRemote) when agents run on more than one machine
Every acquire reports lost Check the holder in status. The lease is decided by a lock file or a git ref, never by the log, so this is a real holder — not a parse failure
A command stops with the … log is N/M unparseable Past 2%, every command that replays a log refuses it rather than acting on a partial history. Fix or remove the malformed lines; acquire is unaffected, because a lease is not decided there
Guarded edit blocked in Claude Code Working as designed: acquire the key first, or unstage the file. The lease must be taken in the repository that owns the file (cd <that repo> && agent_sync.py acquire <KEY>) — a lease in the session's own project does not cover another repository
release printed released T-1-2 but git ls-remote origin 'refs/agent-sync/leases/*' still shows T-1.2 agent-sync 1.21.2 and earlier reported a dotted key by its dash-slug (#25). Update; 1.21.3 reports the real key, and release T-1-2 or release --held still finds a lease 1.21.2 took
NOT released: <KEY> with is still on 'origin' after the delete The remote refused the delete (a protected-ref rule) or could not be reached. The lease is still held and the local note is kept; fix the remote and release again
Guarded edit not blocked You are not on Claude Code. Run guard <path> yourself; the run is ungated
AGENT_SYNC_OUTLINE_COLLECTION is not set Run bootstrap and paste the printed id into .env.agent-sync
An HTTP 400/403 from the backend The response body is surfaced verbatim — read it; a bad collection id and a bad token look nothing alike
A stale skill after updating Two channels serving one skill. Delete ~/.claude/skills/agent-sync and keep the plugin
The board refuses to write A human took the page over (no generated marker on line 1). Reported, never overwritten

Uninstall

claude plugin uninstall agent-sync@agent-sync
npx --yes skills remove agent-sync --global --yes

Project files stay where they are; delete .claude/agent-sync.json, .env.agent-sync
and .agent-sync/ if you want the project clean too.

Develop and verify

These run in a clone of this repository. The published npm package ships no
test/ directory, so from an install they are names, not commands.

python3 test/validate.py             # manifests, version sync, no host/credential leaks
npm test                             # every CI test suite, each in a temporary HOME (test/run_gate.py); self-test, smoke and plugin validate stay CI-only
npm run test:negatives               # validate.py --self-test: the validator must still be able to fail
npm run test:all                     # both of the above

The default npm test is the fast gate. The self-test re-runs the whole validator once
per planted defect, so it lives in test:negatives; CI (validate.yml) and the release
workflow run it on every push and every tag, so nothing a merge or a release enforces
moved.

What ships: one skill (agent-sync), scripts/agent_sync.py (stdlib only), four hook
scripts, the slash command, agent-sync.schema.json, and twelve reference contracts the
agent loads on their own trigger rather than by default:

Reference Read it when
adapter-contract.md adding or auditing a knowledge backend
lease-protocol.md changing acquisition, expiry, stealing or id allocation
backend-outline.md making any Outline API call, or debugging one
backend-notion.md making any Notion API call, or debugging one
backend-fs.md running without a cloud backend, or explaining degraded mode
pipeline-binding.md wiring pipeline.json, or adding a stage hook
hooks.md installing, debugging or removing the Claude Code hooks
two-sources.md before the first reconcile, or when deciding where a document belongs
earned-rules.md why identity is resolved the way it is, and why finish exists — the two failures that produced both
roadmap.md configuring claimTags, taking or closing a task, or re-planning a board
branching.md starting work that will produce commits, merging a branch, or asking what landed while you were away
fabric-coordination.md mapping Fabric's coordination contract to commands, and what is not conformant yet

See CONTRIBUTING.md and CHANGELOG.md. Everyone
taking part is expected to follow the Code of Conduct.
Security reports: SECURITY.md.

agent-sync also ships in the sshlg-skills
bundle, which installs the whole family for Claude Code, Cursor, Codex and 70+
other agents with one command.

Author

Built by ssheleg — sshlg.me

License

MIT © ssheleg

Yorumlar (0)

Sonuc bulunamadi