@ssheleg/agent-sync 1.2.2

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/README.md ADDED
@@ -0,0 +1,389 @@
1
+ # agent-sync
2
+
3
+ [![CI](https://github.com/appvillis-com/agent-sync/actions/workflows/validate.yml/badge.svg)](https://github.com/appvillis-com/agent-sync/actions/workflows/validate.yml)
4
+ [![npm](https://img.shields.io/npm/v/%40ssheleg%2Fagent-sync)](https://www.npmjs.com/package/@ssheleg/agent-sync)
5
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+
7
+ **Several coding agents, one repository, no collisions — and each one can see what the
8
+ others are doing.**
9
+
10
+ `agent-sync` is an agent skill (plus a Claude Code plugin) that gives concurrent
11
+ coding agents a coordination plane: leases with a TTL, race-free id reservation, a run
12
+ journal, a cross-repo signal feed and a generated board.
13
+
14
+ - [The problem](#the-problem)
15
+ - [What you get](#what-you-get)
16
+ - [Requirements](#requirements)
17
+ - [Install](#install)
18
+ - [Update](#update)
19
+ - [Set up a project](#set-up-a-project)
20
+ - [Everyday use](#everyday-use)
21
+ - [Configuration](#configuration)
22
+ - [Backends](#backends)
23
+ - [Enforcement hooks](#enforcement-hooks)
24
+ - [Where it plugs into task-pipeline](#where-it-plugs-into-task-pipeline)
25
+ - [Limits, stated plainly](#limits-stated-plainly)
26
+ - [Troubleshooting](#troubleshooting)
27
+ - [Uninstall](#uninstall)
28
+ - [Develop and verify](#develop-and-verify)
29
+
30
+ ## The problem
31
+
32
+ When more than one agent works a project at the same time, the coordination substrate
33
+ most teams already have — a decisions log, a roadmap, a board, per-repo task files —
34
+ stops being enough. Every one of those is a file edited by hand. That works for people
35
+ taking turns and fails for agents working at once:
36
+
37
+ | What goes wrong | Why |
38
+ |---|---|
39
+ | Two agents mint the same decision id | "Next free id" is a line in a file; reading it is not reserving it |
40
+ | A claim blocks a task forever | A role name is not a holder, and it has no expiry |
41
+ | Two agents start the same task | Git shows what was committed, never what is in flight |
42
+ | An agent is blocked but not informed | Knowing a task is taken is not knowing who has it or what they touch |
43
+ | Merge conflicts on every shared register | Everyone writes the same three files |
44
+ | A cross-repo dependency is never noticed | Filing one notifies nobody |
45
+
46
+ `agent-sync` closes exactly those, and nothing else.
47
+
48
+ ## The idea
49
+
50
+ > **Git is the record plane. The cloud is the coordination plane.**
51
+
52
+ A fact that must survive is written to git first and referenced from the cloud. A fact
53
+ about *who is doing what right now* lives in the cloud and expires. No cloud object is
54
+ ever the only home of a durable fact, so your single-source-of-truth rules stay intact.
55
+
56
+ **The knowledge base never decides a lease.** It cannot: measured against a real
57
+ instance, twelve concurrent appends to one document returned twelve successes and left
58
+ three lines. Exclusion comes from something that genuinely has compare-and-swap — an
59
+ atomic file create on one machine (`leaseBackend: "local"`), or a pushed git ref across
60
+ machines (`leaseBackend: "git"`), where the remote's non-fast-forward rejection *is* the
61
+ CAS. Id reservation still replays the log, which is safe because allocation is positional
62
+ and every reader computes the same answer.
63
+
64
+ ## What you get
65
+
66
+ - **Leases with a TTL** — claim a task, renew automatically, steal an expired one.
67
+ Exclusive across machines with `leaseBackend: "git"`; the tool always states which
68
+ guarantee you have rather than implying the stronger one.
69
+ - **The claim written through to the roadmap** — one row, one cell, refused on ambiguity,
70
+ and restored verbatim on release. Closing a task stays yours.
71
+ - **Awareness, not just exclusion** — `status` lists every *other* run's live holdings,
72
+ so an agent learns who holds a task and what they are touching, instead of only that
73
+ it is taken.
74
+ - **Race-free id reservation** — positional allocation over the log, so two agents
75
+ cannot be handed one number.
76
+ - **A run journal** — what each run did, with commits, gate results and evidence.
77
+ - **A cross-repo signal feed** — `filed → accepted → delivered → closed`. `status`
78
+ surfaces what landed since this run last looked, watermarked per run so it stays quiet
79
+ until something actually changes.
80
+ - **A generated board** — machine-written, commit-stamped, and it refuses to overwrite a
81
+ page a human took over.
82
+ - **Enforcement hooks** for Claude Code that deny an edit to a guarded register file, or
83
+ a commit staging one, without a live lease.
84
+
85
+ ## Requirements
86
+
87
+ | Requirement | Why | Check |
88
+ |---|---|---|
89
+ | **python3 ≥ 3.9** | the coordinator is one stdlib-only script — HTTP included, nothing to `pip install` | `python3 --version` |
90
+ | **git** | the record plane, and the cross-machine lease store | `git --version` |
91
+ | **bash** | the four Claude Code hook scripts | `bash --version` |
92
+ | **Node ≥ 18** | only for the `npx @ssheleg/agent-sync` installer | `node --version` |
93
+ | **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | `agent-sync` supplies stages, it does not define them — without it `status` prints one line and stops | `npx sshlg-skills install` |
94
+ | A knowledge-base instance *(optional)* | the shared record, awareness and board; without one the `fs` backend keeps leases but loses cross-agent visibility | — |
95
+
96
+ ## Install
97
+
98
+ ```bash
99
+ npx @ssheleg/agent-sync install
100
+ ```
101
+
102
+ Claude Code gets the plugin; every other agent gets the skill through the
103
+ [skills CLI](https://github.com/vercel-labs/skills). The duplicate plain copy in
104
+ `~/.claude/skills/` is pruned afterwards, because that shadow silently serves a stale
105
+ skill over the installed plugin — **one channel per agent** is the rule.
106
+
107
+ Restart Claude Code after installing, so it picks the plugin up.
108
+
109
+ <details>
110
+ <summary>Other install routes</summary>
111
+
112
+ Track `main` from GitHub instead of the npm release:
113
+
114
+ ```bash
115
+ npx github:appvillis-com/agent-sync install
116
+ ```
117
+
118
+ Claude Code only, no skills CLI:
119
+
120
+ ```bash
121
+ npx @ssheleg/agent-sync install --claude-only
122
+ ```
123
+
124
+ Pick which agents the skills CLI installs for:
125
+
126
+ ```bash
127
+ npx @ssheleg/agent-sync install --agent cursor,codex
128
+ ```
129
+
130
+ Or add the plugin by hand — the full `<name>@<name>` form is required:
131
+
132
+ ```bash
133
+ claude plugin marketplace add appvillis-com/agent-sync && claude plugin install agent-sync@agent-sync
134
+ ```
135
+
136
+ </details>
137
+
138
+ ## Update
139
+
140
+ **agent-sync itself** — update every channel you installed, then restart Claude Code:
141
+
142
+ ```bash
143
+ claude plugin marketplace update agent-sync && claude plugin update agent-sync@agent-sync && npx --yes skills update agent-sync --global --yes
144
+ ```
145
+
146
+ Re-running the installer works too, but pin `@latest` or npx may serve you its cache:
147
+
148
+ ```bash
149
+ npx @ssheleg/agent-sync@latest install
150
+ ```
151
+
152
+ Check what you are actually running — the plugin and the skill must report the same
153
+ version, and a mismatch means one channel is stale:
154
+
155
+ ```bash
156
+ claude plugin list | grep agent-sync
157
+ python3 ~/.claude/plugins/cache/*/agent-sync/*/skills/agent-sync/scripts/agent_sync.py --version
158
+ ```
159
+
160
+ **Its dependencies** — `task-pipeline` (and the rest of the same family) come from one
161
+ installer, which also prunes the shadow copies:
162
+
163
+ ```bash
164
+ npx sshlg-skills install
165
+ ```
166
+
167
+ Nothing else to update: the coordinator is stdlib-only python, and the npm package has
168
+ zero runtime dependencies.
169
+
170
+ ## Set up a project
171
+
172
+ **Initialisation is the first command, and it asks a question rather than guessing.**
173
+
174
+ ```
175
+ /agent-sync init
176
+ ```
177
+
178
+ The agent asks where coordination state should live — a knowledge cloud, or local files
179
+ — and, for the cloud, the instance URL. Then it writes two files:
180
+
181
+ | File | Holds | Committed? |
182
+ |---|---|---|
183
+ | `.claude/agent-sync.json` | **shape** — backend, TTLs, guarded files, registers, gates | yes |
184
+ | `.env.agent-sync` | **identity** — instance URL, token, collection id | **no** — mode 600, added to `.gitignore` |
185
+
186
+ The token line is written **empty**. Creating the API token in your own instance and
187
+ pasting it into that line is your step, and it stays yours: the tool never asks for a
188
+ token in chat, never echoes one, and never passes one as a command-line argument.
189
+
190
+ Load the environment before running agents:
191
+
192
+ ```bash
193
+ set -a && . ./.env.agent-sync && set +a
194
+ ```
195
+
196
+ Then create the container the coordination log lives in, once per project, and paste the
197
+ id it prints into `AGENT_SYNC_OUTLINE_COLLECTION`:
198
+
199
+ ```bash
200
+ /agent-sync bootstrap
201
+ ```
202
+
203
+ Verify the setup — idempotent, repairs what is missing, and names exactly one next
204
+ action:
205
+
206
+ ```bash
207
+ /agent-sync status
208
+ ```
209
+
210
+ ## Everyday use
211
+
212
+ In an agent session you use the slash command (`/agent-sync claim ASC-072`); the same
213
+ commands run directly against the coordinator script, which is what the hooks and CI do:
214
+
215
+ ```bash
216
+ python3 "$SKILL_DIR/scripts/agent_sync.py" <command>
217
+ ```
218
+
219
+ | Command | Does |
220
+ |---|---|
221
+ | `init` | **Run first.** Ask where state lives, write config + gitignored env file, print your step |
222
+ | `status` | Inspect, repair, report — including other runs' leases and signals new since you last looked |
223
+ | `bootstrap` | Create the cloud container and print the id to paste into the env file |
224
+ | `acquire <KEY>` | Take the lease on a task id. Prints `won`, or `lost <holder>` |
225
+ | `renew <KEY>` | Extend the lease. In Claude Code the `PostToolUse` hook does this for you |
226
+ | `release <KEY>` | Give the lease back. Always, including on failure |
227
+ | `reserve <REG>` | Reserve the next id in a register (`DEC`, `OQ`, `DEP`, …). Prints the id |
228
+ | `release-id <REG> <ID>` | Return an id you did not end up writing to git |
229
+ | `journal <text>` | Append one line to this run's journal |
230
+ | `signal <DEP-ID> <state>` | Move a cross-repo dependency: `filed`/`accepted`/`delivered`/`closed`/`refused` |
231
+ | `guard <path>` | May this run write that path? Exit 0 = yes, 2 = no |
232
+ | `board` | Regenerate the read-only board and the mirror from git |
233
+ | `whoami` | Print this run's id and its held leases |
234
+
235
+ The shape that matters:
236
+
237
+ ```
238
+ acquire → do the work → release
239
+ ```
240
+
241
+ Never skip `release`, including when the work failed. An abandoned lease blocks the task
242
+ until its TTL expires, and the next agent cannot tell "in progress" from "crashed an hour
243
+ ago". A lease is a promise to come back.
244
+
245
+ **The lease is not the claim.** The lease says who holds the task *now* and expires; the
246
+ durable claim is the tag in git (`[name]`, `todo (claimed: <role>)`). `acquire` writes
247
+ that tag through and `release` clears it, so one fact keeps one home.
248
+
249
+ ## Configuration
250
+
251
+ `.claude/agent-sync.json` — committed, validated against
252
+ [`agent-sync.schema.json`](agent-sync.schema.json), starting point in
253
+ [`agent-sync.example.json`](agent-sync.example.json):
254
+
255
+ | Key | Meaning |
256
+ |---|---|
257
+ | `backend` | `outline` or `fs` (required) |
258
+ | `leaseTtlSeconds` | how long a lease survives without a renew (default 2700) |
259
+ | `renewIntervalSeconds` | how often a live run renews (default 300) |
260
+ | `gated` | whether runs may be recorded as enforced at all |
261
+ | `idRegisters` | register → the git file that owns it, and its "next free id" pattern |
262
+ | `guardedFiles` | registry files no run may edit without a live lease |
263
+ | `claimTags` | file → the durable claim tag `acquire`/`release` writes through |
264
+ | `gates` | commands the pipeline stages run as gates |
265
+ | `mirror` | which git files are rendered into the read-only mirror |
266
+
267
+ `.env.agent-sync` — gitignored, mode 600:
268
+
269
+ ```
270
+ AGENT_SYNC_BACKEND=outline
271
+ AGENT_SYNC_OUTLINE_URL=https://<your-instance>
272
+ AGENT_SYNC_OUTLINE_TOKEN= # you fill this line, nobody else
273
+ AGENT_SYNC_OUTLINE_COLLECTION= # printed by `bootstrap`
274
+ ```
275
+
276
+ Never write a host name or a token into the config, a test, an example or a commit. If
277
+ an agent offers to handle a token for you, that is the wrong answer.
278
+
279
+ **A submodule's config declares only its own registers.** Cross-repository facts belong
280
+ to the parent repository; a service repo listing the parent's decision register is a
281
+ configuration defect.
282
+
283
+ ## Backends
284
+
285
+ The knowledge store is a **pluggable adapter** — six primitives, three declared
286
+ capabilities. Nothing about a specific vendor is baked in, and no instance address ships
287
+ in this repository.
288
+
289
+ | Backend | Lease authority | Notes |
290
+ |---|---|---|
291
+ | `outline` | yes | [Outline](https://www.getoutline.com), hosted or self-hosted. Server-side append gives a total order without compare-and-swap |
292
+ | `fs` | no — **degraded** | Local files. Real mutual exclusion between agents on one machine, none across machines. Every run is recorded `ungated` |
293
+
294
+ **A backend that cannot arbitrate says so.** When the adapter is not the lease authority,
295
+ `agent-sync` announces it, falls back to git-file leases, and marks runs `ungated` —
296
+ because a lease that is not actually exclusive is worse than none, and the other agent
297
+ has stopped checking.
298
+
299
+ Adding one: read
300
+ [`references/adapter-contract.md`](plugins/agent-sync/skills/agent-sync/references/adapter-contract.md).
301
+
302
+ ## Enforcement hooks
303
+
304
+ Installed with the Claude Code plugin. Every hook exits immediately in projects without
305
+ `.claude/agent-sync.json`, so installing globally changes nothing elsewhere.
306
+
307
+ | Hook | Runs | Effect |
308
+ |---|---|---|
309
+ | `SessionStart` | startup, resume | `status` — the board summary, other runs, one next action |
310
+ | `PreToolUse` | `Edit`/`Write`/`MultiEdit`/`NotebookEdit`, and `git commit` | Denies the edit (exit 2) when the path is guarded and this run holds no lease; a `git commit` is checked against every staged path |
311
+ | `PostToolUse` | every tool call | Throttled `renew` — touches the network at most once per `renewIntervalSeconds` |
312
+ | `SessionEnd` | session end | Releases every lease this run holds |
313
+
314
+ Details and removal:
315
+ [`references/hooks.md`](plugins/agent-sync/skills/agent-sync/references/hooks.md).
316
+
317
+ ## Where it plugs into task-pipeline
318
+
319
+ `agent-sync` supplies stages; it does not define them. It binds to task-pipeline's
320
+ stages 0, 3, 4, 5, 9 and 10 — lease before the brief is committed, reserve ids before
321
+ they reach git, register file ownership for parallel groups, signal and regenerate the
322
+ board at docs, release everything at acceptance. Wiring:
323
+ [`references/pipeline-binding.md`](plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md).
324
+
325
+ ## Limits, stated plainly
326
+
327
+ - **Hooks are Claude Code only.** On Cursor, Codex and the rest there is no `PreToolUse`,
328
+ so nothing blocks a guarded edit; the same checks run as a self-check and the run is
329
+ recorded `ungated`. Read the board's column rather than assuming.
330
+ - **Ordering, not clocks.** Document order decides who holds a lease; timestamps only
331
+ expire one. Agents' clocks differ and the protocol does not depend on them.
332
+ - **A reserved id that never reaches git is reported, not reclaimed.** A half-written
333
+ decision on a branch is not an unused number.
334
+ - **`fs` is not cross-machine.** It is a real mutex between agents on one host and
335
+ nothing more, which is why it never claims lease authority.
336
+
337
+ ## Troubleshooting
338
+
339
+ | Symptom | Cause and fix |
340
+ |---|---|
341
+ | `task-pipeline is not installed` and `status` stops | Intentional — there are no stages to bind to. `npx sshlg-skills install` |
342
+ | `⚠ ungated backend — this lease is advisory` | The `fs` backend, or missing credentials. Expected; configure `outline` for enforced leases |
343
+ | Every `acquire` reports `lost` | Check the holder in `status`. If the log itself is unreadable, `acquire` raises instead — that is a parse failure, not a race |
344
+ | Guarded edit blocked in Claude Code | Working as designed: `acquire` the key first, or unstage the file |
345
+ | Guarded edit *not* blocked | You are not on Claude Code. Run `guard <path>` yourself; the run is `ungated` |
346
+ | `AGENT_SYNC_OUTLINE_COLLECTION is not set` | Run `bootstrap` and paste the printed id into `.env.agent-sync` |
347
+ | 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 |
348
+ | A stale skill after updating | Two channels serving one skill. Delete `~/.claude/skills/agent-sync` and keep the plugin |
349
+ | The board refuses to write | A human took the page over (no generated marker on line 1). Reported, never overwritten |
350
+
351
+ ## Uninstall
352
+
353
+ ```bash
354
+ claude plugin uninstall agent-sync@agent-sync
355
+ npx --yes skills remove agent-sync --global --yes
356
+ ```
357
+
358
+ Project files stay where they are; delete `.claude/agent-sync.json`, `.env.agent-sync`
359
+ and `.agent-sync/` if you want the project clean too.
360
+
361
+ ## Develop and verify
362
+
363
+ ```bash
364
+ python3 test/validate.py # manifests, version sync, no host/credential leaks
365
+ python3 test/validate.py --self-test # the validator must still be able to fail
366
+ npm test # both of the above
367
+ ```
368
+
369
+ What ships: one skill (`agent-sync`), `scripts/agent_sync.py` (stdlib only), four hook
370
+ scripts, the slash command, `agent-sync.schema.json`, and eight reference contracts the
371
+ agent loads on their own trigger rather than by default:
372
+
373
+ | Reference | Read it when |
374
+ |---|---|
375
+ | [`adapter-contract.md`](plugins/agent-sync/skills/agent-sync/references/adapter-contract.md) | adding or auditing a knowledge backend |
376
+ | [`lease-protocol.md`](plugins/agent-sync/skills/agent-sync/references/lease-protocol.md) | changing acquisition, expiry, stealing or id allocation |
377
+ | [`backend-outline.md`](plugins/agent-sync/skills/agent-sync/references/backend-outline.md) | making any Outline API call, or debugging one |
378
+ | [`backend-fs.md`](plugins/agent-sync/skills/agent-sync/references/backend-fs.md) | running without a cloud backend, or explaining degraded mode |
379
+ | [`pipeline-binding.md`](plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md) | wiring `pipeline.json`, or adding a stage hook |
380
+ | [`hooks.md`](plugins/agent-sync/skills/agent-sync/references/hooks.md) | installing, debugging or removing the Claude Code hooks |
381
+ | [`two-sources.md`](plugins/agent-sync/skills/agent-sync/references/two-sources.md) | before the first reconcile, or when deciding where a document belongs |
382
+ | [`roadmap.md`](plugins/agent-sync/skills/agent-sync/references/roadmap.md) | configuring `claimTags`, taking or closing a task, or re-planning a board |
383
+
384
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md). Security
385
+ reports: [SECURITY.md](SECURITY.md).
386
+
387
+ ## License
388
+
389
+ MIT © Appvillis
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/appvillis-com/agent-sync/main/agent-sync.schema.json",
3
+ "backend": "outline",
4
+ "leaseTtlSeconds": 2700,
5
+ "renewIntervalSeconds": 300,
6
+ "gated": true,
7
+ "idRegisters": {
8
+ "DEC": {
9
+ "file": "docs/DECISIONS.md",
10
+ "nextFreeIdPattern": "\\*\\*Next free ID:\\*\\* `DEC-(\\d{4})`"
11
+ },
12
+ "OQ": {
13
+ "file": "docs/OPEN_QUESTIONS.md",
14
+ "nextFreeIdPattern": "\\*\\*Next free ID:\\*\\* `OQ-(\\d{4})`"
15
+ }
16
+ },
17
+ "guardedFiles": [
18
+ "docs/DECISIONS.md",
19
+ "docs/OPEN_QUESTIONS.md",
20
+ "docs/ROADMAP.md"
21
+ ],
22
+ "claimTags": {
23
+ "docs/ROADMAP.md": {
24
+ "mode": "cell",
25
+ "cell": -1,
26
+ "held": "{prev} (claimed: {holder})"
27
+ }
28
+ },
29
+ "gates": [
30
+ "bash scripts/check-docs.sh"
31
+ ],
32
+ "mirror": {
33
+ "enabled": true,
34
+ "sources": [
35
+ "docs/DECISIONS.md",
36
+ "docs/ROADMAP.md"
37
+ ]
38
+ },
39
+ "leaseBackend": "git"
40
+ }
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/appvillis-com/agent-sync/main/agent-sync.schema.json",
4
+ "title": "agent-sync project configuration",
5
+ "description": "Shape only. Identity (instance URL, token, container id) lives in .env.agent-sync and is never committed.",
6
+ "type": "object",
7
+ "required": [
8
+ "backend"
9
+ ],
10
+ "additionalProperties": false,
11
+ "properties": {
12
+ "$schema": {
13
+ "type": "string"
14
+ },
15
+ "backend": {
16
+ "type": "string",
17
+ "enum": [
18
+ "outline",
19
+ "fs"
20
+ ],
21
+ "description": "Which adapter holds coordination state. 'fs' is degraded: not the lease authority across machines."
22
+ },
23
+ "leaseTtlSeconds": {
24
+ "type": "integer",
25
+ "minimum": 60,
26
+ "default": 2700
27
+ },
28
+ "renewIntervalSeconds": {
29
+ "type": "integer",
30
+ "minimum": 30,
31
+ "default": 300
32
+ },
33
+ "gated": {
34
+ "type": "boolean",
35
+ "default": true,
36
+ "description": "Whether runs may be reported as enforced. Effective only when the backend is the lease authority."
37
+ },
38
+ "idRegisters": {
39
+ "type": "object",
40
+ "description": "Registers whose next free id must be reserved before it is written to git.",
41
+ "additionalProperties": {
42
+ "type": "object",
43
+ "required": [
44
+ "file",
45
+ "nextFreeIdPattern"
46
+ ],
47
+ "additionalProperties": false,
48
+ "properties": {
49
+ "file": {
50
+ "type": "string",
51
+ "description": "Repo-relative path to the register file."
52
+ },
53
+ "nextFreeIdPattern": {
54
+ "type": "string",
55
+ "description": "Regex with one capture group holding the next free number."
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "guardedFiles": {
61
+ "type": "array",
62
+ "items": {
63
+ "type": "string"
64
+ },
65
+ "description": "Glob patterns that require a live lease before being written. A submodule lists only its own registers; cross-repository files belong to the parent repository."
66
+ },
67
+ "claimTags": {
68
+ "type": "object",
69
+ "description": "How a lease is written through to the roadmap. See references/roadmap.md.",
70
+ "additionalProperties": {
71
+ "type": "object",
72
+ "required": [
73
+ "mode"
74
+ ],
75
+ "additionalProperties": false,
76
+ "properties": {
77
+ "mode": {
78
+ "type": "string",
79
+ "enum": [
80
+ "cell"
81
+ ],
82
+ "description": "'cell' edits one cell of the single table row containing the task id."
83
+ },
84
+ "cell": {
85
+ "type": "integer",
86
+ "description": "0-based cell index in that row; negative counts from the end."
87
+ },
88
+ "held": {
89
+ "type": "string",
90
+ "description": "Template for the held value. {prev} is the current cell text, {holder} the run id."
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "gates": {
96
+ "type": "array",
97
+ "items": {
98
+ "type": "string"
99
+ },
100
+ "description": "Shell commands run at the pipeline's lint stage; each result is journaled."
101
+ },
102
+ "mirror": {
103
+ "type": "object",
104
+ "additionalProperties": false,
105
+ "properties": {
106
+ "enabled": {
107
+ "type": "boolean",
108
+ "default": false
109
+ },
110
+ "sources": {
111
+ "type": "array",
112
+ "items": {
113
+ "type": "string"
114
+ },
115
+ "description": "Files or directories rendered one-way into the knowledge store, stamped with the source commit."
116
+ }
117
+ }
118
+ },
119
+ "setupFile": {
120
+ "type": "string",
121
+ "description": "Where the generated setup snapshot is written. Defaults to docs/AGENT_SYNC.md when docs/ exists, else AGENT_SYNC.md."
122
+ },
123
+ "leaseBackend": {
124
+ "type": "string",
125
+ "enum": [
126
+ "local",
127
+ "git"
128
+ ],
129
+ "default": "local",
130
+ "description": "Where a contended lease is decided. 'local' is an atomic file create \u2014 exclusive between processes on one filesystem. 'git' pushes a ref, and the remote's non-fast-forward rule is a real compare-and-swap, exclusive across machines."
131
+ },
132
+ "leaseRemote": {
133
+ "type": "string",
134
+ "default": "origin",
135
+ "description": "Git remote holding the lease refs when leaseBackend is 'git'."
136
+ },
137
+ "settleSeconds": {
138
+ "type": "number",
139
+ "minimum": 0,
140
+ "default": 3.0,
141
+ "description": "Unused by the git and local backends; retained for adapters that must wait for writes to become visible."
142
+ }
143
+ }
144
+ }