@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.
@@ -0,0 +1,372 @@
1
+ ---
2
+ name: agent-sync
3
+ description: "Use when several coding agents work one repository at the same time and must not collide - claiming a task, reserving the next decision/question/ticket id, journaling a run, filing or answering a cross-repo dependency, or regenerating the shared board. Triggers - 'claim this task' / 'возьми задачу', 'who is working on X' / 'кто сейчас делает X', 'reserve an id' / 'зарезервируй id', 'sync the board' / 'обнови доску', 'set up agent coordination' / 'настрой координацию агентов', /agent-sync. Use it BEFORE editing any shared registry file (decisions, open questions, roadmap, workstreams, dependencies) in a project that has .claude/agent-sync.json, even when the user never mentions coordination - an unclaimed edit to those files is how two agents overwrite each other."
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
+ license: MIT
6
+ metadata:
7
+ version: "1.2.2"
8
+ author: appvillis-com
9
+ ---
10
+
11
+ # agent-sync — one project, many agents, no collisions
12
+
13
+ Two planes, and one rule between them:
14
+
15
+ > **Git is the record plane. The cloud is the coordination plane.**
16
+ > A fact that must survive is written to git first and referenced from the cloud.
17
+ > A fact about *who is doing what right now* lives in the cloud and expires.
18
+
19
+ No cloud object is ever the only home of a durable fact. Everything below exists to
20
+ keep that true while several agents write at once.
21
+
22
+ ## Four traps — read these before anything else
23
+
24
+ **1. The knowledge base never decides a lease.** It cannot: twelve concurrent appends to
25
+ one Outline document returned twelve successes and left **three** lines. Exclusion comes
26
+ from something with real compare-and-swap. The plane carries the record and nothing else.
27
+ Full measurements in `references/lease-protocol.md`.
28
+
29
+ **2. Know which lease you have, and say so.** `leaseBackend: "local"` is an atomic file
30
+ create — exclusive between processes on one filesystem, **advisory across machines**.
31
+ `leaseBackend: "git"` pushes a ref, and the remote's non-fast-forward rejection **is** a
32
+ compare-and-swap — exclusive across machines. `acquire` prints which. A pretended lease is
33
+ worse than no lease: the other agent stops checking.
34
+
35
+ **`acquire` also writes the claim through to the roadmap**, and `release` restores exactly
36
+ what was there — one row, one cell, refused on ambiguity, `git diff` empty after a
37
+ round-trip. **Read `references/roadmap.md`** before configuring `claimTags` or closing a
38
+ task; closing is a statement about the work and stays yours.
39
+
40
+ **3. Hooks exist only in Claude Code.** Elsewhere nothing blocks a guarded edit: run
41
+ `guard` yourself and record the run as `ungated`. Do not describe a project as protected
42
+ when it is not.
43
+
44
+ **4. Parse liberally, and never call an unreadable log a lost race.** The store rewrites
45
+ what you wrote — Outline turns a `- ` bullet into `* `. Emit `- `, accept `-`/`*`/`+`,
46
+ count anything entry-shaped that fails, and **fail loudly** past 2% unparseable. Reporting
47
+ `lost` when the truth is *unreadable* names a holder who does not exist. Watch for a
48
+ silent pre-filter: a `continue` before the regex hides bad lines from the counter built to
49
+ expose them.
50
+
51
+ ## Bringing this into ANY project — the whole chain
52
+
53
+ ```
54
+ scaffold → create the documentation architecture, only where it is absent
55
+ adopt → read the repository, propose a config, write nothing
56
+ init → write the approved config + the gitignored env file
57
+ (operator pastes the token — never you)
58
+ reconcile --set-baseline → make history a counted backlog, once
59
+ setup → generate the snapshot that describes this project's wiring
60
+ check → validate the whole thing; non-zero if it is not healthy
61
+ ```
62
+
63
+ **`check` is what makes the skill self-sufficient.** It refuses to call a setup healthy
64
+ on any of: a register file that does not exist or whose allocation pattern matches
65
+ nothing; a guard glob that matches no file (a rule that protects nothing); a claim-tag
66
+ pattern with nothing to look for; a gate command whose script is missing; a mirror source
67
+ that is not there; missing or empty credentials; a `.gitignore` that does not cover the
68
+ env file — or the env file being **tracked by git**, which is the one unrecoverable
69
+ mistake here; a missing, hand-edited or stale snapshot; **a snapshot no agent instruction
70
+ file links**, because agents that cannot find it will infer the pipeline instead; and a
71
+ register with no as-built baseline. Every one of those failed for real during this tool's
72
+ own adoption.
73
+
74
+ Run `check` after adopting, after changing the config, and in CI.
75
+
76
+ **`scaffold` never overwrites.** It seeds a decision register with an allocation line and
77
+ an `AGENTS.md` that points at the snapshot, and leaves every existing file untouched — a
78
+ tool that rewrites a project's own conventions on adoption is worse than one that does
79
+ nothing.
80
+
81
+ ## Existing project: start with `adopt`
82
+
83
+ Run `adopt` before `init`. It reads the repository and prints what it found — id
84
+ registers, registry files, gates — plus the decisions it **refuses to make for you**,
85
+ then proposes a config. It writes nothing.
86
+
87
+ ```bash
88
+ python3 "$SKILL_DIR/scripts/agent_sync.py" adopt
89
+ ```
90
+
91
+ Confirm the registers and guarded files with the operator before writing them. A
92
+ register pointed at the wrong file makes every later check confidently wrong, and a
93
+ guarded list that misses a shared file leaves the one place collisions actually happen
94
+ unprotected. In a submodule it declares no registers at all: decisions belong to the
95
+ parent repository.
96
+
97
+ Then: `init` → paste the approved config → `reconcile --set-baseline` → `setup` →
98
+ commit the snapshot and link it from the project's agent instructions.
99
+
100
+ ## First command in a project: `init`
101
+
102
+ **Never run anything else against an uninitialised project.** `init` is where the
103
+ storage question gets asked and answered, once, and written down.
104
+
105
+ **Ask the operator these two things in chat — do not guess, do not pick a default:**
106
+
107
+ 1. **Where should coordination state live?**
108
+ - a knowledge cloud (`outline`) — the shared record, awareness and board across
109
+ machines. **It does not decide leases**; nothing in it can (trap 1);
110
+ - or local files (`fs`) — no credentials, no shared awareness, every run `ungated`.
111
+
112
+ The lease is decided separately by `leaseBackend` — `git` for cross-machine exclusion,
113
+ `local` otherwise.
114
+ 2. **If cloud: the instance URL.** The URL is configuration, not a secret, so you
115
+ may write it. The **token is not** — you never ask for it in chat, never read it
116
+ back, and never place it yourself.
117
+
118
+ Then run it with their answers:
119
+
120
+ ```bash
121
+ python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend outline --url https://<their-instance>
122
+ python3 "$SKILL_DIR/scripts/agent_sync.py" init --backend fs
123
+ ```
124
+
125
+ `init` writes `.claude/agent-sync.json` (shape, committed), writes
126
+ `.env.agent-sync` with the keys and an **empty** token line (identity, mode 600),
127
+ adds `.env.agent-sync` and `.agent-sync/` to `.gitignore`, and then prints exactly
128
+ what the operator must do themselves — create the token in their own instance and
129
+ paste it into that one line. It never overwrites an existing config or env file
130
+ without `--force`.
131
+
132
+ Relay those closing instructions to the operator verbatim. Getting the token into
133
+ the file is their step, and the design depends on it staying theirs.
134
+
135
+ ## Then, before every session
136
+
137
+ ```bash
138
+ python3 "$SKILL_DIR/scripts/agent_sync.py" status
139
+ ```
140
+
141
+ Idempotent. Inspects, repairs what is missing, prints a status block, names exactly
142
+ ONE next action.
143
+
144
+ **Read the two awareness sections it prints — they are the point, not decoration.**
145
+
146
+ - **Other runs working this project right now.** Who holds what, this minute. Do not
147
+ take those on, and do not "just look at" the files they cover. A lease you cannot
148
+ see makes you blocked; a lease you can see makes you coordinated.
149
+ - **New since you last looked.** Cross-repo dependency moves that landed while you
150
+ were away. A dependency that moved may unblock what you planned — or invalidate it.
151
+ This list is watermarked per run, so it stays quiet until something actually
152
+ changes; when it speaks, it matters.
153
+
154
+ An agent that skips this block will re-derive work someone else is doing and act on a
155
+ dependency state that changed an hour ago.
156
+
157
+ What else `status` decides for you:
158
+
159
+ - No credentials in the environment → degraded mode, reported, and it continues.
160
+ Missing credentials are not an error, they are a smaller mode.
161
+ - `task-pipeline` absent → it prints the install line and stops. Do not improvise a
162
+ substitute flow; without those stages there is nothing to bind to.
163
+
164
+ ```bash
165
+ npx sshlg-skills install
166
+ ```
167
+
168
+ ## The commands
169
+
170
+ | Command | Does |
171
+ |---|---|
172
+ | `init` | **Run first.** Ask where state lives, write config + gitignored env file, print the operator's step |
173
+ | `status` | Inspect, repair, report, name one next action |
174
+ | `bootstrap` | Create the cloud container and print the id to paste into the env file |
175
+ | `acquire <KEY>` | Take the lease on a task id. Prints `won` or `lost <holder>` |
176
+ | `renew <KEY>` | Extend the lease. The `PostToolUse` hook does this for you |
177
+ | `release <KEY>` | Give the lease back. Always do this, including on failure |
178
+ | `reserve <REG>` | Reserve the next id in a register (`DEC`, `OQ`, `DEP`, …). Prints the id |
179
+ | `release-id <REG> <ID>` | Return an id you did not end up writing to git |
180
+ | `journal <text>` | Append one line to this run's journal |
181
+ | `record <text>` | Append what you **actually built** — `--decision DEC-…`, `--files a,b` |
182
+ | `reconcile` | Intent (git) vs as-built (cloud). `--set-baseline` once per project |
183
+ | `signal <DEP-ID> <state>` | Move a cross-repo dependency: `filed`/`accepted`/`delivered`/`closed`/`refused` |
184
+ | `guard <path>` | Answer whether this run may write that path. Exit 0 = yes, 2 = no |
185
+ | `board` | Regenerate the shared board and this repo's page. `--mirror` also renders the configured git docs into the plane |
186
+ | `whoami` | Print this run's id and its held leases |
187
+ | `setup` | Write the generated snapshot of how **this** project is wired, for agents to read |
188
+ | `adopt` | Inspect an existing project and **propose** a config — writes nothing |
189
+ | `scaffold` | Create the missing documentation architecture. Never overwrites |
190
+ | `check` | Validate the whole setup end to end. Non-zero when it is not healthy |
191
+
192
+ `$SKILL_DIR` is this skill's own directory. Every command reads
193
+ `.claude/agent-sync.json` from the project root and needs no arguments beyond those
194
+ listed.
195
+
196
+ ## Claiming — the shape that matters
197
+
198
+ ```
199
+ acquire → do the work → release
200
+ ```
201
+
202
+ Never skip `release`, including on failure: an abandoned lease blocks the task until its
203
+ TTL expires, and the next agent cannot tell "in progress" from "crashed an hour ago".
204
+
205
+ **The lease is not the claim.** The lease says who holds it *now* and expires; the durable
206
+ claim is the tag in git, written through by `acquire` and cleared by `release`. One fact,
207
+ one home — do not invent a third place that records ownership.
208
+
209
+ **Read `references/lease-protocol.md`** before changing acquisition, expiry, stealing or
210
+ id allocation.
211
+
212
+ ## Guarded files
213
+
214
+ The config lists registry files several agents write. Before editing one:
215
+
216
+ ```bash
217
+ python3 "$SKILL_DIR/scripts/agent_sync.py" guard docs/DECISIONS.md
218
+ ```
219
+
220
+ Exit 2 means another run holds it. Do not edit anyway, and do not "just fix one line" —
221
+ a clobbered decision looks exactly like a decision.
222
+
223
+ Claude Code's `PreToolUse` hook runs this for you. Elsewhere nothing does.
224
+
225
+ ## Reserving an id
226
+
227
+ Reading a "Next free ID" line is not reserving it — two agents read the same number and
228
+ both use it.
229
+
230
+ ```bash
231
+ python3 "$SKILL_DIR/scripts/agent_sync.py" reserve DEC # → DEC-0216
232
+ ```
233
+
234
+ Allocation is positional over the log, so every agent computes the same answer. Reserve
235
+ and not write it to git? `release-id` it — otherwise the number is a hole the board
236
+ reports as a leak, and nobody can tell a hole from work on a branch.
237
+
238
+ ## Nothing in a log is ever edited or deleted
239
+
240
+ Logs are **replayed in order**, so an edit silently rewrites a conclusion other agents
241
+ already acted on. Correct by **appending**: release a lease, `release-id` an unused id,
242
+ supersede a wrong as-built entry with a later one. Generated pages are the only
243
+ exception, and one that lost its `agent-sync:generated` marker is **refused**, not
244
+ overwritten. Lifetimes: `references/two-sources.md`.
245
+
246
+ ## Two documentation sources, and the duty to reconcile them
247
+
248
+ Git docs answer **how it should be** — written before the code, often without it.
249
+ The as-built record answers **how it actually is** — derived from what agents really
250
+ wrote. Neither is a copy of the other, and neither outranks the other, because they
251
+ answer different questions. **The gap between them is the finding**, not a defect.
252
+
253
+ The duty runs at both ends of every task:
254
+
255
+ - **Before starting** (docs-study stage) — `reconcile`, then read both sides for the
256
+ area you are about to touch. Resolve each divergence: the git doc is stale, or the
257
+ as-built record is wrong, or they genuinely disagree and that is a decision to make.
258
+ Building on an unresolved divergence means writing code against a system that does
259
+ not exist.
260
+ - **After finishing** (docs stage) — `record` what you actually built, update the git
261
+ documents that state intent, then `reconcile` again. A task that updated one side
262
+ has left the next agent a divergence to find the hard way.
263
+
264
+ `reconcile` is mechanical and says so: it compares ids, commits and presence, and refuses
265
+ to judge whether the built thing matches the document. That reading is yours.
266
+
267
+ Every project also carries a **generated snapshot** of its own wiring (`setup`) — commit
268
+ it and link it from the agent instructions, so agents read the pipeline instead of
269
+ inferring it.
270
+
271
+ **Read `references/two-sources.md`** before the first reconcile, and whenever deciding
272
+ which side a document belongs on.
273
+
274
+ ## Binding to task-pipeline
275
+
276
+ This skill supplies stages; it does not define them. Stage names are
277
+ `task-pipeline`'s own.
278
+
279
+ | Stage | What to do here |
280
+ |---|---|
281
+ | 0 Intake grill | Add the cloud KB and the board to the harvest's source ledger; `acquire` **before** the brief is committed |
282
+ | 1 Docs study | `reconcile` — study git docs **and** the as-built record, resolve every divergence before writing code |
283
+ | 2 Brainstorm | `journal`; warn if a live run holds an overlapping key |
284
+ | 3 Spec | `reserve` every id before writing it to git |
285
+ | 4 Plan | Register file ownership for the plan's parallel groups |
286
+ | 5 Dev | Lease renews itself; own the submodule-commit → parent-gitlink bump |
287
+ | 6 Tests · 7 Lint · 8 Post-deploy | `journal` each gate result |
288
+ | 9 Docs + wiki | The main write point — `record` what was built, update the git docs, `signal` the dependency flips, `reconcile` again, then `board` |
289
+ | 10 Acceptance | `release` every lease, write the durable claim tag through to done |
290
+
291
+ **Read `references/pipeline-binding.md` when wiring `pipeline.json`** — it holds the
292
+ `skills[]` entries and the gate expressions.
293
+
294
+ ## Configuration
295
+
296
+ Two files, and the split between them is the whole security model.
297
+
298
+ **`.claude/agent-sync.json`** — *shape*, committed: which backend, TTLs, which files
299
+ are guarded, which registers exist, which gates to run.
300
+
301
+ **`.env.agent-sync`** — *identity*, created by `init`, mode 600, gitignored:
302
+
303
+ ```
304
+ AGENT_SYNC_BACKEND=outline
305
+ AGENT_SYNC_OUTLINE_URL=https://<instance>
306
+ AGENT_SYNC_OUTLINE_TOKEN= # the operator fills this line, nobody else
307
+ AGENT_SYNC_OUTLINE_COLLECTION= # printed by `bootstrap`
308
+ ```
309
+
310
+ Load it before running agents:
311
+
312
+ ```bash
313
+ set -a && . ./.env.agent-sync && set +a
314
+ ```
315
+
316
+ Never write a host name or token into the config, a test, an example or a commit. Do not
317
+ handle a token value, echo it, or pass it in `argv`. If the operator offers one in chat,
318
+ tell them to put it in that file instead.
319
+
320
+ **A submodule's config declares only its own registers.** Cross-repository facts belong to
321
+ the parent; a service repo listing the parent's decision register is a config defect.
322
+
323
+ ## Backends
324
+
325
+ | Backend | Read when |
326
+ |---|---|
327
+ | `outline` | `references/backend-outline.md` — before any Outline call |
328
+ | `fs` | `references/backend-fs.md` — local files, no shared awareness |
329
+
330
+ **Read `references/adapter-contract.md` before adding a backend** — six primitives, the
331
+ capability flags, and a degradation path that must be honest.
332
+
333
+ ## Generated objects
334
+
335
+ The board and the mirror are machine-written. Their first line is
336
+
337
+ ```
338
+ <!-- agent-sync:generated source=<repo>@<sha> at=<iso8601> — edit in git, not here -->
339
+ ```
340
+
341
+ A write to an object missing that marker is refused, not forced. If a human took over a
342
+ generated page, report it and stop.
343
+
344
+ The mirror is a **rendering** of git, stamped with the source commit. It has no
345
+ authority. When its stamp and `HEAD` disagree, the board gate fails — that is
346
+ drift, not a formatting problem.
347
+
348
+ ## Non-negotiables
349
+
350
+ - Append, read back, then act. Never rewrite a coordination document.
351
+ - `release` what you `acquire`, on every path including failure.
352
+ - Credentials never reach `argv`, a log line, or the repository.
353
+ - Degrade out loud. `ungated` is an acceptable state; a false claim of enforcement is not.
354
+ - Everything the cloud holds about a durable fact is a link to git, never a substitute.
355
+
356
+ ## References
357
+
358
+ Each file is loaded on its own trigger, not by default.
359
+
360
+ | File | Read it when |
361
+ |---|---|
362
+ | `references/adapter-contract.md` | adding or auditing a knowledge backend |
363
+ | `references/lease-protocol.md` | changing acquisition, expiry, stealing or id allocation |
364
+ | `references/backend-outline.md` | making any Outline API call, or debugging one |
365
+ | `references/backend-fs.md` | running without a cloud backend, or explaining degraded mode |
366
+ | `references/pipeline-binding.md` | wiring `pipeline.json`, or adding a stage hook |
367
+ | `references/hooks.md` | installing, debugging or removing the Claude Code hooks |
368
+ | `references/two-sources.md` | before the first reconcile, or when deciding where a document belongs |
369
+ | `references/roadmap.md` | configuring `claimTags`, taking or closing a task, or re-planning a board |
370
+
371
+ If this copy arrived without `references/`, fetch them from
372
+ `https://raw.githubusercontent.com/appvillis-com/agent-sync/main/plugins/agent-sync/skills/agent-sync/references/<file>`.
@@ -0,0 +1,80 @@
1
+ # Adapter contract
2
+
3
+ **Read this when** adding a knowledge backend, auditing one, or deciding whether a
4
+ candidate backend can be trusted with leases.
5
+
6
+ A backend is an adapter that implements six primitives and declares three
7
+ capabilities. Nothing else about it is the coordinator's business.
8
+
9
+ ## Primitives
10
+
11
+ | Primitive | Signature | Semantics |
12
+ |---|---|---|
13
+ | `tree.ensure` | `(path) -> id` | Idempotently create the container/document at `path`, return its id. MUST NOT overwrite existing content. |
14
+ | `log.append` | `(id, line) -> ok` | Append exactly one `\n`-terminated line to the end of the object's text, **server-side**, with no read-modify-write cycle. |
15
+ | `log.read` | `(id) -> text` | Return the object's full text. Line order MUST be identical for every reader at a given revision. |
16
+ | `doc.put` | `(id, text) -> ok` | Replace the object's text wholesale. Generators only. |
17
+ | `doc.get` | `(id) -> text` | Return the object's text. |
18
+ | `search` | `(query, limit) -> [{id,title,snippet}]` | Full-text search. Feeds the pipeline's stage-0 knowledge harvest. |
19
+
20
+ ## Capabilities
21
+
22
+ ```json
23
+ { "atomicAppend": true, "totalOrderRead": true, "search": true,
24
+ "exclusiveLease": false }
25
+ ```
26
+
27
+ - **`atomicAppend`** — `log.append` reaches the server as an append. If the adapter
28
+ implements it as *read text, concatenate, write text back*, this is **false**, no
29
+ matter how fast that is.
30
+ - **`totalOrderRead`** — concurrent appends land in one order and every reader sees
31
+ the same order. A backend that merges concurrently, or that returns per-reader
32
+ views, is **false**.
33
+ - **`search`** — `search` is implemented rather than stubbed.
34
+ - **`exclusiveLease`** — whether *this backend* can decide a contended lease. Almost
35
+ always **false**: it requires compare-and-swap, and none of the document stores has
36
+ it. Declaring it true without one is the most damaging lie an adapter can tell, so
37
+ the default is false and the burden of proof is on the adapter.
38
+
39
+ ## Degradation — non-negotiable
40
+
41
+ **No adapter is the lease authority.** Exclusion is an atomic local lock; the adapter
42
+ carries the record and the awareness. If `atomicAppend` or `totalOrderRead` is false the
43
+ coordinator additionally:
44
+
45
+ 1. states it once, in plain words, at session start;
46
+ 2. keeps the local lock as the only arbiter (`references/backend-fs.md`);
47
+ 3. marks every run `ungated` on the board.
48
+
49
+ There is no third option. A lease that is not actually exclusive is worse than no
50
+ lease at all, because the other agent stops checking.
51
+
52
+ ## Errors and retries
53
+
54
+ Every primitive returns a typed failure; none may raise past the caller.
55
+
56
+ | Condition | Handling |
57
+ |---|---|
58
+ | Rate limited | Honour the backend's own retry hint; exponential backoff; at most 5 attempts, then fail loudly |
59
+ | Auth failure | Fail immediately. Never retry a credential — it is not going to become valid |
60
+ | Not found | For `tree.ensure`, create. For everything else, report the missing path; do not create silently |
61
+ | Transport error | Retry twice with backoff, then fail with the underlying message intact |
62
+
63
+ Never swallow an error into a success. A coordination layer that reports a write it
64
+ did not make is the failure this whole design exists to prevent.
65
+
66
+ ## Credentials
67
+
68
+ - Read from the environment only. Never from the config file, never from `argv`.
69
+ - Never echo, never log, never include in a journal line or a board render.
70
+ - When absent: degraded mode with a clear message, not a crash and not a prompt.
71
+
72
+ ## Adding a backend — checklist
73
+
74
+ - [ ] Six primitives implemented, each returning a typed failure
75
+ - [ ] Three capabilities declared **honestly**; `atomicAppend` false unless the append is server-side
76
+ - [ ] Credentials read from env only, and absent from every code path that builds a command line
77
+ - [ ] Rate-limit hint honoured
78
+ - [ ] `tree.ensure` proven idempotent by calling it twice against a live instance
79
+ - [ ] `log.append` proven atomic: two processes appending 100 lines each yield 200 lines, in one order both readers agree on
80
+ - [ ] Degradation path exercised: force `atomicAppend: false` and confirm the coordinator refuses lease authority and says so
@@ -0,0 +1,57 @@
1
+ # Filesystem backend — the degraded mode
2
+
3
+ **Read this when** running without a cloud backend, or explaining to an operator
4
+ what they do and do not get.
5
+
6
+ ## What it is
7
+
8
+ Plain files under `.agent-sync/` in the repository:
9
+
10
+ ```
11
+ .agent-sync/
12
+ ├── claims.log # the append log
13
+ ├── reservations.log
14
+ ├── signals.log
15
+ ├── runs/<runId>.log
16
+ └── board.md # generated
17
+ ```
18
+
19
+ ## Capabilities — declared honestly
20
+
21
+ ```json
22
+ { "atomicAppend": false, "totalOrderRead": false, "search": false }
23
+ ```
24
+
25
+ `atomicAppend` is false: agents on two clones append to two files, and a merge decides the
26
+ order after the fact — not at the moment the protocol needs one.
27
+
28
+ ## What follows from that
29
+
30
+ **No adapter is ever the lease authority** — not this one, not the cloud. Exclusion comes
31
+ from `leaseBackend` (`local` = `O_EXCL`, `git` = a pushed ref). What this backend costs is
32
+ **awareness**: with it configured, the coordinator:
33
+
34
+ 1. says so at session start, in one plain sentence;
35
+ 2. keeps the lease exactly as configured — `leaseBackend` is independent of this choice;
36
+ 3. marks every run `ungated` on the board, because nobody else can read the state.
37
+
38
+ ## The lease is not this backend's job
39
+
40
+ Do not look for a lease mechanism here. `leaseBackend: "local"` decides with an atomic
41
+ file create; `leaseBackend: "git"` decides with a pushed ref whose non-fast-forward
42
+ rejection is a real compare-and-swap. Both work regardless of which knowledge backend is
43
+ configured — see `lease-protocol.md`.
44
+
45
+ ## When this is the right choice
46
+
47
+ - A project with one agent at a time, which wants the journal and the board without
48
+ standing up a service.
49
+ - An air-gapped or offline repository.
50
+ - A first run, before the operator has chosen a knowledge backend.
51
+
52
+ ## When it is the wrong choice
53
+
54
+ - Several agents at once: they hold leases correctly but cannot **see** each other, and
55
+ the whole point of the coordination plane is that they can.
56
+ - Anything where the operator has been told the project is coordinated. The lease still
57
+ works; the awareness does not, and the board must keep saying `ungated`.
@@ -0,0 +1,103 @@
1
+ # Outline backend
2
+
3
+ **Read this when** making any Outline API call or debugging one.
4
+
5
+ [Outline](https://www.getoutline.com) is a collaborative knowledge base, available
6
+ hosted or self-hosted. Both work; the instance URL is configuration, never code.
7
+
8
+ ## Shape of the API
9
+
10
+ - Base: `<AGENT_SYNC_OUTLINE_URL>/api` — the instance URL comes from the
11
+ environment. **Never hardcode a host, not even in a test or an example.**
12
+ - **Every endpoint is `POST`**, including reads.
13
+ - `Authorization: Bearer <token>` and `Content-Type: application/json`.
14
+ - Every response is enveloped: `{"ok": true, "status": 200, "data": {…}}` or
15
+ `{"ok": false, "error": "…", "message": "…", "status": 4xx}`. Check `ok`; an
16
+ HTTP 200 with `ok: false` is a failure.
17
+
18
+ ## Capabilities
19
+
20
+ ```json
21
+ { "atomicAppend": true, "totalOrderRead": true, "search": true }
22
+ ```
23
+
24
+ `atomicAppend` is true because `documents.update` accepts
25
+ `editMode: "append"`, which appends server-side without a read-modify-write cycle.
26
+
27
+ **There is no `lastRevision` parameter and no other optimistic-concurrency
28
+ control.** This is the single most important fact about this backend: a `replace`
29
+ update is last-write-wins and will silently discard a concurrent edit. Coordination
30
+ state is therefore never modelled as a document you rewrite — see
31
+ `lease-protocol.md`.
32
+
33
+ ## Primitive mapping
34
+
35
+ | Primitive | Call | Body |
36
+ |---|---|---|
37
+ | `tree.ensure` (container) | `POST /api/collections.create` | `{name}` — or `collections.list` first and reuse |
38
+ | `tree.ensure` (document) | `POST /api/documents.create` | `{collectionId, parentDocumentId?, title, text, publish: true}` |
39
+ | `log.append` | `POST /api/documents.update` | `{id, text: "<line>\n", editMode: "append"}` |
40
+ | `log.read` | `POST /api/documents.info` | `{id}` → `data.text` |
41
+ | `doc.put` | `POST /api/documents.update` | `{id, text, editMode: "replace"}` |
42
+ | `doc.get` | `POST /api/documents.info` | `{id}` → `data.text` |
43
+ | `search` | `POST /api/documents.search` | `{query, limit, collectionId?}` |
44
+
45
+ `publish: true` on create, or the document stays a draft and no other agent can
46
+ read it.
47
+
48
+ ## Calling it without leaking the token
49
+
50
+ `curl -H "Authorization: Bearer $TOKEN"` puts the credential in `argv`, where every
51
+ other process on the machine can read it. Use a config file on stdin instead:
52
+
53
+ ```bash
54
+ payload=$(mktemp); chmod 600 "$payload"
55
+ printf '%s' "$body_json" > "$payload"
56
+
57
+ curl -sS --config - <<EOF
58
+ url = "$AGENT_SYNC_OUTLINE_URL/api/documents.update"
59
+ request = "POST"
60
+ header = "Authorization: Bearer $AGENT_SYNC_OUTLINE_TOKEN"
61
+ header = "Content-Type: application/json"
62
+ data-binary = "@$payload"
63
+ EOF
64
+
65
+ rm -f "$payload"
66
+ ```
67
+
68
+ The heredoc reaches curl on stdin, so neither the token nor the payload appears in
69
+ the process table. Delete the payload file on every exit path.
70
+
71
+ **Prefer the bundled `scripts/agent_sync.py` over hand-rolling any request.** It
72
+ calls the API through `urllib` inside its own process — no subprocess, no `argv`,
73
+ nothing for another process to read. The `curl` recipe above is for the case where
74
+ you must issue a call by hand; it is the safe way to do that, not the better way.
75
+
76
+ ## Rate limits
77
+
78
+ `429` comes back with `Retry-After` (seconds) plus `X-RateLimit-Limit`,
79
+ `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Honour `Retry-After`, then back off
80
+ exponentially, at most 5 attempts. Do not spin.
81
+
82
+ ## Getting a token
83
+
84
+ The operator creates an API key in their own instance's settings and puts it in
85
+ their environment. Do not ask for the value in chat, do not read it back, and do not
86
+ write it anywhere in the repository.
87
+
88
+ ```
89
+ AGENT_SYNC_OUTLINE_URL=https://<your-instance>
90
+ AGENT_SYNC_OUTLINE_TOKEN=<created by the operator>
91
+ AGENT_SYNC_OUTLINE_COLLECTION=<collection id, printed by `init`>
92
+ ```
93
+
94
+ ## Verifying an instance
95
+
96
+ ```bash
97
+ curl -sS -o /dev/null -w '%{http_code}\n' -X POST \
98
+ "$AGENT_SYNC_OUTLINE_URL/api/auth.info" -H 'Content-Type: application/json' -d '{}'
99
+ ```
100
+
101
+ `401` is the healthy answer without a token: the host is up and demands auth.
102
+ A connection error means the URL is wrong or the instance is down — that is not a
103
+ credentials problem, and retrying with a different token will not help.