@ssheleg/agent-sync 1.4.3 → 1.7.0
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 +335 -0
- package/README.md +26 -15
- package/package.json +1 -1
- package/plugins/agent-sync/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-sync/hooks/guard.sh +63 -17
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +20 -25
- package/plugins/agent-sync/skills/agent-sync/references/branching.md +17 -4
- package/plugins/agent-sync/skills/agent-sync/references/earned-rules.md +9 -0
- package/plugins/agent-sync/skills/agent-sync/references/hooks.md +4 -4
- package/plugins/agent-sync/skills/agent-sync/references/lease-protocol.md +53 -16
- package/plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md +22 -2
- 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 +565 -152
|
@@ -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
|
+
version: "1.7.0"
|
|
8
8
|
author: ssheleg
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -43,8 +43,8 @@ coordination plane, where `status` shows it to every agent without anyone fetchi
|
|
|
43
43
|
branch. Committed to a branch, a claim is invisible until the merge and turns the shared
|
|
44
44
|
roadmap into a file two branches both edit. Land work with `merge`: conflicts computed by
|
|
45
45
|
`git merge-tree` **before anything is touched**, named and refused if any, the merge
|
|
46
|
-
recorded in `docs/MERGES.md` (recent days in full, older compacted on write), the
|
|
47
|
-
released. `merges` tells the next agent what landed while it was away. **Read
|
|
46
|
+
recorded in `docs/MERGES.md` (recent days in full, older compacted on write), the `--key`
|
|
47
|
+
lease released. `merges` tells the next agent what landed while it was away. **Read
|
|
48
48
|
`references/branching.md`** before merging.
|
|
49
49
|
|
|
50
50
|
**3. Hooks exist only in Claude Code.** Elsewhere nothing blocks a guarded edit: run
|
|
@@ -97,10 +97,8 @@ python3 "$SKILL_DIR/scripts/agent_sync.py" adopt
|
|
|
97
97
|
Confirm the registers and guarded files with the operator first: a register pointed at the
|
|
98
98
|
wrong file makes every later check confidently wrong, and a guarded list that misses a
|
|
99
99
|
shared file leaves the one place collisions happen unprotected. In a submodule it declares
|
|
100
|
-
no registers: decisions belong to the parent repository.
|
|
101
|
-
|
|
102
|
-
Then: `init` → paste the approved config → `reconcile --set-baseline` → `setup` →
|
|
103
|
-
commit the snapshot and link it from the project's agent instructions.
|
|
100
|
+
no registers: decisions belong to the parent repository. Then take the chain above from
|
|
101
|
+
`init`.
|
|
104
102
|
|
|
105
103
|
## First command in a project: `init`
|
|
106
104
|
|
|
@@ -147,7 +145,8 @@ python3 "$SKILL_DIR/scripts/agent_sync.py" status
|
|
|
147
145
|
```
|
|
148
146
|
|
|
149
147
|
Idempotent. Inspects, repairs what is missing, prints a status block, names exactly
|
|
150
|
-
ONE next action
|
|
148
|
+
ONE next action — and carries `check`'s verdict, so the command every session runs and
|
|
149
|
+
the command that validates the setup cannot give two answers about one project.
|
|
151
150
|
|
|
152
151
|
**Read the two awareness sections it prints — they are the point, not decoration.**
|
|
153
152
|
|
|
@@ -191,7 +190,7 @@ npx sshlg-skills install
|
|
|
191
190
|
| `whoami` | Print this run's id and its held leases |
|
|
192
191
|
| `setup` | Write the generated snapshot of how **this** project is wired, for agents to read |
|
|
193
192
|
| `adopt` | Inspect an existing project and **propose** a config — writes nothing |
|
|
194
|
-
| `merge` | Land this branch: conflicts checked **before** anything is touched, merge log written, lease released. `--
|
|
193
|
+
| `merge` | Land this branch: local target fast-forwarded, conflicts checked **before** anything is touched, merge log written, the `--key` lease released. `--summary`, `--dry-run`, `--push` |
|
|
195
194
|
| `merges` | What landed while you were on your branch. `--all` includes the compacted tail |
|
|
196
195
|
| `check` | Validate the whole setup end to end. Non-zero when it is not healthy |
|
|
197
196
|
| `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 |
|
|
@@ -214,10 +213,8 @@ shared.
|
|
|
214
213
|
|
|
215
214
|
The third matters because a plain shell command has no session id and a hook does. So
|
|
216
215
|
`SessionStart` stamps `.agent-sync/sessions/<CLI pid>` with the session it knows, and a later
|
|
217
|
-
command finds itself by walking its own process ancestry to a stamped pid
|
|
218
|
-
|
|
219
|
-
claude paths in its argv, so every heuristic aimed at the binary matched it instead. Stamps
|
|
220
|
-
are removed when their process is gone.
|
|
216
|
+
command finds itself by walking its own process ancestry to a stamped pid. Why that and not
|
|
217
|
+
command-line parsing: `references/earned-rules.md`.
|
|
221
218
|
|
|
222
219
|
When none of the four can be established the run says so — *"this identity is shared with any other
|
|
223
220
|
session in this checkout"* — rather than presenting a shared entry as separation.
|
|
@@ -246,8 +243,11 @@ The config lists registry files several agents write. Before editing one:
|
|
|
246
243
|
python3 "$SKILL_DIR/scripts/agent_sync.py" guard docs/DECISIONS.md
|
|
247
244
|
```
|
|
248
245
|
|
|
249
|
-
Exit 2
|
|
250
|
-
|
|
246
|
+
**Exit 2 is about *this run*: it holds no lease** — not that somebody else holds that
|
|
247
|
+
file. One lease covers every guarded file; hold one or write none. A denial names the
|
|
248
|
+
other run **and its key**, because "r-x holds a lease" beside a path gets repeated as
|
|
249
|
+
"r-x holds this file". Do not edit anyway, and do not "just fix one line" — a clobbered
|
|
250
|
+
decision looks exactly like a decision.
|
|
251
251
|
|
|
252
252
|
Claude Code's `PreToolUse` hook runs this for you. Elsewhere nothing does.
|
|
253
253
|
|
|
@@ -260,9 +260,9 @@ both use it.
|
|
|
260
260
|
python3 "$SKILL_DIR/scripts/agent_sync.py" reserve DEC # → DEC-0216
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
-
Allocation is positional over the log
|
|
264
|
-
and not
|
|
265
|
-
|
|
263
|
+
Allocation is positional over the **merged** log — every shard, never just this run's —
|
|
264
|
+
so every agent computes the same answer. Reserved and not written to git? `release-id`
|
|
265
|
+
it, or the number is a hole the board reports as a leak.
|
|
266
266
|
|
|
267
267
|
## Nothing in a log is ever edited or deleted
|
|
268
268
|
|
|
@@ -304,7 +304,7 @@ which side a document belongs on.
|
|
|
304
304
|
This skill supplies stages; it does not define them. Stage names are
|
|
305
305
|
`task-pipeline`'s own.
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
Five of the eleven stages carry a rule the others do not, and each is about ordering:
|
|
308
308
|
**0** `acquire` before the brief is committed; **1** `reconcile` and resolve every
|
|
309
309
|
divergence before writing code; **3** `reserve` every id before it reaches git; **9**
|
|
310
310
|
`record`, `signal`, `reconcile`, `board` — the main write point. **10** ends the run:
|
|
@@ -368,12 +368,6 @@ The mirror is a **rendering** of git, stamped with the source commit. It has no
|
|
|
368
368
|
authority. When its stamp and `HEAD` disagree, the board gate fails — that is
|
|
369
369
|
drift, not a formatting problem.
|
|
370
370
|
|
|
371
|
-
## Two rules, and the failures that taught them
|
|
372
|
-
|
|
373
|
-
Identity comes before coordination: a lease is only a lease if two agents get two identities. A
|
|
374
|
-
submodule commit is unfinished until its parent points at it. Both:
|
|
375
|
-
[`references/earned-rules.md`](references/earned-rules.md).
|
|
376
|
-
|
|
377
371
|
## Non-negotiables
|
|
378
372
|
|
|
379
373
|
- Append, read back, then act. Never rewrite a coordination document.
|
|
@@ -399,6 +393,7 @@ Each file is loaded on its own trigger, not by default.
|
|
|
399
393
|
| `references/two-sources.md` | before the first reconcile, or when deciding where a document belongs |
|
|
400
394
|
| `references/roadmap.md` | configuring `claimTags`, taking or closing a task, or re-planning a board |
|
|
401
395
|
| `references/branching.md` | starting work that will produce commits, merging a branch, or asking what landed while you were away |
|
|
396
|
+
| `references/earned-rules.md` | asking why identity resolves the way it does, or why `finish` exists |
|
|
402
397
|
|
|
403
398
|
If this copy arrived without `references/`, fetch them from
|
|
404
399
|
`https://raw.githubusercontent.com/ssheleg/agent-sync/main/plugins/agent-sync/skills/agent-sync/references/<file>`.
|
|
@@ -45,8 +45,15 @@ In order, and every check before anything is touched:
|
|
|
45
45
|
|
|
46
46
|
1. **Refuses a detached HEAD, the integration branch itself, and a dirty tree.** A merge
|
|
47
47
|
cannot tell uncommitted work from the branch's own commits.
|
|
48
|
-
2. **Fetches the integration branch
|
|
49
|
-
|
|
48
|
+
2. **Fetches the integration branch and fast-forwards the local one to it**, so the
|
|
49
|
+
preflight and the merge share a base, then reports how far it moved since the branch
|
|
50
|
+
started. Until 1.6.0 they did not share one: conflicts and the diff were measured
|
|
51
|
+
against `origin/<target>` while the merge went into a local `<target>` nothing
|
|
52
|
+
advanced — so `merge` printed the staleness it had just measured, printed `✓ merged`,
|
|
53
|
+
wrote the log entry and released the lease, and the push was rejected. The work had not
|
|
54
|
+
landed, the log said it had, and the task was free for somebody else. A local branch
|
|
55
|
+
that has *diverged* (commits on both sides) cannot be fast-forwarded and is refused
|
|
56
|
+
with both counts.
|
|
50
57
|
3. **Computes conflicts with `git merge-tree`**, in memory. A merge that starts and then
|
|
51
58
|
aborts leaves the operator in a repository they did not ask for. On conflict it names
|
|
52
59
|
the files, changes nothing, and exits non-zero: resolve in your own branch, where the
|
|
@@ -55,8 +62,14 @@ In order, and every check before anything is touched:
|
|
|
55
62
|
the same files, they merge into what is about to land.
|
|
56
63
|
5. Merges `--no-ff`, so the branch stays visible in history.
|
|
57
64
|
6. **Writes the merge log** and commits it.
|
|
58
|
-
7. **Releases
|
|
59
|
-
|
|
65
|
+
7. **Releases the lease named by `--key`.** Only that one: releasing every lease the run
|
|
66
|
+
holds is a different statement from the one this step makes, and it quietly frees work
|
|
67
|
+
that has not landed. Without `--key` there is nothing to name, so it releases what the
|
|
68
|
+
run holds and says so. A run that ends holding a lease blocks the next agent for the
|
|
69
|
+
whole TTL — so release, but release what you landed.
|
|
70
|
+
|
|
71
|
+
The remote is `origin`, the conventional home of the integration branch. `leaseRemote` is a
|
|
72
|
+
different setting for a different job: where the lease refs live.
|
|
60
73
|
|
|
61
74
|
`--dry-run` stops after step 4. `--push` pushes the integration branch afterwards;
|
|
62
75
|
without it, `finish` is the next call — it checks every repository, not just this one.
|
|
@@ -13,6 +13,15 @@ belonged to somebody else, and `release` would have taken it. The resolution ord
|
|
|
13
13
|
`AGENT_SYNC_RUN_ID` · `CLAUDE_SESSION_ID` · the session that started this shell · shared, and where
|
|
14
14
|
none can be established the run says so instead of presenting a shared entry as separation.
|
|
15
15
|
|
|
16
|
+
The third position is the one that needs explaining, because the obvious implementation of it is
|
|
17
|
+
wrong. A plain shell command carries no session id; a hook does. So `SessionStart` stamps
|
|
18
|
+
`.agent-sync/sessions/<CLI pid>` with the session it knows, and any later command finds itself by
|
|
19
|
+
walking its own **process ancestry** to a stamped pid, removing stamps whose process is gone. It is
|
|
20
|
+
deliberately not command-line parsing: the throwaway shell every tool call runs in carries claude
|
|
21
|
+
paths in its own `argv`, so every heuristic aimed at spotting the CLI binary matched that shell
|
|
22
|
+
instead — and matching the wrong ancestor hands two sessions one identity, which is the failure
|
|
23
|
+
above.
|
|
24
|
+
|
|
16
25
|
**Work in a submodule is not finished until its parent says so.** A parent records each submodule as
|
|
17
26
|
a pointer to one commit, and moving the submodule does not move the pointer. The work is committed,
|
|
18
27
|
pushed, green in CI and marked done in its own roadmap — and a clone of the parent gets the commit
|
|
@@ -46,7 +46,7 @@ The hook receives JSON on stdin with `session_id`, `prompt_id`, `transcript_path
|
|
|
46
46
|
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" }] }
|
|
47
47
|
],
|
|
48
48
|
"PreToolUse": [
|
|
49
|
-
{ "matcher": "Edit|Write|MultiEdit",
|
|
49
|
+
{ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
|
|
50
50
|
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/guard.sh" }] },
|
|
51
51
|
{ "matcher": "Bash", "if": "Bash(git commit *)",
|
|
52
52
|
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/guard.sh" }] }
|
|
@@ -66,8 +66,8 @@ The hook receives JSON on stdin with `session_id`, `prompt_id`, `transcript_path
|
|
|
66
66
|
|---|---|
|
|
67
67
|
| `session-start.sh` | Register the run, print the board summary and the one next action |
|
|
68
68
|
| `guard.sh` | Deny an edit to a `guardedFiles[]` path, or a commit staging one, without a live lease |
|
|
69
|
-
| `renew.sh` | Renew the lease, throttled to `renewIntervalSeconds
|
|
70
|
-
| `session-end.sh` | Release every lease
|
|
69
|
+
| `renew.sh` | Renew the lease — moves the timestamp expiry is computed from, throttled to `renewIntervalSeconds`, a no-op most calls |
|
|
70
|
+
| `session-end.sh` | Release every lease this run holds. That is all it does — it writes no journal entry and closes nothing else |
|
|
71
71
|
|
|
72
72
|
## Performance
|
|
73
73
|
|
|
@@ -82,7 +82,7 @@ throttle is broken — fix the throttle rather than removing the hook.
|
|
|
82
82
|
|---|---|
|
|
83
83
|
| Guarded edits go through | The guard crashed. Any exit code other than 2 is non-blocking. Run it by hand with a sample stdin payload |
|
|
84
84
|
| Everything is denied | No config, or no lease. `status` says which |
|
|
85
|
-
| Session start is slow | The backend is unreachable
|
|
85
|
+
| Session start is slow | The backend is unreachable. Each hook is capped twice — `run_limited 10` inside the script, and the `timeout` in `hooks.json` (15–20 s) — so it degrades rather than hanging |
|
|
86
86
|
| Renew floods the log | The throttle file is not being written — check its path is writable |
|
|
87
87
|
|
|
88
88
|
Run the guard directly to see what it decides:
|
|
@@ -42,20 +42,37 @@ tests for the exact bullet you emitted skips malformed lines *before* they can b
|
|
|
42
42
|
counted, so the ratio reads 0% while nothing parses — the guard and the counter both
|
|
43
43
|
go quiet at once. This is not hypothetical; it is how the bug above stayed invisible.
|
|
44
44
|
|
|
45
|
-
**An unreadable log is not a lost race.**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
**An unreadable log is not a lost race.** Past 2% unparseable, **every command that
|
|
46
|
+
replays a log refuses it** — the read itself fails, so `status`, `board`, `check`,
|
|
47
|
+
`reconcile` and `reserve` stop and name the ratio instead of acting on a partial
|
|
48
|
+
history. Replaying it would report holders who do not exist and silence where the real
|
|
49
|
+
ones are, and both of those look exactly like an answer.
|
|
50
|
+
|
|
51
|
+
`acquire` is not in that list, and the reason is the design: the lease is decided by the
|
|
52
|
+
lock file or the git ref, never by the log, so a corrupt log cannot make a lease look
|
|
53
|
+
lost. Until 1.6.0 the threshold was a constant nothing read — declared, quoted in three
|
|
54
|
+
documents, and implemented nowhere except a warning line on the board that returned zero.
|
|
49
55
|
|
|
50
56
|
## Acquiring — the third design, and the first that is true
|
|
51
57
|
|
|
52
58
|
```
|
|
53
|
-
1.
|
|
54
|
-
2.
|
|
55
|
-
3.
|
|
56
|
-
4.
|
|
59
|
+
1. free os.open(lock, O_CREAT | O_EXCL) — this is the decision, and it is atomic
|
|
60
|
+
2. held live holder -> report it; this run -> already ours
|
|
61
|
+
3. expired take the steal section, re-read expiry inside it, then reap and create
|
|
62
|
+
4. lost FileExistsError, or the section is held -> read the holder and report it
|
|
63
|
+
5. won write {run, ts, ttl, repo}; publish op=acquire to the plane for visibility
|
|
57
64
|
```
|
|
58
65
|
|
|
66
|
+
**Step 3 is one critical section, not two calls.** `unlink` followed by `O_EXCL create`
|
|
67
|
+
leaves a gap, and a second stealer that has already read the lock as expired removes the
|
|
68
|
+
lock the first one just created — both then hold what each believes is exclusive. Twelve
|
|
69
|
+
racing processes never showed it; a 300 ms delay injected between the two calls produced
|
|
70
|
+
two winners out of two, and in production that delay is an ordinary scheduler hiccup. So
|
|
71
|
+
`<K>.lock.steal` is created with `O_EXCL`, the expiry is re-read **inside** it (the holder
|
|
72
|
+
may have renewed, or another stealer may have finished), and it carries its own 30-second
|
|
73
|
+
abandonment grace — without one, a crash between two filesystem calls would cost the key
|
|
74
|
+
until somebody deleted a file nobody documents.
|
|
75
|
+
|
|
59
76
|
**Publishing is not the decision.** A failure to reach the knowledge base costs
|
|
60
77
|
visibility, never correctness: the lock is already held. So the append is wrapped and
|
|
61
78
|
its failure reported, not raised.
|
|
@@ -86,16 +103,29 @@ The tool reports which guarantee is in force; it never implies the stronger one.
|
|
|
86
103
|
|
|
87
104
|
## Expiry and stealing
|
|
88
105
|
|
|
89
|
-
A lock is expired when `now > ts + ttl` for the timestamp inside it
|
|
90
|
-
|
|
106
|
+
A lock is expired when `now > ts + ttl` for the timestamp inside it.
|
|
107
|
+
|
|
108
|
+
**`renew` moves that timestamp, in the plane that arbitrates the lease** — it rewrites the
|
|
109
|
+
lock file in `local` mode, and re-pushes the ref with `--force-with-lease` against the
|
|
110
|
+
exact object it read in `git` mode. The `op=renew` line it also appends to the record plane
|
|
111
|
+
is visibility, not renewal.
|
|
112
|
+
|
|
113
|
+
That distinction is the whole of the bug fixed in 1.5.3: `renew` wrote *only* the record
|
|
114
|
+
line. The lock's `ts` was written once, by `acquire`, so a run holding a lease lost it at
|
|
115
|
+
TTL while still working — its own guard began denying it, and another run acquired the task
|
|
116
|
+
it was in the middle of. Nothing reported it, because from the record plane's side the
|
|
117
|
+
renewals were arriving exactly as scheduled. **A renewal that does not move the timestamp
|
|
118
|
+
the expiry is computed from is not a renewal**, however faithfully it is logged.
|
|
91
119
|
|
|
92
120
|
Default `ttl` is 2700 s (45 minutes). `renew` is emitted at most once per
|
|
93
121
|
`renewIntervalSeconds` (default 300 s) — by the `PostToolUse` hook in Claude Code,
|
|
94
|
-
and by the agent itself everywhere else.
|
|
122
|
+
and by the agent itself everywhere else. A `renew` for a key this run does not hold
|
|
123
|
+
refreshes nothing and says so.
|
|
95
124
|
|
|
96
|
-
**Stealing an expired lease is the ordinary `acquire` path.** There is no force flag:
|
|
97
|
-
|
|
98
|
-
the plane with both run ids, so an operator can see that it happened
|
|
125
|
+
**Stealing an expired lease is the ordinary `acquire` path.** There is no force flag: the
|
|
126
|
+
steal section above removes an expired lock and creates the new one, as one operation. The
|
|
127
|
+
steal is visible on the plane with both run ids, so an operator can see that it happened
|
|
128
|
+
and when.
|
|
99
129
|
|
|
100
130
|
## Releasing
|
|
101
131
|
|
|
@@ -120,7 +150,14 @@ Then, replaying in order and maintaining a free list:
|
|
|
120
150
|
- `op=reserve key=DEC` takes the free-list head if it is non-empty; otherwise it
|
|
121
151
|
takes `base + (count of prior reserves not served from the free list)`.
|
|
122
152
|
|
|
123
|
-
Every reader computes the same assignment for every reserve line, including its own
|
|
153
|
+
Every reader computes the same assignment for every reserve line, including its own —
|
|
154
|
+
**and "the log" means every shard merged, never the one this run writes.** Reading only
|
|
155
|
+
its own document is how `reserve` handed three runs `DEC-0007` three times (fixed in
|
|
156
|
+
1.5.3): each replayed a log containing only its own lines, each seeded its own `base`
|
|
157
|
+
from the register, and each was correct about a history nobody else shared. The failure
|
|
158
|
+
is the same one that disqualified per-writer documents as a *lease* store, arriving in
|
|
159
|
+
the allocator — so a `base` now only ever moves allocation **forward**, and two runs
|
|
160
|
+
opening a register in the same minute cannot restart each other's count.
|
|
124
161
|
|
|
125
162
|
**An id you reserved and did not write to git must be released** with
|
|
126
163
|
`release_id`. An id that is reserved, unreleased and absent from git after its run
|
|
@@ -133,7 +170,7 @@ number, and silently handing it out again would produce two documents with one i
|
|
|
133
170
|
| Fact | Home | Lifetime |
|
|
134
171
|
|---|---|---|
|
|
135
172
|
| Who holds this task **right now** | the lease log | ephemeral, TTL |
|
|
136
|
-
| Who **owns** this task | the git claim tag
|
|
173
|
+
| Who **owns** this task | the git claim tag — `todo (claimed: r-7f3a91)`, the template from `claimTags.held` | durable |
|
|
137
174
|
|
|
138
175
|
`acquire` writes the git tag through and `release` restores exactly what was there. The
|
|
139
176
|
objection that once demoted this to a check — an unattended process rewriting a shared
|
|
@@ -5,12 +5,29 @@
|
|
|
5
5
|
`agent-sync` supplies stages; it does not define them. The stage names below are
|
|
6
6
|
`task-pipeline`'s own — do not rename, renumber or fork them.
|
|
7
7
|
|
|
8
|
+
## The numbers, once
|
|
9
|
+
|
|
10
|
+
<!-- agent-sync:stages rules=0,1,3,9,10 wired=0,1,3,4,5,9,10 -->
|
|
11
|
+
|
|
12
|
+
Two different questions, and answering them in one list is why three documents once gave
|
|
13
|
+
three answers:
|
|
14
|
+
|
|
15
|
+
- **Stages carrying a rule — 0, 1, 3, 9, 10.** Something must happen there or the run is
|
|
16
|
+
wrong, and each rule is about ordering. Quoted in `SKILL.md`.
|
|
17
|
+
- **Stages wired into `pipeline.json` — 0, 1, 3, 4, 5, 9, 10.** The list above plus the two
|
|
18
|
+
where the journal has teeth (file ownership, submodule pointers). Quoted in the README.
|
|
19
|
+
|
|
20
|
+
`SKILL.md` said "four of the eleven stages" and then listed five; the README named a third
|
|
21
|
+
set; and this file called stage 1 *"nothing shared to coordinate"* — the stage `reconcile`
|
|
22
|
+
belongs to. The marker above is the source, and the validator fails when a surface stops
|
|
23
|
+
agreeing with it.
|
|
24
|
+
|
|
8
25
|
## Where it plugs in
|
|
9
26
|
|
|
10
27
|
| Stage | Calls | Why there and not elsewhere |
|
|
11
28
|
|---|---|---|
|
|
12
29
|
| **0 Intake grill** | `status`, then `acquire <KEY>` | The cloud KB and the board join the harvest's source ledger. The lease is taken **before the brief is committed**, or two agents write two briefs for one task |
|
|
13
|
-
| **1 Docs study** |
|
|
30
|
+
| **1 Docs study** | `reconcile`, then resolve every divergence | The git documents say how it *should* be, the as-built record how it *is*. Building on an unresolved divergence is writing code against a system that does not exist — and this is the last stage where that costs nothing |
|
|
14
31
|
| **2 Brainstorm + decompose** | `journal` | Also warns when a live run holds an overlapping key — cheapest moment to find the overlap |
|
|
15
32
|
| **3 Spec** | `reserve <REG>` per id | Ids must be reserved *before* they are written to git. Reading "next free id" is not reserving it |
|
|
16
33
|
| **4 Plan** | `journal` with the plan's file ownership | Parallel groups that write one file are a merge conflict scheduled for later |
|
|
@@ -33,6 +50,9 @@ clause to each of those stages' existing `gate.check`:
|
|
|
33
50
|
{ "id": 0, "state": "intake", "name": "Intake grill",
|
|
34
51
|
"skills": ["task-pipeline:grill", "agent-sync"],
|
|
35
52
|
"gate": { "type": "manual", "check": "<the stage's own criteria> AND the lease for this task is held before the brief is committed" } },
|
|
53
|
+
{ "id": 1, "state": "docs-study", "name": "Docs study",
|
|
54
|
+
"skills": ["task-pipeline:knowledge", "agent-sync"],
|
|
55
|
+
"gate": { "type": "manual", "check": "<the stage's own criteria> AND `reconcile` ran and every divergence it named is resolved or recorded as standing" } },
|
|
36
56
|
{ "id": 3, "state": "spec", "name": "Spec",
|
|
37
57
|
"skills": ["task-pipeline:spec", "agent-sync"],
|
|
38
58
|
"gate": { "type": "manual", "check": "<the stage's own criteria> AND every id the spec writes was reserved first" } },
|
|
@@ -65,7 +85,7 @@ that copied it silently dropped the stage's real gate: stage 9 lost the propagat
|
|
|
65
85
|
sweep and the documentation gate, stage 10 lost the ladder walk and the evidence
|
|
66
86
|
rule.
|
|
67
87
|
|
|
68
|
-
Stages
|
|
88
|
+
Stages 2, 6, 7 and 8 keep their own `skills[]`; `agent-sync` only journals there,
|
|
69
89
|
which needs no wiring.
|
|
70
90
|
|
|
71
91
|
## What must be guarded
|