@awebai/oats 0.25.0 → 0.25.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.
@@ -16,6 +16,19 @@ schemaVersion 2 only; found 1"`, `E_LOCK_SCHEMA`), never a silent fallback.
16
16
  Keep the 0.24 kernel installed until the last 0.24 deployment you care about is
17
17
  rebuilt; the two do not share files.
18
18
 
19
+ **One thing a 0.25 kernel changes for a classic home it does launch.** Decision
20
+ 13 ("harnesses start normally") is a property of the 0.25 *launcher*, not of the
21
+ v2 files: every `pi` launch a 0.25 kernel performs — `oats spawn`, `oats session
22
+ start|restart`, scheduled runs — starts pi with cwd = the instance home and pi's
23
+ own skill and context discovery intact (`--append-system-prompt <home>/AGENTS.md`,
24
+ no `--no-skills` / `--no-context-files` / `--no-prompt-templates` exclusion).
25
+ That holds for a classic 0.24 home (no `oats-local.yaml`, spawned through the
26
+ pre-v2 compose path that 0.25 still carries) exactly as for a module home. If you
27
+ relied on 0.24's ambient-skill exclusion to hide machine-level or repo-level
28
+ skills from an instance, that isolation is gone the moment a 0.25 kernel
29
+ launches it — keep the 0.24 kernel for those homes, or accept the ambient set
30
+ (the spawn preview lists composed skill names so a clash is visible).
31
+
19
32
  ## 1. Decide the one workspace
20
33
 
21
34
  One workspace per organisation. Pick the repo that **hosts**
@@ -35,6 +48,8 @@ capabilities plus `oats.core`), so a public soul stays usable.
35
48
  Two teams that need two different messaging identities (an open-source team
36
49
  and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
37
50
  and the provider payload is addressed by label under `messaging.byTeam` (§2).
51
+ Read §8b before relying on it: the kernel merges `byTeam`, but oats.aweb 1.11.2
52
+ does not yet read the `team` it delivers.
38
53
 
39
54
  ## 2. Write `oats-workspace.yaml` v2 in the host repo
40
55
 
@@ -93,6 +108,29 @@ Delete `oats.yaml`. Its `exports:` lists are gone: every `souls/*/soul.yaml` and
93
108
  `capabilities/*/oats.json` is discoverable; add `private: true` to the ones that
94
109
  should stay internal. The host repo backlinks to itself like any member.
95
110
 
111
+ ## 3b. Move the souls: `agents/<name>/soul/` → `souls/<name>/`
112
+
113
+ In 0.24 a repo's souls lived at `agents/<name>/soul/` beside that soul's
114
+ instances. Under v2 discovery looks **only** at `souls/<name>/soul.yaml`; the
115
+ `agents/` directory belongs to the *deployment* (instance homes and, under the
116
+ kernel's per-commit soul cache, the fetched soul copies — see §7b) and is not
117
+ read as a soul source. Move every soul as a tracked rename so history follows:
118
+
119
+ ```bash
120
+ mkdir -p souls
121
+ git mv agents/release-manager/soul souls/release-manager
122
+ # … one line per soul; then
123
+ git rm -r --cached agents 2>/dev/null; echo 'agents/' >> .gitignore # instances were never meant to be tracked
124
+ ```
125
+
126
+ `souls/<name>/` keeps its `AGENTS.md`, `CLAUDE.md → AGENTS.md` alias, `skills/`,
127
+ `knowledge/` and `soul.yaml` (rewritten in §4); the directory name must equal
128
+ `soul.yaml#name`. Then fix whatever enumerates the old path: repo tests, scripts,
129
+ CI checks and any `oats.yaml`-era `exports:` tooling that globbed
130
+ `agents/*/soul/soul.yaml` (`git grep -n 'agents/.*/soul'` finds them) — under v2
131
+ they enumerate `souls/*/soul.yaml`. A soul left under `agents/` is invisible to
132
+ `oats souls` and to `oats spawn`; nothing warns about it.
133
+
96
134
  ## 4. Edit every `soul.yaml` to v2
97
135
 
98
136
  | 0.24 | v2 |
@@ -105,7 +143,7 @@ should stay internal. The host repo backlinks to itself like any member.
105
143
  | `stores.inherit` | delete (stores are declared once in the workspace) |
106
144
  | `imports` | delete |
107
145
  | `kind`, `type`, `repo`, `runtime`, `model`, `launch-config` | delete — model/runtime/launch config are spawn-time choices; `team:` replaces `type:` as the grouping |
108
- | `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot |
146
+ | `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot. **For `oats.okf` see the box below: the payload is the binding's SETTINGS keys only; what the soul owns/reads stays in `okf.json`** |
109
147
  | — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
110
148
 
111
149
  ```yaml
@@ -116,9 +154,8 @@ work: worktree
116
154
  team: engineering
117
155
  capabilities:
118
156
  acme-release-tooling: { from: here }
119
- knowledge:
120
- owns: release-manager
121
- reads: [platform-engineer]
157
+ # knowledge: — nothing here for oats.okf: the workspace default fills the slot and
158
+ # souls/release-manager/okf.json (below) says what this soul owns and reads.
122
159
  messaging:
123
160
  channels: [acme-eng]
124
161
  ```
@@ -128,31 +165,133 @@ are required. Capabilities the repo exports live at
128
165
  `capabilities/<name>/oats.json` — the manifest is unchanged; you may add
129
166
  `private: true` / `team:`.
130
167
 
168
+ **`oats.okf` 2.1.3 reads `souls/<name>/okf.json`, not a `knowledge:` payload.**
169
+ Earlier drafts of this guide showed `knowledge: { owns: …, reads: … }` or
170
+ `knowledge: { store, root }` on the soul; **no shipped provider consumes those
171
+ keys**. What OKF 2.1.3 actually reads at spawn is two things:
172
+
173
+ 1. **`<soul>/okf.json`** (travels with the soul, fetched into the per-commit
174
+ soul cache like `AGENTS.md`) — the soul's knowledge declaration, exactly
175
+ these keys and no others:
176
+
177
+ ```json
178
+ { "version": 1,
179
+ "owner": "release-manager",
180
+ "owns": ["org/release-manager"],
181
+ "reads": ["org/platform-engineer"] }
182
+ ```
183
+
184
+ `owner` is the stable owner id (what `owners.json` pins, §7b); `owns` /
185
+ `reads` are `<base alias>/<node>` references into the bases the machine's
186
+ bindings file declares (`oats okf init` / `oats okf migrate` write it;
187
+ `capabilities/oats-okf/lib/config.mjs#validateDeclaration` is the
188
+ authority). Keep the file where 0.24 had it — it moves with the soul in
189
+ §3b. A soul without `okf.json` whose slot resolves to `oats.okf` fails the
190
+ required spawn hook (`soul has no okf.json`), by design.
191
+ 2. **The merged payload, as `OATS_SETTINGS`** — the binding's **settings
192
+ keys only**, the list in `capabilities/oats-okf/oats.json#settings`:
193
+ `bindings-file`, `state-dir` (both required, absolute host paths →
194
+ `oats-local.yaml`, §5), `harvest-runtime`, `harvest-model` (optional). Any
195
+ other key — `owns`, `reads`, `store`, `root`, `stores` — is refused
196
+ (`unknown OATS_SETTINGS property`). So for `oats.okf` the soul's
197
+ `knowledge:` payload is normally **absent** (the workspace default
198
+ `defaults.knowledge: { oats.okf: { from: package } }` fills the slot) or
199
+ carries a soul-true binding setting such as `harvest-runtime: claude`;
200
+ `knowledge: none` opts the soul out.
201
+
202
+ `stores:` in the workspace file names repositories for the **workspace**; where
203
+ OKF's bases live inside them is a **bindings-file** concern today (`bases.<alias>`
204
+ with `repository` + `root`), not a soul payload key. A soul payload grammar for
205
+ OKF (`owns`/`reads`/`root` on `soul.yaml`) is an OKF follow-up (it lands with an
206
+ `oats.okf` release that declares it in its binding, and this guide will say so);
207
+ until then the kernel forwards the payload opaquely and OKF refuses what it does
208
+ not know.
209
+
210
+ **Carry `team:` on every soul, or on its repo's membership.** A soul's team is
211
+ `soul.yaml#team`, else `oats-membership.yaml#team`, else *unassigned*
212
+ (`null`). Labels never gate anything, but the kernel addresses provider payload
213
+ by label: an unlabelled soul receives the messaging **base** payload only —
214
+ `workspace.messaging` minus `byTeam`, no `byTeam.<label>` block, and no
215
+ `defaults.byTeam.<label>` capabilities either. If your 0.24 deployment had one
216
+ messaging identity per team (§1), a soul that loses its label silently lands
217
+ outside every team-addressed payload; nothing refuses it. Label the membership
218
+ when a whole repo belongs to one team, and the soul when it does not.
219
+
220
+ **Per-soul memory-harvest opt-out:** not available in OKF 2.1.3 — an OKF 2.1.4
221
+ item. Neither `okf.json` (`version`, `owner`, `owns`, `reads`) nor the settings
222
+ payload (`bindings-file`, `state-dir`, `harvest-runtime`, `harvest-model`) has a
223
+ key that keeps a soul registered for reads while excluding it from harvest. A
224
+ soul that must not be harvested today says `knowledge: none` (no OKF at all for
225
+ that soul) or `oats.okf: off`; do not invent a key — both readers refuse unknown
226
+ keys.
227
+
131
228
  ## 5. Write `oats-local.yaml` on each machine
132
229
 
133
230
  ```
134
- ~/acme-workspace/ # the taught convention: "<name>-workspace"
231
+ ~/acme/ # the directory YOU choose — an existing folder with your clones is the usual case
135
232
  ├── oats-local.yaml
136
- ├── agents/ # instance homes
137
- └── platform/ # member clones, only where someone works IN them
233
+ ├── agents/ # instance homes — created by `oats sync` if absent (0.25.2)
234
+ └── platform/ # member clones, wherever you keep them (here, or named in clones:)
138
235
  ```
139
236
 
140
237
  ```yaml
141
238
  schemaVersion: 2
142
239
  workspace: git:github.com/acme/agents
240
+ clones: # optional: member clones that are NOT at <deployment>/<member name>
241
+ github.com/acme/platform: /Users/ana/src/acme-platform
143
242
  settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
144
243
  oats.okf:
145
- bindings-file: /Users/ana/.oats/okf-bindings.json
146
- state-dir: /Users/ana/.oats/okf
244
+ bindings-file: /Users/ana/.oats/okf-bindings.json # required by the OKF binding: absolute host path
245
+ state-dir: /Users/ana/.oats/okf-state # required by the OKF binding: absolute host path; FRESH for a rebuilt deployment (§7b)
246
+ harvest-runtime: pi # optional: pi | claude | codex (default pi)
247
+ oats.aweb:
248
+ delivery: channel # channel (default) | session — see capabilities/oats-aweb/oats.json#settings.delivery
147
249
  souls:
148
250
  disabled: [data-analyst]
149
251
  ```
150
252
 
253
+ **Where the kernel looks for a member clone** (a `work: worktree | checkout`
254
+ soul needs one; nothing else does). In this order, first hit wins:
255
+
256
+ 1. `oats spawn … --repo <abs path>` — this spawn only.
257
+ 2. `oats-local.yaml` `clones: { <repo key>: <abs path> }` — the key is the
258
+ member's **canonical key** (`github.com/acme/platform`; any ref spelling you
259
+ write is normalised through `parseRepoRef`, so `git:github.com/acme/platform`
260
+ and `https://github.com/acme/platform.git` address the same entry).
261
+ 3. The convention: `<deployment>/<member name>` — the last path segment of the
262
+ repo key (`platform` for `github.com/acme/platform`). One exception: a member
263
+ whose name is `agents` is looked for at `<deployment>/agents-repo`, because
264
+ `<deployment>/agents/` is the instance root (above).
265
+ 4. None found → `E_CLONE_MISSING`, naming the three remedies. A directory that
266
+ *is* found but whose `origin` remote is a **different repo** →
267
+ `E_CLONE_MISMATCH` (the clone is not the member; nothing is spawned into it).
268
+
269
+ This order was documented before 0.25.2 but the kernel did not honour it (a
270
+ clone had to be `--repo`'d or sit at the convention); 0.25.2 implements it as
271
+ written here. If your host repo is named `agents`, clone it as
272
+ `<deployment>/agents-repo` or name it in `clones:`.
273
+
274
+ `settings.<cap>` is merged into that capability's payload after the soul's
275
+ slot payload and before `spawn --provider` (decision 14); the keys are the
276
+ capability's own (`oats.json#settings`). For **`oats.okf` 2.1.3** the binding
277
+ requires both `bindings-file` and `state-dir` as normalized absolute host
278
+ paths (`setting state-dir is required (absolute host path)` is a refusal, not a
279
+ default) and accepts `harvest-runtime` / `harvest-model`. For **`oats.aweb`**
280
+ the one machine-level key is `delivery`: `channel` (the native aweb channel
281
+ packages wake the instance; default) or `session` (delivery is external —
282
+ `AWEB_DELIVERY=session`, the host wake broker registers the instance once it
283
+ exists; requires an `aw` that ships `aw wake`). `identity.source` is also legal
284
+ here but see §8 for why it belongs at spawn.
285
+
151
286
  Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
152
287
  `oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
153
288
  `oats-config.yaml`; it is not read. Do not commit `oats-local.yaml`.
154
- (`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`
155
- and runs the first `sync` for you; add `settings:` afterwards.)
289
+ (`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`,
290
+ creates `agents/` and runs the first `sync` for you; add `settings:` afterwards.
291
+ Its `next.clone` list names **every** member that lacks a clone at the
292
+ convention — the host included: the host is a member like any other, and a
293
+ soul that lives in it and says `work: worktree` needs its clone too. Under an
294
+ explicit `standalone:` header the list says so and names only that repo.)
156
295
 
157
296
  ## 6. `oats sync`
158
297
 
@@ -162,11 +301,17 @@ From the deployment directory:
162
301
  oats sync
163
302
  ```
164
303
 
165
- It confirms every member (fix any `no-backlink` / `backlink-elsewhere` /
166
- `cannot-read` row before going on), resolves `packages:` to commits, writes
167
- `oats-lock.json` (lockfileVersion 3) and asks for executable approval once per
168
- package version. The 0.24 lock is not read; delete it (`E_LOCK_SCHEMA` names
169
- it if you leave it in the way).
304
+ It creates `agents/` if it is absent (0.25.2; a hand-written `oats-local.yaml`
305
+ no longer needs a `mkdir`), confirms every member (fix any `no-backlink` /
306
+ `backlink-elsewhere` / `cannot-read` row before going on), resolves `packages:`
307
+ to commits, writes `oats-lock.json` (lockfileVersion 3) and asks for executable
308
+ approval once per package version. The 0.24 lock is not read; delete it
309
+ (`E_LOCK_SCHEMA` names it if you leave it in the way).
310
+
311
+ The legacy "You run on OATS" block is no longer composed into `AGENTS.md` when
312
+ `oats.core` resolves as a module (0.25.2): an instance gets **one** such block,
313
+ the one `oats.core`'s inject carries. If you see two, the soul resolved without
314
+ `oats.core` (check `oats spawn <soul> --preview`).
170
315
 
171
316
  ## 7. Approve packages
172
317
 
@@ -174,10 +319,59 @@ Approval is **per package version, once, in the lock** — no `oats trust`, no
174
319
  per-capability approval, no per-operator trust list. `oats sync` on a terminal
175
320
  prints every executable (`commands.*` and `hooks.*.command` targets of every
176
321
  capability the package provides) and asks `approve <id> <version>? [y/N]`.
177
- Declined or non-interactive → exit `2`, the lock records the entry
178
- unapproved, and spawns of souls using it are refused (`E_PACKAGE_UNAPPROVED`)
179
- until you run `oats sync` in a terminal and say yes. Member capabilities need no
180
- approval: membership is the trust.
322
+ Declined, **Ctrl+D at the prompt**, or non-interactive → exit `2`, the lock
323
+ records the entry unapproved, and spawns of souls using it are refused
324
+ (`E_PACKAGE_UNAPPROVED`) until you run `oats sync` in a terminal and say yes.
325
+ Member capabilities need no approval: membership is the trust.
326
+
327
+ **Non-interactive approval (CI, scripted rebuilds):**
328
+
329
+ ```bash
330
+ oats sync --approve oats.okf@v2.1.3 --approve oats.aweb@v1.11.2
331
+ ```
332
+
333
+ `--approve <id>@<version>` is repeatable and approves **exactly** the entry the
334
+ resolution contains for that id and version — the executables digest is always
335
+ computed by `sync` over the fetched tree and recorded in the lock; you never
336
+ type a digest. An `--approve` that names an id or version the resolution does
337
+ not contain is an error, not a silent skip; an entry the flags do not cover
338
+ stays unapproved (exit `2`, as above).
339
+
340
+ ## 7b. OKF 2: start a FRESH `state-dir` — do not re-point the old one
341
+
342
+ OKF 2 pins each knowledge **owner** to a soul by path: at source registration
343
+ (the `oats.okf` spawn hook) it writes `owners.json` in `state-dir` as
344
+ `{ <owner id>: realpath(<home>/soul) }` and refuses a later registration whose
345
+ owner resolves to a different path (`E_OWNER stable owner ID already identifies
346
+ a different soul in this state namespace`).
347
+
348
+ Under v2 that path is no longer your checkout. `oats spawn` fetches the soul
349
+ from its member repo at the confirmed commit into the deployment's
350
+ **per-commit soul cache**, `agents/<name>/souls/<commit12>/` (immutable once
351
+ written; `agents/<name>/soul` is a kernel-swapped pointer to the current one),
352
+ and the instance's `<home>/soul` links **its own commit's directory** — so the
353
+ realpath the hook pins is `<deployment>/agents/<name>/souls/<commit12>`, which
354
+ never equals the 0.24 pin (`<repo>/agents/<name>/soul`) and changes whenever the
355
+ member moves. Two consequences:
356
+
357
+ - **Do not reuse the 0.24 `state-dir`.** Its `owners.json` pins every owner to
358
+ the old path; the first v2 spawn of each soul would be refused with `E_OWNER`.
359
+ Give the rebuilt deployment a fresh `state-dir` (§5) and a fresh
360
+ `bindings-file` if the old one names the old state root. The old `state-dir`
361
+ is **frozen custody**: read-only history (`oats okf inspect --source
362
+ <old-state>/sources/<id>/source.json …` still works against it), never edited,
363
+ never re-pointed at the new soul path. Accepted knowledge is not affected —
364
+ it lives in the bases, not in `state-dir`.
365
+ - **The owner pin is per commit.** OKF 2.1.3 records the realpath at first
366
+ registration and the kernel keeps that commit directory for as long as any
367
+ instance links it, so a running instance's pin stays valid; a *later* spawn of
368
+ the same soul at a newer member commit links a different directory and
369
+ registers under the same owner id → `E_OWNER` again. Until OKF re-bases the
370
+ pin on the owner identity rather than the path (an OKF 2.1.4 item), the
371
+ practical rule is: one `state-dir` per (deployment, soul commit) is safe;
372
+ moving a member that owns knowledge means a fresh `state-dir` for the new
373
+ commit's spawns (the previous one becomes frozen custody, as above). Plan
374
+ knowledge-owning souls' member commits deliberately.
181
375
 
182
376
  ## 8. Re-take a retained messaging seat with `spawn --provider`
183
377
 
@@ -186,32 +380,108 @@ In 0.24, an instance-specific messaging identity (a retained seat) was pinned in
186
380
  **spawn**:
187
381
 
188
382
  ```bash
189
- oats spawn release-manager --purpose seat --provider oats.aweb identity.source=retained:release-seat
383
+ oats spawn release-manager --purpose seat --provider oats.aweb identity.source=/abs/path/to/retained/.aw
190
384
  ```
191
385
 
192
386
  `--provider <cap> key=value` is repeatable; dotted keys nest. The payload is
193
387
  merged after the soul's `messaging:` and the machine's `settings.oats.aweb`, and
194
388
  recorded in `instance.json.providers.oats.aweb`, so exactly one instance holds
195
- the seat while other instances of the soul mint fresh identities. Consult your
196
- messaging capability's documentation for the exact key it reads. The Desktop's
389
+ the seat while other instances of the soul mint fresh identities.
390
+
391
+ **The value is the path itself.** `oats.aweb` reads `identity.source` as the
392
+ absolute path of the `.aw` directory to retain (it must hold `signing.key`); the
393
+ kernel does not resolve symbolic seat names. Because it is an absolute path it is
394
+ a fact about ONE machine, so its other legal home is `oats-local.yaml`
395
+ (`settings.oats.aweb.identity.source: /abs/path`) — never the workspace file
396
+ (absolute paths are refused there, decision 14). Prefer the spawn form: a
397
+ machine-level setting would give the seat to EVERY instance of every messaging
398
+ soul on that machine, and a seat can be held once. The Desktop's
197
399
  confirmed apply carries the same map.
198
400
 
401
+ ## 8b. Where the team `.aw` lives now (oats.aweb 1.11.2), and what `byTeam` does today
402
+
403
+ A freshly minted identity (every spawn without `identity.source`) needs an
404
+ **initialised aweb root**: a directory holding `.aw` with a team membership to
405
+ mint into. oats.aweb 1.11.2's spawn hook looks for `.aw` among these, first hit
406
+ wins: the declared team scope (`OATS_TEAM_SCOPE`, from the removed
407
+ `oats-config.yaml` `team:` block — **empty under v2**), the instance home, the
408
+ git repo containing the home, the resolution context (the soul's work repo) and
409
+ the git repo containing it, and the workspace root (`OATS_WORKSPACE`, which
410
+ under v2 is the **deployment directory** — the one holding `oats-local.yaml`).
411
+ None of these is the 0.24 team root you initialised with `oats aweb setup`, so
412
+ a rebuilt deployment mints nothing until you put `.aw` where the hook looks:
413
+
414
+ - **at the deployment directory** — `<deployment>/.aw`: one team for every
415
+ messaging soul spawned here; or
416
+ - **inside a member clone** (gitignored — add `.aw/` to the clone's
417
+ `.gitignore`; never commit `signing.key`): `<clone>/.aw` is found through the
418
+ soul's work repo, so souls whose `work:` targets *that* member mint into
419
+ *that* team.
420
+
421
+ `cp -R <old team root>/.aw <deployment>/.aw` (or into the clone) carries the
422
+ existing memberships over; `aw team list` from that directory shows the active
423
+ team. A `.aw` at your user home or above the deployment is **not** found on
424
+ purpose (a `.aw` there would be a different team; minting into it would be a
425
+ silent cross-team leak).
426
+
427
+ **Two teams, two identities — what actually decides the team in 1.11.2.** The
428
+ hook resolves the target team as: `OATS_TEAM_ID` / `OATS_TEAM_NAME` from the
429
+ removed `oats-config.yaml` `team:` block (empty under v2), else **the active
430
+ team at the `.aw` root it found**. It **does not read a `team` key from its
431
+ payload** (`OATS_SETTINGS`): the only payload keys 1.11.2 acts on are
432
+ `delivery` and `identity.source`/`identity.takeOver`. Consequently
433
+ `messaging.byTeam.<label>: { team: aweb:… }` is **kernel-merged and
434
+ delivered, but a NO-OP for oats.aweb 1.11.2** — the kernel does its part
435
+ (`spawn --preview` shows the merged `settings.oats.aweb` with the label's
436
+ `team`, and `instance.json.providers.oats.aweb` records it); the provider
437
+ ignores it until an oats.aweb release reads `team` from the payload. Until then
438
+ the only way to get per-label minting is **per-repo placement**: give each
439
+ team's member clone its own `.aw` whose active team is that team’s, and make
440
+ sure the souls of that team say `work: worktree | checkout` **on that repo**.
441
+ A soul with `work: directory | workspace` has no member clone as context and
442
+ falls through to `<deployment>/.aw` — one team only. Keep `byTeam` in the
443
+ workspace file anyway: it is the declared intent, the kernel honours it, and
444
+ the next oats.aweb picks it up without a workspace edit.
445
+
199
446
  ## 9. Spawn, and check drift
200
447
 
201
448
  ```bash
202
449
  oats souls # every non-private soul of every confirmed member, with origin and team
203
450
  oats capabilities # every capability, member (origin: member <key> @ <commit>) or package (package <id> v<ver>)
204
- oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision
451
+ oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision,
452
+ # providers (the --provider map as given) and settings.<cap> (the merged payload each provider receives)
205
453
  oats spawn <soul> --purpose x
206
- oats status # per instance: modules … [member moved since (now @ …)] / [capability no longer present]
454
+ oats status # per instance: soul: <name> from <member> @ <c7> [member moved since …]
455
+ # modules … [member moved since (now @ …)] / [capability no longer present]
207
456
  ```
208
457
 
458
+ `--preview` (0.25.2) prints `providers` — exactly the `--provider <cap> k=v`
459
+ map you gave — and `settings.<cap>` — the **merged** payload the provider's
460
+ binding will receive (`workspace.messaging` base ⊕ `byTeam[team]` ⊕ soul slot
461
+ payload ⊕ `oats-local.yaml settings.<cap>` ⊕ `--provider`), so you can see
462
+ before creating anything that `state-dir` is the fresh one (§7b) and that the
463
+ team block reached the payload (§8b). `oats status` (0.25.2) shows drift for
464
+ the **soul source** as well as for modules: `soul: <name> from <member> @ <c7>`
465
+ with `[member moved since …]` when the member's default branch has moved past
466
+ the commit the instance was spawned from; `--json` carries it as
467
+ `instances[].soul { repoKey, commit, current, status }`. A moved soul is
468
+ information, not a fault — the running instance keeps its own commit (§7b);
469
+ re-spawn when you want the new one.
470
+
471
+ **Work modes and clones.** `work: worktree | checkout` needs the member clone
472
+ (§5 order); `work: directory` needs nothing; `work: workspace` (a coordination
473
+ soul) links `./work` to the **deployment directory** — the one holding
474
+ `oats-local.yaml`, with `agents/` and whatever clones sit beside it — read-only
475
+ across members, no branch (0.25.1). Such a soul finds a member whose clone is
476
+ elsewhere through `oats-local.yaml` `clones:`.
477
+
209
478
  ## What disappears
210
479
 
211
480
  | Gone | Replaced by |
212
481
  |---|---|
213
482
  | `oats-config.yaml` (and the laptop/workspace/repo config chain, `agent-types`, `capabilities.layers`/`additive`, `souls:`, adopted config templates) | `oats-workspace.yaml` defaults + `soul.yaml` `capabilities:`; `oats-local.yaml` for host settings; `spawn --provider` for per-instance facts |
214
483
  | `oats.yaml` | `oats-membership.yaml` |
484
+ | `agents/<name>/soul/` as the tracked soul source | `souls/<name>/` (tracked); `agents/` is deployment state — instance homes and the kernel's per-commit soul cache `agents/<name>/souls/<commit12>/` |
215
485
  | `.agents/capabilities/installed/` and `owned/` | nothing is installed; `<instance>/.oats/modules/<cap>/` per instance; member capabilities under `<repo>/capabilities/` |
216
486
  | `oats init`, `oats use`, `oats install`, `oats restore`, `oats trust`, `oats list`, `oats catalog`, `oats remove`, `oats migrate`, `oats config` | `oats sync`, `oats package add \| remove`, `oats workspace status`, `oats capabilities`, `oats souls` — each removed verb answers `E_UNKNOWN_COMMAND` naming its replacement |
217
487
  | lock v1 / v2 | lock v3 (`packages` only, with `url`, `capabilities`, `approved`) |
@@ -0,0 +1,94 @@
1
+ # OATS v0.25.1 — workspace-model fix round
2
+
3
+ Kernel/Pi **0.25.1**. Tag `v0.25.1` → the commit carrying these notes; the
4
+ version-bump commit lands after the tag. Consumers gate on `oats version --json`
5
+ `features[]` names and API integers — never on the version.
6
+
7
+ **No API change.** `workspaceApi: 2`, every other API integer and the
8
+ `features[]` list are exactly those of [0.25.0](v0.25.0.md). Every item below
9
+ is a correctness, security or documentation fix found by the team review of
10
+ the 0.25.0 workspace model; the normative record is the "0.25.1 fix round"
11
+ section of `docs/design/2026-09-23-workspace-module-contracts.md` and the
12
+ matching clarifications in
13
+ `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`.
14
+
15
+ ## Kernel
16
+
17
+ - **M1 (high) — a running instance's soul no longer changes under it.** Souls
18
+ are fetched into a per-commit cache `agents/<name>/souls/<commit12>/`
19
+ (immutable); `agents/<name>/soul` is an atomically swapped pointer to the
20
+ current commit; each home links its own commit's directory. A 0.25.0 layout
21
+ is migrated in place on first use. OKF's path-pinned owner stays valid per
22
+ instance.
23
+ - **M2 — SSH remotes are fetched over SSH.** The canonical repo key is
24
+ unchanged; the fetch url honours the ref as written (`git@…`/`ssh://` → SSH,
25
+ `https://` → HTTPS, bare `git:` → HTTPS unless `remoteOptions.transport:
26
+ ssh`). A private repo is no longer probed over HTTPS and silently degraded to
27
+ the standalone view; the standalone fallback now reports the host failure
28
+ that triggered it.
29
+ - **M3 (high) — package approval is re-verified at spawn.** `resolveSoul`
30
+ recomputes the executables digest over the package tree at the locked commit
31
+ and refuses `E_PACKAGE_UNAPPROVED { reason: "digest-mismatch" }` when it
32
+ differs from the approved one; one shared `executablesDigestAt` serves
33
+ `sync` and `resolve`.
34
+ - **M4 — annotated tag OIDs are peeled.** `observeRemote` records the peeled
35
+ commit, never a tag object, in results, locks and `instance.json`.
36
+ - **B2 — `work: workspace` spawns on a v2 deployment.** `./work` is the
37
+ deployment directory (the one holding `oats-local.yaml`); no branch recorded;
38
+ the remedy names `oats-local.yaml`.
39
+ - **B3 — operator-level capability commands from the deployment.**
40
+ `oats <ns> <cmd> … --soul <name>` outside a home resolves exactly as a spawn
41
+ of that soul, fetches the module into `<deployment>/.oats/modules/<cap>@<commit12>/`
42
+ and dispatches there with the soul's merged payload (`oats okf init` before
43
+ any instance exists). `--soul` absent → `E_BAD_ARGS`; unknown namespace →
44
+ `E_UNKNOWN_COMMAND`.
45
+ - **L1 — slot `none` empties the slot.** A soul's `knowledge|messaging|tasks:
46
+ none` drops any layer-bearing capability the workspace defaults contributed
47
+ for that layer; only a layer-bearing capability the soul itself declares next
48
+ to `none` is `E_SLOT_CONFLICT`.
49
+ - **L2 — absolute-path refusal is scoped to ref/path fields.** Team
50
+ descriptions and the opaque messaging payload may contain `/`-rooted text.
51
+ - **L3 — one unsafe deep entry no longer blanks a member's souls.** Depth
52
+ filtering precedes the entry-name safety check.
53
+ - **L4 — listing failures are classified.** `maxBuffer` overflow is not
54
+ reported as `timeout`; an unclassified git listing failure becomes a
55
+ discovery problem row (`E_REMOTE_UNREADABLE { reason: "unknown" }`) instead
56
+ of an abort.
57
+ - **L6 — revision splits declarations from payload.** `revision =
58
+ hash(declRevision, payloadRevision)`; decision binding unchanged; preview can
59
+ report `changed since: declarations | payload | both`.
60
+
61
+ ## Documentation
62
+
63
+ - **M5 — rebuild guide gaps closed** (`docs/rebuild-to-v2.md`): the tracked
64
+ `git mv agents/<name>/soul souls/<name>` step and the tests that enumerate
65
+ soul paths; OKF 2 owner re-registration (fresh `state-dir` for a rebuilt
66
+ deployment, the old one frozen custody); `oats-local.yaml` example with
67
+ `settings.oats.aweb.delivery` and `settings.oats.okf` `state-dir` +
68
+ `bindings-file`; unlabelled souls receive the messaging base payload only;
69
+ per-soul memory-harvest opt-out is not available in OKF 2.1.3 (an OKF 2.1.4
70
+ item).
71
+ - **L7 — decision 13 reach.** Every `pi` launch a 0.25 kernel performs starts
72
+ the harness normally, classic 0.24 homes included (rebuild guide §0,
73
+ `conventions.md`). `oats session recompose` is `E_UNSUPPORTED_MODE` for
74
+ module homes; `session-recompose` stays advertised for classic homes
75
+ (`desktop-cli-api.md`).
76
+ - **L8 — v1 no longer presented as live** in `schedules.md`, `conventions.md`,
77
+ `implementation.md`, `execution-targets.md`, `desktop.md`,
78
+ `desktop-succession.md`, `integrations.md`, `migration-from-oas.md`
79
+ (a 0.24.x procedure), `desktop-cli-api.md` (readiness producers are the 0.24
80
+ tier), `knowledge-migration.md` (`state-dir` is required; four settings) and
81
+ `knowledge.md` (operator-level `oats okf … --soul <x>` from the deployment).
82
+
83
+ ## Known follow-ups (unchanged from 0.25.0 unless noted)
84
+
85
+ - The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose;
86
+ `composeInstance` still reads `yolo` / `launch-configs` from an
87
+ `oats-config.yaml` chain when one sits above a deployment.
88
+ - The readiness quartet (`readinessApi: 1`) is still produced by the 0.24 tier
89
+ observers; re-basing it on `spawn --preview` / `sync` / `workspace status` is
90
+ a named follow-up.
91
+ - OKF 2's owner pin is per soul path (hence per commit under M1); re-basing it
92
+ on the owner identity is an OKF 2.1.4 item, as is a per-soul harvest opt-out.
93
+ - `oats-local.yaml` `transport:` (M2's per-machine SSH default) needs a schema
94
+ addition before it can be written.
@@ -0,0 +1,80 @@
1
+ # OATS v0.25.2 — operator-rebuild round
2
+
3
+ Kernel/Pi **0.25.2**. Tag `v0.25.2` → the commit carrying these notes; the
4
+ version-bump commit lands after the tag. Consumers gate on `oats version --json`
5
+ `features[]` names and API integers — never on the version.
6
+
7
+ **No API change.** `workspaceApi: 2`, every other API integer and the
8
+ `features[]` list are exactly those of [0.25.1](v0.25.1.md). The only surface
9
+ additions are **additive fields**: `oats spawn --preview` gains `providers` and
10
+ `settings`, `oats status --json` gains `instances[].soul`; `oats sync` gains
11
+ the `--approve` flag. Every item below comes from an operator's first rebuild
12
+ of a real two-team deployment on 0.25.0, following `docs/rebuild-to-v2.md`
13
+ literally — the guide is a contract the kernel honours. Normative record:
14
+ "0.25.2 operator-rebuild round" in
15
+ `docs/design/2026-09-23-workspace-module-contracts.md` and the matching
16
+ clarifications in
17
+ `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`.
18
+
19
+ ## Kernel
20
+
21
+ - **R1 — member clones are found as the guide says.** For a `work: worktree |
22
+ checkout` soul: `--repo`, then `oats-local.yaml` `clones:` (keys normalised
23
+ through `parseRepoRef`), then `<deployment>/<member name>` (a member named
24
+ `agents` → `agents-repo`); none → `E_CLONE_MISSING` naming the three
25
+ remedies; a directory whose remotes name another repo → `E_CLONE_MISMATCH`.
26
+ - **R2 — `oats sync` creates `<deployment>/agents/` when absent.**
27
+ - **R3 — one "You run on OATS" block.** With `oats.core` resolved as a module
28
+ the kernel's legacy block is suppressed; the module's inject is the briefing.
29
+ - **R4 — `oats status` shows soul-source drift.** `soul: <name> from <member>
30
+ @ <c7> [member moved since …]` beside the module rows; `--json`
31
+ `instances[].soul { repoKey, commit, current, status }`.
32
+ - **R5 — `spawn --preview` shows the payload.** `providers` (the `--provider`
33
+ map as given) and `settings.<cap>` (the merged payload each provider
34
+ receives).
35
+ - **R9 — non-interactive approval.** `oats sync --approve <id>@<version>`
36
+ (repeatable) approves exactly the locked entry at that version; the digest is
37
+ always computed, never typed; an id/version not in the resolution is
38
+ `E_BAD_ARGS`. Ctrl+D at the interactive prompt is a decline (exit `2`).
39
+ - **R10 — `oats onboard` lists the host** among the clones to make, like any
40
+ member; an explicit `standalone:` header is named as such in the next steps.
41
+
42
+ ## Documentation
43
+
44
+ - **R6** — the rebuild guide's work-mode section states that a coordination
45
+ soul's (`work: workspace`) `./work` is the deployment directory (0.25.1 B2).
46
+ - **R7 — where the team `.aw` lives now.** Rebuild guide §8b: oats.aweb 1.11.2
47
+ finds `.aw` among the home, the home's git repo, the soul's work repo and the
48
+ deployment directory — never the 0.24 team root; place it inside the member
49
+ clone (gitignored) or at the deployment directory. **`messaging.byTeam` is
50
+ kernel-merged but a no-op for oats.aweb 1.11.2**, which does not read `team`
51
+ from its payload; per-repo `.aw` placement is the working alternative
52
+ (`workspaces.md` byTeam paragraph updated accordingly).
53
+ - **R8 — OKF 2.1.3 reads `okf.json`, not a soul payload.** The guide, `workspaces.md`,
54
+ `souls-and-instances.md` and `knowledge.md` no longer show `knowledge: { owns,
55
+ reads }` / `{ store, root }` on `soul.yaml`: the soul's declaration is
56
+ `souls/<name>/okf.json` (`version`, `owner`, `owns`, `reads`); the `knowledge:`
57
+ payload may carry only the binding's settings (`bindings-file`, `state-dir`,
58
+ `harvest-runtime`, `harvest-model`); a base's `root` is the bindings file's.
59
+ Decision 24's example is corrected. §7b (fresh `state-dir`) confirmed.
60
+ - `configuration.md` `clones` row states the R1 order; `workspaces.md` and
61
+ `souls-and-instances.md` describe the R4/R5 fields and the per-commit soul
62
+ cache paths.
63
+
64
+ ## Provider follow-ups (not kernel)
65
+
66
+ - **oats.aweb follow-up:** read `team` from the spawn payload (so
67
+ `messaging.byTeam` yields per-label identities) and accept the deployment
68
+ directory as a first-class aweb root. Until that release, 1.11.2 behaves as
69
+ §8b describes.
70
+ - **oats.okf follow-up:** a soul-payload grammar (`owns`/`reads` on
71
+ `soul.yaml`), declared in the binding when it lands; owner pin re-based on
72
+ identity rather than path, and per-soul harvest opt-out (2.1.4 items, unchanged).
73
+
74
+ ## Known follow-ups (unchanged from 0.25.1)
75
+
76
+ - The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose.
77
+ - The readiness quartet (`readinessApi: 1`) is still produced by the 0.24 tier
78
+ observers.
79
+ - `oats-local.yaml` `transport:` needs a schema addition before it can be
80
+ written.
package/docs/schedules.md CHANGED
@@ -1,12 +1,18 @@
1
1
  # Schedules
2
2
 
3
3
  A schedule launches an agent, runs an oats command, or wakes an existing
4
- instance on a cron. Definitions belong to a scope, the team workspace (the
5
- config level that declares the team, else the outermost `oats-config.yaml`
6
- level), and are committable; every `oats schedule` command run anywhere
7
- inside that scope, including from an instance home, reads and writes the
8
- same file. Execution belongs to the host that holds the scope, so a
9
- schedule on a registered server keeps running while your laptop sleeps.
4
+ instance on a cron. Definitions belong to a scope and are committable; every
5
+ `oats schedule` command run anywhere inside that scope, including from an
6
+ instance home, reads and writes the same file. On a **workspace deployment**
7
+ (0.25, [workspaces.md](workspaces.md)) the scope is the deployment directory
8
+ — the one holding `oats-local.yaml` and the `agents/` root (the kernel derives
9
+ it as the directory above the agents root; a leftover `oats-config.yaml` that
10
+ declares `team:` would still win, so remove it); scheduled spawns there
11
+ materialize exactly like `oats spawn`. On a classic 0.24 deployment the scope
12
+ is the team workspace (the config level that declares the team, else the
13
+ outermost `oats-config.yaml` level). Execution belongs to the host that holds
14
+ the scope, so a schedule on a registered server keeps running while your laptop
15
+ sleeps.
10
16
 
11
17
  There is no daemon. One host timer (a launchd user agent on macOS, a systemd
12
18
  user timer on Linux) runs `oats schedule tick --host` once a minute; the tick