@ssheleg/agent-sync 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +17 -0
- package/package.json +2 -2
- package/plugins/agent-sync/.claude-plugin/plugin.json +2 -2
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +9 -1
- package/plugins/agent-sync/skills/agent-sync/references/earned-rules.md +27 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-314.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +37 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 1.3.5 — 2026-07-29
|
|
7
|
+
|
|
8
|
+
### The two rules this plugin enforces are now stated, with the failures that taught them
|
|
9
|
+
|
|
10
|
+
Both are already code. Neither was written down, and a mechanism nobody can explain is a mechanism
|
|
11
|
+
the next person removes.
|
|
12
|
+
|
|
13
|
+
- **Identity comes before coordination.** Both ways of getting it wrong have happened here: one
|
|
14
|
+
session with two identities (acquired as one, denied by its own guard as the other) and two
|
|
15
|
+
sessions with one identity, which is worse because it is silent — `whoami` reported a lease that
|
|
16
|
+
belonged to somebody else and `release` would have taken it.
|
|
17
|
+
- **Work in a submodule is not finished until its parent points at it.** Neither repository looks
|
|
18
|
+
wrong alone; the disagreement lives between them, which is why it survives every check that runs
|
|
19
|
+
inside one. That is what `finish` is for.
|
|
20
|
+
- **The rule under both:** before trusting a tool's report about the world, make it report something
|
|
21
|
+
you can already verify.
|
|
22
|
+
|
|
23
|
+
## 1.3.4 — 2026-07-29
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **`release` reported success for a lease it did not release, and cleared the
|
|
27
|
+
board claim on the way.** The lease plane refused correctly — a lease held by
|
|
28
|
+
another run stayed held, and `_git_release` printed a note saying so — but the
|
|
29
|
+
command printed `released <key>` over the top of it and exited 0, and
|
|
30
|
+
`write_claim` had already blanked the claim cell before the refusal was
|
|
31
|
+
reached. The result was the board advertising a task as free while the lease
|
|
32
|
+
still held it: the exact disagreement a lease exists to prevent, manufactured
|
|
33
|
+
by the tool. Ownership is now checked **first**, in whichever plane arbitrates
|
|
34
|
+
it; nothing is written when the answer is no; the command exits non-zero and
|
|
35
|
+
says who holds it. Both lease modes were affected.
|
|
36
|
+
- A regression check covers it in `local` and `git` mode, and was probed against
|
|
37
|
+
the old code in both before being trusted.
|
|
38
|
+
|
|
6
39
|
## 1.3.3 — 2026-07-29
|
|
7
40
|
|
|
8
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -137,6 +137,20 @@ claude plugin marketplace add appvillis-com/agent-sync && claude plugin install
|
|
|
137
137
|
|
|
138
138
|
## Update
|
|
139
139
|
|
|
140
|
+
**Update the whole family — one package, every agent.** A bundle with one member current and the
|
|
141
|
+
rest stale is a combination nobody tested:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx sshlg-skills update # installed but behind — updates everything
|
|
145
|
+
npx sshlg-skills install # nothing installed yet
|
|
146
|
+
npx --yes sshlg-skills@latest list # what the current release of each member is
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Restart your agent afterwards: skills and hooks load at session start, so the session that updates
|
|
150
|
+
is not the session that gets the new ones.
|
|
151
|
+
|
|
152
|
+
<details><summary>Updating this one member only</summary>
|
|
153
|
+
|
|
140
154
|
**agent-sync itself** — update every channel you installed, then restart Claude Code:
|
|
141
155
|
|
|
142
156
|
```bash
|
|
@@ -167,6 +181,8 @@ npx sshlg-skills install
|
|
|
167
181
|
Nothing else to update: the coordinator is stdlib-only python, and the npm package has
|
|
168
182
|
zero runtime dependencies.
|
|
169
183
|
|
|
184
|
+
</details>
|
|
185
|
+
|
|
170
186
|
## Set up a project
|
|
171
187
|
|
|
172
188
|
**Initialisation is the first command, and it asks a question rather than guessing.**
|
|
@@ -387,6 +403,7 @@ agent loads on their own trigger rather than by default:
|
|
|
387
403
|
| [`pipeline-binding.md`](plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md) | wiring `pipeline.json`, or adding a stage hook |
|
|
388
404
|
| [`hooks.md`](plugins/agent-sync/skills/agent-sync/references/hooks.md) | installing, debugging or removing the Claude Code hooks |
|
|
389
405
|
| [`two-sources.md`](plugins/agent-sync/skills/agent-sync/references/two-sources.md) | before the first reconcile, or when deciding where a document belongs |
|
|
406
|
+
| [`earned-rules.md`](plugins/agent-sync/skills/agent-sync/references/earned-rules.md) | why identity is resolved the way it is, and why `finish` exists — the two failures that produced both |
|
|
390
407
|
| [`roadmap.md`](plugins/agent-sync/skills/agent-sync/references/roadmap.md) | configuring `claimTags`, taking or closing a task, or re-planning a board |
|
|
391
408
|
|
|
392
409
|
See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md). Everyone
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Let concurrent coding agents share one project without colliding
|
|
3
|
+
"version": "1.3.5",
|
|
4
|
+
"description": "Let concurrent coding agents share one project without colliding \u2014 leases with TTL, race-free id reservation, a run journal and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-sync": "bin/agent-sync.js"
|
|
7
7
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Coordination layer for multi-agent repositories
|
|
3
|
+
"version": "1.3.5",
|
|
4
|
+
"description": "Coordination layer for multi-agent repositories \u2014 leases with TTL, race-free ID reservation, a run journal, a cross-repo signal feed and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "appvillis-com"
|
|
7
7
|
},
|
|
@@ -4,7 +4,7 @@ description: "Use when several coding agents work one repository at the same tim
|
|
|
4
4
|
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."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: "1.3.
|
|
7
|
+
version: "1.3.5"
|
|
8
8
|
author: appvillis-com
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -371,12 +371,20 @@ The mirror is a **rendering** of git, stamped with the source commit. It has no
|
|
|
371
371
|
authority. When its stamp and `HEAD` disagree, the board gate fails — that is
|
|
372
372
|
drift, not a formatting problem.
|
|
373
373
|
|
|
374
|
+
## Two rules, and the failures that taught them
|
|
375
|
+
|
|
376
|
+
Identity comes before coordination: a lease is only a lease if two agents get two identities. A
|
|
377
|
+
submodule commit is unfinished until its parent points at it. Both:
|
|
378
|
+
[`references/earned-rules.md`](references/earned-rules.md).
|
|
379
|
+
|
|
374
380
|
## Non-negotiables
|
|
375
381
|
|
|
376
382
|
- Append, read back, then act. Never rewrite a coordination document.
|
|
377
383
|
- `release` what you `acquire`, on every path including failure.
|
|
378
384
|
- Credentials never reach `argv`, a log line, or the repository.
|
|
379
385
|
- Degrade out loud. `ungated` is an acceptable state; a false claim of enforcement is not.
|
|
386
|
+
- Two agents in one checkout are two identities, or the lease is decoration.
|
|
387
|
+
- A submodule commit is unfinished until the parent points at it — `finish` before you call it done.
|
|
380
388
|
- Everything the cloud holds about a durable fact is a link to git, never a substitute.
|
|
381
389
|
|
|
382
390
|
## References
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Two rules this plugin exists to enforce, and the failures that taught them
|
|
2
|
+
|
|
3
|
+
Both were found by running this plugin on a four-repository project with several agents working at
|
|
4
|
+
once. Each is now code, not advice — and each is stated here because a mechanism nobody can explain
|
|
5
|
+
is a mechanism the next person removes.
|
|
6
|
+
|
|
7
|
+
**Identity comes before coordination.** A lease is only a lease if two agents get two identities.
|
|
8
|
+
Both ends of getting this wrong have happened: deriving the id from `CLAUDE_SESSION_ID` alone gave
|
|
9
|
+
**one session two identities** — it acquired as one and was denied by its own guard as the other —
|
|
10
|
+
and keeping one id per checkout gave **two sessions one identity**, which is worse because it is
|
|
11
|
+
silent. Both acquired as one run, both were guarded as one run, `whoami` reported a lease that
|
|
12
|
+
belonged to somebody else, and `release` would have taken it. The resolution order is
|
|
13
|
+
`AGENT_SYNC_RUN_ID` · `CLAUDE_SESSION_ID` · the session that started this shell · shared, and where
|
|
14
|
+
none can be established the run says so instead of presenting a shared entry as separation.
|
|
15
|
+
|
|
16
|
+
**Work in a submodule is not finished until its parent says so.** A parent records each submodule as
|
|
17
|
+
a pointer to one commit, and moving the submodule does not move the pointer. The work is committed,
|
|
18
|
+
pushed, green in CI and marked done in its own roadmap — and a clone of the parent gets the commit
|
|
19
|
+
**before** it. Neither repository looks wrong on its own; the disagreement exists only between them,
|
|
20
|
+
which is why it survives every check that runs inside one. `finish` is that check, and it is the
|
|
21
|
+
reason this plugin's pipeline binding no longer describes gate expressions it never ran.
|
|
22
|
+
|
|
23
|
+
**The rule under both:** before trusting a tool's report about the world, make it report something
|
|
24
|
+
you can already verify. A test suite reporting green having skipped every assertion, a gate printing
|
|
25
|
+
`FAIL` and exiting `0`, containers reporting healthy while the tools talked to the host's services,
|
|
26
|
+
and a lease held by the wrong identity are the same failure — a tool describing a world it is not
|
|
27
|
+
looking at.
|
|
Binary file
|
|
@@ -32,7 +32,7 @@ from datetime import datetime, timezone
|
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Any
|
|
34
34
|
|
|
35
|
-
VERSION = "1.3.
|
|
35
|
+
VERSION = "1.3.5"
|
|
36
36
|
|
|
37
37
|
CONFIG_PATH = Path(".claude/agent-sync.json")
|
|
38
38
|
ENV_FILE = Path(".env.agent-sync")
|
|
@@ -960,7 +960,36 @@ class Sync:
|
|
|
960
960
|
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
961
961
|
marker.write_text(now_iso())
|
|
962
962
|
|
|
963
|
-
def
|
|
963
|
+
def _lease_holder(self, key: str) -> str | None:
|
|
964
|
+
"""Who holds this lease right now, in whichever plane arbitrates it."""
|
|
965
|
+
if self.lease_mode == "git":
|
|
966
|
+
sha, held = self._git_read_lease(key)
|
|
967
|
+
return held.get("run") if sha else None
|
|
968
|
+
lock = self._local_lock(key)
|
|
969
|
+
if not lock.exists():
|
|
970
|
+
return None
|
|
971
|
+
try:
|
|
972
|
+
return json.loads(lock.read_text()).get("run")
|
|
973
|
+
except (json.JSONDecodeError, OSError):
|
|
974
|
+
return None
|
|
975
|
+
|
|
976
|
+
def release(self, key: str) -> bool:
|
|
977
|
+
"""Release only what this run holds, and say so plainly when it does not.
|
|
978
|
+
|
|
979
|
+
This used to clear the board claim and report success unconditionally. The lease
|
|
980
|
+
plane refused correctly — `_git_release` prints a note and returns — but the
|
|
981
|
+
caller printed "released" over the top of it and exited 0, and `write_claim` had
|
|
982
|
+
already blanked the claim cell on the way in. The board then said the task was
|
|
983
|
+
free while the lease said it was taken: the exact disagreement a lease exists to
|
|
984
|
+
prevent, manufactured by the tool. Ownership is therefore checked FIRST, and
|
|
985
|
+
nothing is written when the answer is no.
|
|
986
|
+
"""
|
|
987
|
+
holder = self._lease_holder(key)
|
|
988
|
+
if holder is not None and holder != self.rid:
|
|
989
|
+
print(f"note: {key} is held by {holder}, not this run — nothing released",
|
|
990
|
+
file=sys.stderr)
|
|
991
|
+
return False
|
|
992
|
+
|
|
964
993
|
for n in self.write_claim(key, None):
|
|
965
994
|
print(f" {n}")
|
|
966
995
|
if self.lease_mode == "git":
|
|
@@ -978,6 +1007,7 @@ class Sync:
|
|
|
978
1007
|
fmt_line("release", key, self.rid))
|
|
979
1008
|
except Fail as exc:
|
|
980
1009
|
print(f"note: released locally, not published ({exc})", file=sys.stderr)
|
|
1010
|
+
return True
|
|
981
1011
|
|
|
982
1012
|
def held(self) -> list[str]:
|
|
983
1013
|
d = self.root / STATE_DIR / "leases"
|
|
@@ -2008,7 +2038,11 @@ def cmd_renew(args: argparse.Namespace) -> int:
|
|
|
2008
2038
|
|
|
2009
2039
|
|
|
2010
2040
|
def cmd_release(args: argparse.Namespace) -> int:
|
|
2011
|
-
|
|
2041
|
+
# Exit non-zero when nothing was released. A caller that scripts `release` in a
|
|
2042
|
+
# cleanup path has no other way to learn the lease is still out there.
|
|
2043
|
+
if not Sync().release(args.key):
|
|
2044
|
+
print(f"NOT released: {args.key} is held by another run", file=sys.stderr)
|
|
2045
|
+
return 1
|
|
2012
2046
|
print(f"released {args.key}")
|
|
2013
2047
|
return 0
|
|
2014
2048
|
|