@awebai/oats 0.25.1 → 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.
@@ -748,7 +748,7 @@ Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
748
748
  `oats onboard [<dir>] --workspace <repo ref> [--json]`
749
749
 
750
750
  The **bootstrap** of a deployment (decision 9): realizes a workspace on this
751
- machine in the taught `<name>-workspace/` layout. It writes
751
+ machine in the directory the operator chooses (any existing folder). It writes
752
752
  `<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
753
753
  `<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
754
754
  over the directory just written — discover over the remotes, confirm
package/docs/desktop.md CHANGED
@@ -86,7 +86,7 @@ The app starts on the directory it was launched with (its own folder by
86
86
  default). To view a deployment, open the workspace switcher in the sidebar
87
87
  and choose **Add workspace → Browse**, then point it at an OATS deployment —
88
88
  a directory containing `agents/` (under the 0.25 workspace model that is the
89
- `<name>-workspace/` directory holding `oats-local.yaml` and `agents/`;
89
+ deployment directory (the operator's choice) holding `oats-local.yaml` and `agents/`;
90
90
  under 0.24, an `agents/` root, a `local-agents/` root for machine-local souls,
91
91
  or a team scope whose `oats-config.yaml` declares `team:`). *The Desktop's own
92
92
  multi-repo roster ("team scopes show every member repo's agents under one
@@ -56,15 +56,15 @@ one soul and, optionally, one capability. Every soul gets `oats.core` from the
56
56
  the resulting layout).
57
57
 
58
58
  ```bash
59
- oats onboard ~/acme-workspace --workspace git:github.com/acme/agents
59
+ oats onboard ~/acme --workspace git:github.com/acme/agents # any directory — an existing one with your clones is fine
60
60
  ```
61
61
 
62
62
  ```
63
- ~/acme-workspace/ # the taught "<name>-workspace" convention
63
+ ~/acme/ # the directory you chose; these three entries are what the kernel needs
64
64
  ├── oats-local.yaml # { schemaVersion: 2, workspace: git:github.com/acme/agents }
65
65
  ├── oats-lock.json # lockfileVersion 3: commit + integrity + approval per package
66
66
  ├── agents/ # instance homes
67
- └── <member>/ # clones of the members you work IN (printed as next steps)
67
+ └── <member>/ # clones of the members you work IN — here or anywhere named in oats-local.yaml clones:
68
68
  ```
69
69
 
70
70
  Read the report it prints: every member row must be `✓↔` (confirmed) — fix
package/docs/knowledge.md CHANGED
@@ -66,9 +66,11 @@ packages:
66
66
  defaults:
67
67
  knowledge: { oats.okf: { from: package } }
68
68
 
69
- # souls/domain-expert/soul.yaml
69
+ # souls/domain-expert/soul.yaml — nothing under knowledge: for oats.okf; the default fills the slot.
70
+ # What the soul owns/reads is souls/domain-expert/okf.json (below), not a soul.yaml payload.
71
+ # A soul-true binding setting is the one thing the payload may carry, e.g.:
70
72
  knowledge:
71
- owns: domain-expert
73
+ harvest-runtime: claude
72
74
 
73
75
  # oats-local.yaml (this machine)
74
76
  settings:
@@ -79,11 +81,14 @@ settings:
79
81
 
80
82
  ```bash
81
83
  oats sync # resolves v2.1.3 to a commit, asks executable approval once
82
- oats spawn domain-expert --preview --json # the exact oats.okf module (package, version, commit)
84
+ oats spawn domain-expert --preview --json # the exact oats.okf module (package, version, commit) + settings.oats.okf (the merged payload)
83
85
  ```
84
86
 
85
- Pinning activates nothing by itself: the soul's `knowledge:` payload and the
86
- machine's `settings.oats.okf` must be bindable. The lock stays exact until the
87
+ Pinning activates nothing by itself: the soul's `okf.json` must exist and the
88
+ merged payload (soul `knowledge:` ⊕ `settings.oats.okf` ⊕ `--provider`) must be
89
+ bindable — it may carry **only** the four settings below (`bindings-file`,
90
+ `state-dir`, `harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` on the
91
+ soul payload are refused by 2.1.3, not read. The lock stays exact until the
87
92
  workspace bumps `packages.oats.okf`; v1 operators must plan migration before
88
93
  that bump. Executable changes come with a new version and a new approval. A
89
94
  service worker need not itself fill the knowledge slot (`knowledge: none`).
@@ -129,8 +134,10 @@ directory, not the current working directory:
129
134
  source homes/worktrees; keep the bindings file outside state and bases. Local
130
135
  Git locators must not be disposable linked worktrees. Directory lock/journal
131
136
  artifacts also must not overlap state, sources or another base.
132
- - Settings are `bindings-file`, `harvest-runtime` (`pi`, `claude`, `codex`,
133
- default `pi`), and optional `harvest-model`. Choose an installed, authenticated
137
+ - Settings are `bindings-file`, `state-dir` (both required, absolute host
138
+ paths), `harvest-runtime` (`pi`, `claude`, `codex`, default `pi`), and
139
+ optional `harvest-model` — the complete list a 2.1.3 payload may carry.
140
+ Choose an installed, authenticated
134
141
  worker runtime independently of the source; omitted models use that runtime's
135
142
  configured default. V1 record-window settings are not v2 settings.
136
143
 
@@ -48,6 +48,8 @@ capabilities plus `oats.core`), so a public soul stays usable.
48
48
  Two teams that need two different messaging identities (an open-source team
49
49
  and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
50
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.
51
53
 
52
54
  ## 2. Write `oats-workspace.yaml` v2 in the host repo
53
55
 
@@ -141,7 +143,7 @@ they enumerate `souls/*/soul.yaml`. A soul left under `agents/` is invisible to
141
143
  | `stores.inherit` | delete (stores are declared once in the workspace) |
142
144
  | `imports` | delete |
143
145
  | `kind`, `type`, `repo`, `runtime`, `model`, `launch-config` | delete — model/runtime/launch config are spawn-time choices; `team:` replaces `type:` as the grouping |
144
- | `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`** |
145
147
  | — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
146
148
 
147
149
  ```yaml
@@ -152,9 +154,8 @@ work: worktree
152
154
  team: engineering
153
155
  capabilities:
154
156
  acme-release-tooling: { from: here }
155
- knowledge:
156
- owns: release-manager
157
- 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.
158
159
  messaging:
159
160
  channels: [acme-eng]
160
161
  ```
@@ -164,6 +165,48 @@ are required. Capabilities the repo exports live at
164
165
  `capabilities/<name>/oats.json` — the manifest is unchanged; you may add
165
166
  `private: true` / `team:`.
166
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
+
167
210
  **Carry `team:` on every soul, or on its repo's membership.** A soul's team is
168
211
  `soul.yaml#team`, else `oats-membership.yaml#team`, else *unassigned*
169
212
  (`null`). Labels never gate anything, but the kernel addresses provider payload
@@ -175,25 +218,27 @@ outside every team-addressed payload; nothing refuses it. Label the membership
175
218
  when a whole repo belongs to one team, and the soul when it does not.
176
219
 
177
220
  **Per-soul memory-harvest opt-out:** not available in OKF 2.1.3 — an OKF 2.1.4
178
- item. The 2.1.3 `knowledge:` payload admits `owner`, `owns`, `reads` and
179
- `stores` (plus the kernel-rendered `runtime`/`execution`); there is no key that
180
- keeps a soul registered for reads while excluding it from harvest. A soul that
181
- must not be harvested today says `knowledge: none` (no OKF at all for that
182
- soul) or `oats.okf: off`; do not invent a key — the binding refuses unknown
183
- payload keys.
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.
184
227
 
185
228
  ## 5. Write `oats-local.yaml` on each machine
186
229
 
187
230
  ```
188
- ~/acme-workspace/ # the taught convention: "<name>-workspace"
231
+ ~/acme/ # the directory YOU choose — an existing folder with your clones is the usual case
189
232
  ├── oats-local.yaml
190
- ├── agents/ # instance homes
191
- └── 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:)
192
235
  ```
193
236
 
194
237
  ```yaml
195
238
  schemaVersion: 2
196
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
197
242
  settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
198
243
  oats.okf:
199
244
  bindings-file: /Users/ana/.oats/okf-bindings.json # required by the OKF binding: absolute host path
@@ -205,6 +250,27 @@ souls:
205
250
  disabled: [data-analyst]
206
251
  ```
207
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
+
208
274
  `settings.<cap>` is merged into that capability's payload after the soul's
209
275
  slot payload and before `spawn --provider` (decision 14); the keys are the
210
276
  capability's own (`oats.json#settings`). For **`oats.okf` 2.1.3** the binding
@@ -220,8 +286,12 @@ here but see §8 for why it belongs at spawn.
220
286
  Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
221
287
  `oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
222
288
  `oats-config.yaml`; it is not read. Do not commit `oats-local.yaml`.
223
- (`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`
224
- 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.)
225
295
 
226
296
  ## 6. `oats sync`
227
297
 
@@ -231,11 +301,17 @@ From the deployment directory:
231
301
  oats sync
232
302
  ```
233
303
 
234
- It confirms every member (fix any `no-backlink` / `backlink-elsewhere` /
235
- `cannot-read` row before going on), resolves `packages:` to commits, writes
236
- `oats-lock.json` (lockfileVersion 3) and asks for executable approval once per
237
- package version. The 0.24 lock is not read; delete it (`E_LOCK_SCHEMA` names
238
- 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`).
239
315
 
240
316
  ## 7. Approve packages
241
317
 
@@ -243,10 +319,23 @@ Approval is **per package version, once, in the lock** — no `oats trust`, no
243
319
  per-capability approval, no per-operator trust list. `oats sync` on a terminal
244
320
  prints every executable (`commands.*` and `hooks.*.command` targets of every
245
321
  capability the package provides) and asks `approve <id> <version>? [y/N]`.
246
- Declined or non-interactive → exit `2`, the lock records the entry
247
- unapproved, and spawns of souls using it are refused (`E_PACKAGE_UNAPPROVED`)
248
- until you run `oats sync` in a terminal and say yes. Member capabilities need no
249
- 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).
250
339
 
251
340
  ## 7b. OKF 2: start a FRESH `state-dir` — do not re-point the old one
252
341
 
@@ -309,16 +398,83 @@ machine-level setting would give the seat to EVERY instance of every messaging
309
398
  soul on that machine, and a seat can be held once. The Desktop's
310
399
  confirmed apply carries the same map.
311
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
+
312
446
  ## 9. Spawn, and check drift
313
447
 
314
448
  ```bash
315
449
  oats souls # every non-private soul of every confirmed member, with origin and team
316
450
  oats capabilities # every capability, member (origin: member <key> @ <commit>) or package (package <id> v<ver>)
317
- 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)
318
453
  oats spawn <soul> --purpose x
319
- 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]
320
456
  ```
321
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
+
322
478
  ## What disappears
323
479
 
324
480
  | Gone | Replaced by |
@@ -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.
@@ -25,10 +25,12 @@ A soul is durable and committed. It is the part you review, improve, and keep.
25
25
  AGENTS.md # canonical operating doc
26
26
  CLAUDE.md → AGENTS.md
27
27
  skills/ # skills specific to this expert
28
+ okf.json # if the soul uses oats.okf: { version: 1, owner, owns: ["<base>/<node>"], reads: […] } — the provider's, not the kernel's
28
29
  ```
29
30
 
30
- (A deployment's own `agents/<name>/soul/` has the same shape; a member soul is
31
- fetched there at its discovered commit before its first spawn.)
31
+ (A deployment's `agents/<name>/souls/<commit12>/` has the same shape; a member
32
+ soul is fetched there at its discovered commit before its first spawn, and
33
+ `agents/<name>/soul` points at the current commit.)
32
34
 
33
35
  ### `soul.yaml` v2
34
36
 
@@ -47,8 +49,8 @@ capabilities: # WHERE each capability comes from —
47
49
  acme-house-style: off # removes a workspace/team default
48
50
 
49
51
  knowledge: # provider payloads — opaque to the kernel, consumed by the slot's capability
50
- owns: release-manager
51
- reads: [platform-engineer]
52
+ harvest-runtime: claude # (oats.okf 2.1.3 reads only its binding's settings keys here; what the soul
53
+ # owns/reads is in this directory's okf.json — see "Soul anatomy")
52
54
  messaging:
53
55
  channels: [acme-eng]
54
56
  tasks: none # `none` empties the slot (drops the workspace default)
@@ -63,7 +65,7 @@ compatibility: # optional floors on PACKAGE versions
63
65
  | `team` | A label declared in the workspace's `teams:`; may add `defaults.byTeam` capabilities. Never gates or restricts. |
64
66
  | `private` | `true` keeps the soul out of workspace discovery; its own repo can still spawn it. |
65
67
  | `capabilities` | `<cap>: { from: here \| <repo key> \| package }` or `<cap>: off`. Composed over `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[team]`; the soul wins. |
66
- | `knowledge` / `messaging` / `tasks` | The slot's provider payload (true of every instance of the soul), or `none`. Merged with `oats-local.yaml` `settings.<cap>` and `spawn --provider <cap>`; the provider's `binding` contract validates the result. |
68
+ | `knowledge` / `messaging` / `tasks` | The slot's provider payload (true of every instance of the soul), or `none`. Merged with `oats-local.yaml` `settings.<cap>` and `spawn --provider <cap>`; the provider's `binding` contract validates the result — and refuses keys it does not declare. For `oats.okf` 2.1.3 the admitted keys are its settings (`bindings-file`, `state-dir`, `harvest-runtime`, `harvest-model`); the soul's `owns`/`reads` live in `souls/<name>/okf.json`, which OKF reads from the soul directory. |
67
69
  | `compatibility` | `<cap>: <semver range>` checked against the locked package version (`E_COMPATIBILITY`). |
68
70
 
69
71
  Schema: [`soul.schema.json`](soul.schema.json). Not in v2: `kind`, `type`,
@@ -82,7 +84,9 @@ souls — is ordinary capability content: **`oats.core`** (package
82
84
  `defaults.capabilities: { oats.core: { from: package } }`; a soul may say
83
85
  `oats.core: off`. **`oats.setup`** (same package) carries the whole-architecture
84
86
  knowledge an onboarding expert needs. Neither is kernel magic; the kernel still
85
- composes its own instance-boundary and work-mode briefings.
87
+ composes its own instance-boundary and work-mode briefings — and, when
88
+ `oats.core` resolves as a module, leaves the "You run on OATS" briefing to the
89
+ module's inject (one block, not two; 0.25.2).
86
90
 
87
91
  ## Instance anatomy
88
92
 
@@ -134,7 +138,7 @@ skills and instructions), a workspace spawn records:
134
138
  }
135
139
  },
136
140
  "providers": {
137
- "oats.okf": { "owns": "release-manager", "reads": ["platform-engineer"], "state-dir": "/Users/ana/.oats/okf" },
141
+ "oats.okf": { "bindings-file": "/Users/ana/.oats/okf-bindings.json", "state-dir": "/Users/ana/.oats/okf", "harvest-runtime": "claude" },
138
142
  "acme-release-tooling": {}
139
143
  },
140
144
  "workspace": {
@@ -149,9 +153,13 @@ skills and instructions), a workspace spawn records:
149
153
  shows `moved` / `missing` per module (drift is shown, not prevented).
150
154
  - `providers.<cap>` — the merged provider payload the capability was bound with
151
155
  (soul ⊕ machine settings ⊕ `--provider`), so a later inspection can tell
152
- which instance holds a retained seat or a one-off state root.
156
+ which instance holds a retained seat or a one-off state root. `spawn
157
+ --preview` shows the same map before anything exists, as `settings.<cap>`,
158
+ beside `providers` (the `--provider` flags as given).
153
159
  - `workspace` — the workspace commit observed at spawn, the soul's repo/commit/
154
- team, and the **resolution revision** the spawn decision bound.
160
+ team, and the **resolution revision** the spawn decision bound. `oats status`
161
+ compares `workspace.soul` with the member's current commit too: `soul: <name>
162
+ from <member> @ <c7> [member moved since …]` (`--json`: `instances[].soul`).
155
163
 
156
164
  A running instance never changes under itself: a member moving or a package
157
165
  bump affects only new spawns.
@@ -183,7 +191,9 @@ From a deployment (where `oats-local.yaml` is), a spawn: reads the local file
183
191
  discovers the workspace over its remotes and confirms membership → finds the
184
192
  soul among the confirmed members (or `external:`; an ambiguous bare name is
185
193
  `E_SOUL_AMBIGUOUS` — say `<repo>/<soul>`) → fetches the soul's source into
186
- `<agents-root>/<soul>/soul/` at its commit → resolves every capability by
194
+ `<agents-root>/<soul>/souls/<commit12>/` at its commit (the home links that
195
+ directory; `<agents-root>/<soul>/soul` points at the current one) → resolves
196
+ every capability by
187
197
  `from:` (member = latest, package = locked + approved) → creates the home →
188
198
  **materializes each module whole** into `.oats/modules/` and copies its skills
189
199
  into `.agents/skills/` (a transaction: any failure leaves nothing behind) →
@@ -194,8 +204,10 @@ unchanged. This is a normal agent process with its own home and tools, not a
194
204
  subagent call.
195
205
 
196
206
  `--preview` reports `modules[]` (`from`, `layer`, `changedSince` the newest
197
- previous instance of the soul), `team`, the `resolution` revision and the
198
- decision it would bind; the apply refuses with `E_DECISION_STALE` if a member
207
+ previous instance of the soul), `team`, the `resolution` revision, the decision
208
+ it would bind, `providers` (the `--provider` map exactly as given) and
209
+ `settings.<cap>` (the merged payload each provider's binding will receive);
210
+ the apply refuses with `E_DECISION_STALE` if a member
199
211
  moved in between. `--provider <cap> key=value` (repeatable; dotted keys nest)
200
212
  must name a capability the soul resolves (`E_CAPABILITY_MISSING` otherwise) and
201
213
  needs a workspace deployment. The full DTOs are in
@@ -324,7 +336,10 @@ directory is for, not a place to settle in.
324
336
  ### `worktree` — isolated branch
325
337
 
326
338
  `work/` is a git worktree on the instance's own branch, by default
327
- `agents/<instance>`.
339
+ `agents/<instance>`. The worktree is created from the member's **clone**, found
340
+ as `--repo`, then `oats-local.yaml` `clones:`, then `<deployment>/<member name>`
341
+ (`E_CLONE_MISSING` / `E_CLONE_MISMATCH` otherwise — see
342
+ [configuration.md](configuration.md)).
328
343
 
329
344
  Use this for agents that will edit code or docs independently.
330
345
 
@@ -341,7 +356,8 @@ inside each fresh worktree. Failures warn but do not block spawn.
341
356
 
342
357
  ### `checkout` — shared current branch
343
358
 
344
- `work/` is a symlink to the repo checkout itself.
359
+ `work/` is a symlink to the repo checkout itself (the member clone, found as for
360
+ `worktree`).
345
361
 
346
362
  Use this for maintainers, coordinators, auditors, or agents working on the
347
363
  repo's current state.
@@ -382,8 +398,10 @@ exchanged for a symlink. Recovery does not replace the worker's delivery protoco
382
398
 
383
399
  ### `workspace` — cross-repo coordinator
384
400
 
385
- `work/` is a symlink to the **whole deployment** (the `<name>-workspace/`
386
- directory holding `oats-local.yaml` and the member clones) — not a repo. Every
401
+ `work/` is a symlink to the **whole deployment** — the directory holding
402
+ `oats-local.yaml`, with `agents/` and the member clones that sit beside it —
403
+ not a repo (0.25.1; a member cloned elsewhere is reached through
404
+ `oats-local.yaml` `clones:`). Every
387
405
  member repo is read-context; the instance's product is coordination:
388
406
  routing, analysis, task-writing, messaging, spawning specialists.
389
407