@ssheleg/agent-sync 1.7.0 → 1.7.1
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 +65 -0
- package/README.md +3 -2
- package/package.json +1 -1
- package/plugins/agent-sync/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-sync/commands/agent-sync.md +2 -2
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +14 -19
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-312.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +44 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
+
## v1.7.1
|
|
2
|
+
|
|
3
|
+
**A second audit, along a different axis: not whether the tool keeps its promises, but whether an
|
|
4
|
+
agent reading this skill can follow it and get a correct result.** The scenarios were executed, not
|
|
5
|
+
imagined — cold start, the whole adoption chain, the per-task cycle, two agents contending for one
|
|
6
|
+
task, every hook with a realistic payload, and the edges (no git repository, uninitialised project,
|
|
7
|
+
absolute paths, `git -C <dir> commit`). The coordination core came through clean. What did not was
|
|
8
|
+
everything the agent *reads*.
|
|
9
|
+
|
|
10
|
+
### The documents the tool generates were two versions behind the doctrine it teaches
|
|
11
|
+
|
|
12
|
+
`setup` writes the snapshot every agent is told to read first — *"it states how documentation and
|
|
13
|
+
coordination work here"* — and `scaffold` seeds `AGENTS.md`. Neither mentioned a branch or `merge`
|
|
14
|
+
even once. Both prescribed a cycle ending in `release`, and the snapshot stated *"the claim tag in
|
|
15
|
+
git is written through"* as an unconditional fact, which has been false on any branch since 1.4.0 —
|
|
16
|
+
the branch being where the doctrine says the work belongs.
|
|
17
|
+
|
|
18
|
+
So an agent doing exactly what the skill instructs — trust the generated snapshot — got the
|
|
19
|
+
workflow from two releases ago. Regenerating did not help: the generator was what was stale. Both
|
|
20
|
+
now carry the branch rule, `merge --key`, and this project's integration branch by name.
|
|
21
|
+
|
|
22
|
+
`AGENTS.md` stopped restating the cycle altogether. It is seeded once and **never overwritten**, so
|
|
23
|
+
a copy of the protocol there is frozen on the day the project was created while the tool moves on —
|
|
24
|
+
and every project scaffolded before today would have kept the old one forever. It now points at the
|
|
25
|
+
snapshot, which is regenerable and which `check` fails on when it goes stale. One fact, one home.
|
|
26
|
+
|
|
27
|
+
### `check` blessed a project an agent cannot work in
|
|
28
|
+
|
|
29
|
+
A configuration declaring `idRegisters` on a backend whose `reserve` always raises passed as
|
|
30
|
+
`setup healthy`. `check`'s own promise is that it refuses a rule pointing at what is not there, and
|
|
31
|
+
a register nobody can allocate from is exactly that — while the snapshot it generates instructs
|
|
32
|
+
every agent to run `agent_sync.py reserve DEC`, which cannot succeed there. It is now a problem,
|
|
33
|
+
named with both ways out.
|
|
34
|
+
|
|
35
|
+
### The slash command offered a verb the CLI does not have
|
|
36
|
+
|
|
37
|
+
`argument-hint` advertised `claim <KEY>` and the README showed `/agent-sync claim ASC-072`. The
|
|
38
|
+
command is `acquire`; `claim` is an `invalid choice`. First thing an agent reads, first thing it
|
|
39
|
+
types.
|
|
40
|
+
|
|
41
|
+
### `$SKILL_DIR` was used in every example and defined nowhere
|
|
42
|
+
|
|
43
|
+
Six invocations tell the agent to run `python3 "$SKILL_DIR/scripts/agent_sync.py"`, and the only
|
|
44
|
+
explanation was the prose *"this skill's own directory"*. Nothing gave a value. The Cursor rule
|
|
45
|
+
names a concrete path; the skill body now names both — `${CLAUDE_PLUGIN_ROOT}/skills/agent-sync`
|
|
46
|
+
and `~/.agents/skills/agent-sync`.
|
|
47
|
+
|
|
48
|
+
### What the audit found working
|
|
49
|
+
|
|
50
|
+
Worth recording, because a report that only lists faults says nothing about the rest: two agents
|
|
51
|
+
contending for one task behave correctly end to end — the second loses, sees who holds it and in
|
|
52
|
+
which repository, is denied the guarded file, and cannot release a lease it does not hold. The
|
|
53
|
+
guard denies `Edit`, `Write`, `NotebookEdit` and absolute paths, and blocks `git commit`,
|
|
54
|
+
`git -C <dir> commit` and `cd <dir> && git commit` when a guarded file is staged, while letting
|
|
55
|
+
`git log --grep=commit` and malformed JSON through. `SessionStart` stamps the identity and prints
|
|
56
|
+
the awareness block; `SessionEnd` releases. The adoption chain works with and without a pre-existing
|
|
57
|
+
`docs/`. An uninitialised project and a non-git directory both answer with the next action and a
|
|
58
|
+
non-zero exit.
|
|
59
|
+
|
|
60
|
+
### New checks
|
|
61
|
+
|
|
62
|
+
`check_every_advertised_verb_exists`, `check_generated_docs_carry_current_doctrine`,
|
|
63
|
+
`check_registers_need_a_backend_that_can_reserve`, `check_skill_gives_a_resolvable_script_path` —
|
|
64
|
+
with four more self-test fixtures. The validator now plants and catches 32 distinct defects.
|
|
65
|
+
|
|
1
66
|
## v1.7.0
|
|
2
67
|
|
|
3
68
|
**Observability, honest degradation, and the removal of things that were never load-bearing.**
|
package/README.md
CHANGED
|
@@ -225,8 +225,9 @@ action:
|
|
|
225
225
|
|
|
226
226
|
## Everyday use
|
|
227
227
|
|
|
228
|
-
In an agent session you use the slash command (`/agent-sync
|
|
229
|
-
|
|
228
|
+
In an agent session you use the slash command (`/agent-sync acquire ASC-072` — the verb is
|
|
229
|
+
`acquire`, the same one the CLI takes); the same commands run directly against the
|
|
230
|
+
coordinator script, which is what the hooks and CI do:
|
|
230
231
|
|
|
231
232
|
```bash
|
|
232
233
|
python3 "$SKILL_DIR/scripts/agent_sync.py" <command>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-sync",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
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"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sync",
|
|
3
3
|
"displayName": "Agent Sync",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.1",
|
|
5
5
|
"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.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Coordinate concurrent agents — initialise the shared knowledge store, check status,
|
|
3
|
-
argument-hint: "[init|status|
|
|
2
|
+
description: Coordinate concurrent agents — initialise the shared knowledge store, check status, take a task, reserve an id, or regenerate the board.
|
|
3
|
+
argument-hint: "[init|status|check|acquire <KEY>|release <KEY>|reserve <REG>|board|merge|finish]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Invoke the `agent-sync` skill.
|
|
@@ -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.7.
|
|
7
|
+
version: "1.7.1"
|
|
8
8
|
author: ssheleg
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -113,10 +113,9 @@ storage question gets asked and answered, once, and written down.
|
|
|
113
113
|
- or local files (`fs`) — no credentials, and no visibility to an agent on another
|
|
114
114
|
machine: no shared awareness, no cross-repo signals, no shared board.
|
|
115
115
|
|
|
116
|
-
The lease is decided separately by `leaseBackend`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
with a local lock is *not* exclusion across them. Report the one you actually have.
|
|
116
|
+
The lease is decided separately by `leaseBackend` (trap 2), and **`gated` follows that
|
|
117
|
+
choice, never the record plane**. Report the guarantee you actually have, not the
|
|
118
|
+
stronger one the record plane suggests.
|
|
120
119
|
2. **If cloud: the instance URL.** The URL is configuration, not a secret, so you
|
|
121
120
|
may write it. The **token is not** — you never ask for it in chat, never read it
|
|
122
121
|
back, and never place it yourself.
|
|
@@ -144,9 +143,9 @@ the file is their step, and the design depends on it staying theirs.
|
|
|
144
143
|
python3 "$SKILL_DIR/scripts/agent_sync.py" status
|
|
145
144
|
```
|
|
146
145
|
|
|
147
|
-
Idempotent. Inspects, repairs
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
Idempotent. Inspects, repairs, reports, names exactly ONE next action — and carries
|
|
147
|
+
`check`'s verdict, so the command every session runs and the one that validates the
|
|
148
|
+
setup cannot give two answers about one project.
|
|
150
149
|
|
|
151
150
|
**Read the two awareness sections it prints — they are the point, not decoration.**
|
|
152
151
|
|
|
@@ -196,7 +195,9 @@ npx sshlg-skills install
|
|
|
196
195
|
| `scaffold [--full]` | Create only what is missing, never a line over anything that exists. `--full` also seeds the question register, the index, the dependency board, the data model with its entity register, and the docs gate |
|
|
197
196
|
| `finish [--gates]` | Is the **work** finished — every repository clean, pushed and pointed at, no lease left held. `check` answers whether the project is wired correctly; this answers whether you are done |
|
|
198
197
|
|
|
199
|
-
`$SKILL_DIR` is this skill's own directory
|
|
198
|
+
`$SKILL_DIR` is this skill's own directory: `${CLAUDE_PLUGIN_ROOT}/skills/agent-sync`
|
|
199
|
+
under the Claude Code plugin, `~/.agents/skills/agent-sync` elsewhere. Resolve it once
|
|
200
|
+
per session and reuse it — do not guess a path. Every command reads
|
|
200
201
|
`.claude/agent-sync.json` from the project root and needs no arguments beyond those
|
|
201
202
|
listed.
|
|
202
203
|
|
|
@@ -321,16 +322,10 @@ Two files, and the split between them is the whole security model.
|
|
|
321
322
|
**`.claude/agent-sync.json`** — *shape*, committed: which backend, TTLs, which files
|
|
322
323
|
are guarded, which registers exist, which gates to run.
|
|
323
324
|
|
|
324
|
-
**`.env.agent-sync`** — *identity*,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
AGENT_SYNC_OUTLINE_URL=https://<instance>
|
|
329
|
-
AGENT_SYNC_OUTLINE_TOKEN= # the operator fills this line, nobody else
|
|
330
|
-
AGENT_SYNC_OUTLINE_COLLECTION= # printed by `bootstrap`
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Load it before running agents:
|
|
325
|
+
**`.env.agent-sync`** — *identity*, written by `init` with the keys already in it, mode
|
|
326
|
+
600, gitignored. The operator fills the token line and nobody else; `bootstrap` prints
|
|
327
|
+
the collection id to paste beside it. Found here, or in a superproject, or wherever
|
|
328
|
+
`AGENT_SYNC_ENV` points — `check` prints which. Load it before running agents:
|
|
334
329
|
|
|
335
330
|
```bash
|
|
336
331
|
set -a && . ./.env.agent-sync && set +a
|
|
Binary file
|
|
@@ -33,7 +33,7 @@ from datetime import datetime, timezone
|
|
|
33
33
|
from pathlib import Path
|
|
34
34
|
from typing import Any
|
|
35
35
|
|
|
36
|
-
VERSION = "1.7.
|
|
36
|
+
VERSION = "1.7.1"
|
|
37
37
|
|
|
38
38
|
CONFIG_PATH = Path(".claude/agent-sync.json")
|
|
39
39
|
ENV_FILE = Path(".env.agent-sync")
|
|
@@ -2094,19 +2094,29 @@ class Sync:
|
|
|
2094
2094
|
"## The cycle, per task",
|
|
2095
2095
|
"",
|
|
2096
2096
|
"```",
|
|
2097
|
-
"
|
|
2097
|
+
"merges → what landed while you were away",
|
|
2098
|
+
"status → who else is working, and what changed since you last looked",
|
|
2098
2099
|
"reconcile → resolve every divergence BEFORE writing code",
|
|
2099
|
-
"
|
|
2100
|
+
"branch → work happens on one; the integration branch is somebody",
|
|
2101
|
+
" else's stable base",
|
|
2102
|
+
"acquire ID → take the lease. On the integration branch the claim tag is",
|
|
2103
|
+
" written through to git; on any other branch the holder stays",
|
|
2104
|
+
" in the coordination plane, where `status` shows it to everyone",
|
|
2100
2105
|
" … work …",
|
|
2101
2106
|
"record → what you ACTUALLY built, with the decision id and files",
|
|
2102
2107
|
" … update the git documents in the same change …",
|
|
2103
2108
|
"reconcile → check both sides again",
|
|
2104
2109
|
"board → regenerate the shared view",
|
|
2105
|
-
"
|
|
2110
|
+
"merge --key → land the branch: conflicts checked first, the merge recorded,",
|
|
2111
|
+
" that lease released. Without a branch, `release ID` by hand",
|
|
2112
|
+
" — on every path, including failure",
|
|
2106
2113
|
"```",
|
|
2107
2114
|
"",
|
|
2115
|
+
f"This project's integration branch is `{self.integration_branch}`.",
|
|
2116
|
+
"",
|
|
2108
2117
|
"Full doctrine ships with the skill: `references/two-sources.md`,",
|
|
2109
|
-
"`references/lease-protocol.md`, `references/
|
|
2118
|
+
"`references/lease-protocol.md`, `references/branching.md`,",
|
|
2119
|
+
"`references/roadmap.md`, `references/pipeline-binding.md`.",
|
|
2110
2120
|
]
|
|
2111
2121
|
return "\n".join(L) + "\n"
|
|
2112
2122
|
|
|
@@ -2889,31 +2899,24 @@ annotate the old entry's status line. The body of the old entry stays as history
|
|
|
2889
2899
|
|
|
2890
2900
|
AGENTS_SEED = """# AGENTS.md — working protocol
|
|
2891
2901
|
|
|
2892
|
-
**Read [`{snapshot}`]({snapshot}) first
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
## Before you write anything
|
|
2897
|
-
|
|
2898
|
-
Several agents may work this repository at once.
|
|
2902
|
+
**Read [`{snapshot}`]({snapshot}) first, and follow the cycle it states.** That file is
|
|
2903
|
+
generated from the live configuration: which registers exist, which files need a lease,
|
|
2904
|
+
which gates run, what is written where, what is never deleted, and the order to do it in.
|
|
2899
2905
|
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2906
|
+
This file deliberately does **not** restate that cycle. It is seeded once and never
|
|
2907
|
+
overwritten, so a copy of the protocol here would be frozen on the day the project was
|
|
2908
|
+
created while the tool moved on — and the two would disagree in front of an agent with no
|
|
2909
|
+
way to tell which is current. One fact, one home; the home is the generated snapshot,
|
|
2910
|
+
because it can be regenerated and `agent-sync check` fails when it goes stale.
|
|
2905
2911
|
|
|
2906
|
-
##
|
|
2912
|
+
## The three that are true in every project
|
|
2907
2913
|
|
|
2908
|
-
1.
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
**No decision lives only in chat.** Record it in the decision register, propagate it to
|
|
2916
|
-
every document it affects, and commit referencing the id.
|
|
2914
|
+
1. **Several agents may work this repository at once.** `agent-sync status` before you
|
|
2915
|
+
start: who holds what, and what changed since you last looked.
|
|
2916
|
+
2. **Work on a branch, and land it with `agent-sync merge`.** The integration branch is
|
|
2917
|
+
somebody else's stable base — nothing about work in flight is committed there.
|
|
2918
|
+
3. **No decision lives only in chat.** Record it in the decision register, propagate it to
|
|
2919
|
+
every document it affects, and commit referencing the id.
|
|
2917
2920
|
"""
|
|
2918
2921
|
|
|
2919
2922
|
|
|
@@ -3119,8 +3122,21 @@ def check_setup(root: Path) -> tuple[list[str], list[str], list[str]]:
|
|
|
3119
3122
|
for k in sorted(set(cfg) - CONFIG_KEYS):
|
|
3120
3123
|
problems.append(f"config key '{k}' is not in the schema — it will be ignored")
|
|
3121
3124
|
|
|
3122
|
-
# Registers must exist
|
|
3125
|
+
# Registers must exist, their allocation pattern must match — and the backend must be
|
|
3126
|
+
# able to hand an id out at all. A register declared on a plane whose `reserve` always
|
|
3127
|
+
# raises is a rule that protects nothing, which is the exact thing `check` promises to
|
|
3128
|
+
# refuse: the generated snapshot then instructs every agent to run `reserve <REG>`, and
|
|
3129
|
+
# the command cannot succeed in that project.
|
|
3123
3130
|
regs = cfg.get("idRegisters") or {}
|
|
3131
|
+
if regs:
|
|
3132
|
+
backend = os.environ.get("AGENT_SYNC_BACKEND") or cfg.get("backend") or "fs"
|
|
3133
|
+
probe = make_adapter(cfg, root)
|
|
3134
|
+
if not probe.is_lease_authority:
|
|
3135
|
+
problems.append(
|
|
3136
|
+
f"{len(regs)} id register(s) declared, but backend '{probe.name}' cannot "
|
|
3137
|
+
f"reserve ids (atomicAppend is false){' — the configured backend is ' + backend + ' and it is not reachable, so runs degrade to fs' if backend != probe.name else ''}. "
|
|
3138
|
+
"`reserve` fails every time here; either configure a backend that can "
|
|
3139
|
+
"allocate, or remove the registers and allocate them in the parent repository")
|
|
3124
3140
|
for reg, spec in sorted(regs.items()):
|
|
3125
3141
|
f = root / spec.get("file", "")
|
|
3126
3142
|
if not f.exists():
|