@awebai/oats 0.24.13 → 0.25.1

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.
Files changed (58) hide show
  1. package/bin/oats.mjs +994 -2837
  2. package/docs/capabilities.md +136 -323
  3. package/docs/configuration.md +68 -533
  4. package/docs/conventions.md +51 -24
  5. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
  6. package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
  7. package/docs/design/2026-09-16-portable-onboarding.md +4 -2
  8. package/docs/design/2026-09-20-redesign-program-board.md +1 -1
  9. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
  10. package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
  11. package/docs/design/2026-09-23-workspace-module-contracts.md +460 -0
  12. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
  13. package/docs/design/README.md +20 -8
  14. package/docs/design/operations-contract.md +1 -0
  15. package/docs/design/package-engine-contract.md +1 -1
  16. package/docs/design/package-runtime-api.md +1 -1
  17. package/docs/desktop-cli-api.md +356 -5
  18. package/docs/desktop-succession.md +12 -6
  19. package/docs/desktop.md +9 -4
  20. package/docs/execution-targets.md +16 -4
  21. package/docs/first-team.md +107 -224
  22. package/docs/implementation.md +41 -11
  23. package/docs/integrations.md +50 -47
  24. package/docs/knowledge-capability-authoring.md +10 -4
  25. package/docs/knowledge-migration.md +21 -12
  26. package/docs/knowledge-reference/package-craft.md +11 -3
  27. package/docs/knowledge.md +60 -18
  28. package/docs/layers.md +3 -3
  29. package/docs/migration-from-oas.md +20 -9
  30. package/docs/oats-local.schema.json +50 -0
  31. package/docs/oats-membership.schema.json +23 -0
  32. package/docs/oats-workspace.schema.json +133 -48
  33. package/docs/official-marketplace.md +9 -6
  34. package/docs/packages.md +229 -440
  35. package/docs/rebuild-to-v2.md +347 -0
  36. package/docs/release-notes/v0.25.0.md +99 -0
  37. package/docs/release-notes/v0.25.1.md +94 -0
  38. package/docs/schedules.md +12 -6
  39. package/docs/soul.schema.json +41 -68
  40. package/docs/souls-and-instances.md +175 -108
  41. package/docs/workspace-adoption.md +70 -345
  42. package/docs/workspaces.md +436 -119
  43. package/lib/core.mjs +462 -61
  44. package/lib/instance-resolution.mjs +387 -0
  45. package/lib/materialize.mjs +580 -0
  46. package/lib/operator-dispatch.mjs +117 -0
  47. package/lib/packages.mjs +558 -1269
  48. package/lib/remote.mjs +718 -0
  49. package/lib/resolve.mjs +638 -0
  50. package/lib/schedule.mjs +90 -16
  51. package/lib/workspace.mjs +654 -0
  52. package/package.json +1 -1
  53. package/lib/portable-migration-artifacts.mjs +0 -135
  54. package/lib/portable-migration-evidence.mjs +0 -305
  55. package/lib/portable-migration-store.mjs +0 -199
  56. package/lib/portable-migration.mjs +0 -104
  57. package/lib/portable-onboarding-acceptance.mjs +0 -66
  58. package/lib/setup-expert-source.mjs +0 -100
@@ -0,0 +1,347 @@
1
+ # Rebuilding a 0.24.x deployment for the workspace model (0.25)
2
+
3
+ The workspace model ([workspaces.md](workspaces.md)) is a **clean v2**: no
4
+ converter, no dual-schema reader, no `oats migrate`. This guide is what ships
5
+ instead (decision 15 of `workspace-model-v2`). It is short because the new
6
+ surface is small: three shared files, one local file, one command.
7
+
8
+ ## 0. 0.24.x keeps working
9
+
10
+ A 0.24.x kernel keeps spawning 0.24.x deployments indefinitely. Nothing forces
11
+ the move: install 0.25 when you are ready to rebuild, not before. A 0.25 kernel
12
+ reads only v2 files — a 0.24 `oats-workspace.yaml` (`schemaVersion: 1`), a
13
+ `soul.yaml` with `requires:`/`source:`, an `oats.yaml`, an `oats-config.yaml` or
14
+ a lock v1/v2 is an error **naming the schema** (`E_WORKSPACE_SCHEMA "… reads
15
+ schemaVersion 2 only; found 1"`, `E_LOCK_SCHEMA`), never a silent fallback.
16
+ Keep the 0.24 kernel installed until the last 0.24 deployment you care about is
17
+ rebuilt; the two do not share files.
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
+
32
+ ## 1. Decide the one workspace
33
+
34
+ One workspace per organisation. Pick the repo that **hosts**
35
+ `oats-workspace.yaml` (a dedicated `agents` repo is common; any member can host
36
+ it). Decide the team labels you want (`global`, `engineering`, …) — labels
37
+ organise and may add defaults; they never gate anything.
38
+
39
+ **If any member is private, host the workspace file in a private repo that is
40
+ not a public member.** The workspace file names every member, so whoever can
41
+ read it sees the member list: a public host would publish the private repo's
42
+ name; hosting inside the private member hides the workspace from public
43
+ contributors entirely. A dedicated private repo (`<org>/workspace`) is the
44
+ honest shape. Public contributors who can read a public member but not the
45
+ host still get that member's souls through the standalone case (`from: here`
46
+ capabilities plus `oats.core`), so a public soul stays usable.
47
+
48
+ Two teams that need two different messaging identities (an open-source team
49
+ and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
50
+ and the provider payload is addressed by label under `messaging.byTeam` (§2).
51
+
52
+ ## 2. Write `oats-workspace.yaml` v2 in the host repo
53
+
54
+ Start from the 0.24 file and rewrite it:
55
+
56
+ | 0.24 | v2 |
57
+ |---|---|
58
+ | `schemaVersion: 1` | `schemaVersion: 2` |
59
+ | `members: [{ source: git:… }]` | `members: [git:…]` — plain refs, **no** `@revision` |
60
+ | `imports:` of your **own** repos' souls | delete — member souls are discovered by convention |
61
+ | `imports:` of a **stranger's** soul (with `revision`) | `external: [{ source: git:<repo>@<full OID>, soul: <path> }]` |
62
+ | `teams: { private: per-human }` (the messaging payload) | `messaging: { private: per-human }`; `teams:` now declares **labels** |
63
+ | `defaults.knowledge: { capability, source }` | `defaults.knowledge: { <cap>: { from: package } }` (one entry, or `none`) |
64
+ | per-soul `stores.<x>.inherit` | `stores: { <name>: git:<repo> }` once, here |
65
+ | `catalog:` | delete (bare versions use the official catalog; `OATS_PACKAGE_CATALOG` overrides) |
66
+ | — | `packages: { <id>: <version> \| git:<repo>@<ref> }` — every version your souls used to carry in `source:` lines, **once** |
67
+ | — | `defaults.capabilities: { oats.core: { from: package } }` and whatever every soul should get |
68
+
69
+ ```yaml
70
+ schemaVersion: 2
71
+ name: acme
72
+ members:
73
+ - git:github.com/acme/agents
74
+ - git:github.com/acme/platform
75
+ packages:
76
+ oats.framework: v1.1.3
77
+ oats.okf: v2.1.3
78
+ oats.aweb: v1.11.2
79
+ teams:
80
+ global: { description: Org-wide }
81
+ engineering: { description: Platform }
82
+ defaults:
83
+ capabilities: { oats.core: { from: package } }
84
+ knowledge: { oats.okf: { from: package } }
85
+ messaging: { oats.aweb: { from: package } }
86
+ tasks: none
87
+ stores:
88
+ org: git:github.com/acme/knowledge
89
+ messaging:
90
+ private: per-human
91
+ ```
92
+
93
+ No absolute paths anywhere (they belong in `oats-local.yaml`). `from:` values
94
+ that name a repo are **canonical keys** — `github.com/acme/agents`, not
95
+ `git:github.com/acme/agents` and not `https://…`.
96
+
97
+ ## 3. Add `oats-membership.yaml` to every member (replaces `oats.yaml`)
98
+
99
+ ```yaml
100
+ schemaVersion: 2
101
+ workspace: git:github.com/acme/agents
102
+ team: engineering # optional default label for this repo's souls/capabilities
103
+ ```
104
+
105
+ Delete `oats.yaml`. Its `exports:` lists are gone: every `souls/*/soul.yaml` and
106
+ `capabilities/*/oats.json` is discoverable; add `private: true` to the ones that
107
+ should stay internal. The host repo backlinks to itself like any member.
108
+
109
+ ## 3b. Move the souls: `agents/<name>/soul/` → `souls/<name>/`
110
+
111
+ In 0.24 a repo's souls lived at `agents/<name>/soul/` beside that soul's
112
+ instances. Under v2 discovery looks **only** at `souls/<name>/soul.yaml`; the
113
+ `agents/` directory belongs to the *deployment* (instance homes and, under the
114
+ kernel's per-commit soul cache, the fetched soul copies — see §7b) and is not
115
+ read as a soul source. Move every soul as a tracked rename so history follows:
116
+
117
+ ```bash
118
+ mkdir -p souls
119
+ git mv agents/release-manager/soul souls/release-manager
120
+ # … one line per soul; then
121
+ git rm -r --cached agents 2>/dev/null; echo 'agents/' >> .gitignore # instances were never meant to be tracked
122
+ ```
123
+
124
+ `souls/<name>/` keeps its `AGENTS.md`, `CLAUDE.md → AGENTS.md` alias, `skills/`,
125
+ `knowledge/` and `soul.yaml` (rewritten in §4); the directory name must equal
126
+ `soul.yaml#name`. Then fix whatever enumerates the old path: repo tests, scripts,
127
+ CI checks and any `oats.yaml`-era `exports:` tooling that globbed
128
+ `agents/*/soul/soul.yaml` (`git grep -n 'agents/.*/soul'` finds them) — under v2
129
+ they enumerate `souls/*/soul.yaml`. A soul left under `agents/` is invisible to
130
+ `oats souls` and to `oats spawn`; nothing warns about it.
131
+
132
+ ## 4. Edit every `soul.yaml` to v2
133
+
134
+ | 0.24 | v2 |
135
+ |---|---|
136
+ | `schemaVersion: 1` | `schemaVersion: 2` |
137
+ | `requires.knowledge: { capability: oats.okf, source: git:…@v2.1.3#oats-package }` | `capabilities: { oats.okf: { from: package } }` — or nothing, if the workspace default already says so |
138
+ | `requires.capabilities.<cap>: { source: git:… }` | `<cap>: { from: package }` (published) or `<cap>: { from: here }` / `{ from: <repo key> }` (a member capability) |
139
+ | `source: repo:…` / `path:` | `{ from: here }` |
140
+ | `defaults.capabilities` | fold into `capabilities:`; use `off` to remove a workspace default |
141
+ | `stores.inherit` | delete (stores are declared once in the workspace) |
142
+ | `imports` | delete |
143
+ | `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 |
145
+ | — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
146
+
147
+ ```yaml
148
+ schemaVersion: 2
149
+ name: release-manager
150
+ description: Cuts, verifies and announces releases.
151
+ work: worktree
152
+ team: engineering
153
+ capabilities:
154
+ acme-release-tooling: { from: here }
155
+ knowledge:
156
+ owns: release-manager
157
+ reads: [platform-engineer]
158
+ messaging:
159
+ channels: [acme-eng]
160
+ ```
161
+
162
+ `name` must equal the soul's directory name; `name`, `description` and `work`
163
+ are required. Capabilities the repo exports live at
164
+ `capabilities/<name>/oats.json` — the manifest is unchanged; you may add
165
+ `private: true` / `team:`.
166
+
167
+ **Carry `team:` on every soul, or on its repo's membership.** A soul's team is
168
+ `soul.yaml#team`, else `oats-membership.yaml#team`, else *unassigned*
169
+ (`null`). Labels never gate anything, but the kernel addresses provider payload
170
+ by label: an unlabelled soul receives the messaging **base** payload only —
171
+ `workspace.messaging` minus `byTeam`, no `byTeam.<label>` block, and no
172
+ `defaults.byTeam.<label>` capabilities either. If your 0.24 deployment had one
173
+ messaging identity per team (§1), a soul that loses its label silently lands
174
+ outside every team-addressed payload; nothing refuses it. Label the membership
175
+ when a whole repo belongs to one team, and the soul when it does not.
176
+
177
+ **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.
184
+
185
+ ## 5. Write `oats-local.yaml` on each machine
186
+
187
+ ```
188
+ ~/acme-workspace/ # the taught convention: "<name>-workspace"
189
+ ├── oats-local.yaml
190
+ ├── agents/ # instance homes
191
+ └── platform/ # member clones, only where someone works IN them
192
+ ```
193
+
194
+ ```yaml
195
+ schemaVersion: 2
196
+ workspace: git:github.com/acme/agents
197
+ settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
198
+ oats.okf:
199
+ bindings-file: /Users/ana/.oats/okf-bindings.json # required by the OKF binding: absolute host path
200
+ state-dir: /Users/ana/.oats/okf-state # required by the OKF binding: absolute host path; FRESH for a rebuilt deployment (§7b)
201
+ harvest-runtime: pi # optional: pi | claude | codex (default pi)
202
+ oats.aweb:
203
+ delivery: channel # channel (default) | session — see capabilities/oats-aweb/oats.json#settings.delivery
204
+ souls:
205
+ disabled: [data-analyst]
206
+ ```
207
+
208
+ `settings.<cap>` is merged into that capability's payload after the soul's
209
+ slot payload and before `spawn --provider` (decision 14); the keys are the
210
+ capability's own (`oats.json#settings`). For **`oats.okf` 2.1.3** the binding
211
+ requires both `bindings-file` and `state-dir` as normalized absolute host
212
+ paths (`setting state-dir is required (absolute host path)` is a refusal, not a
213
+ default) and accepts `harvest-runtime` / `harvest-model`. For **`oats.aweb`**
214
+ the one machine-level key is `delivery`: `channel` (the native aweb channel
215
+ packages wake the instance; default) or `session` (delivery is external —
216
+ `AWEB_DELIVERY=session`, the host wake broker registers the instance once it
217
+ exists; requires an `aw` that ships `aw wake`). `identity.source` is also legal
218
+ here but see §8 for why it belongs at spawn.
219
+
220
+ Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
221
+ `oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
222
+ `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.)
225
+
226
+ ## 6. `oats sync`
227
+
228
+ From the deployment directory:
229
+
230
+ ```
231
+ oats sync
232
+ ```
233
+
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).
239
+
240
+ ## 7. Approve packages
241
+
242
+ Approval is **per package version, once, in the lock** — no `oats trust`, no
243
+ per-capability approval, no per-operator trust list. `oats sync` on a terminal
244
+ prints every executable (`commands.*` and `hooks.*.command` targets of every
245
+ 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.
250
+
251
+ ## 7b. OKF 2: start a FRESH `state-dir` — do not re-point the old one
252
+
253
+ OKF 2 pins each knowledge **owner** to a soul by path: at source registration
254
+ (the `oats.okf` spawn hook) it writes `owners.json` in `state-dir` as
255
+ `{ <owner id>: realpath(<home>/soul) }` and refuses a later registration whose
256
+ owner resolves to a different path (`E_OWNER stable owner ID already identifies
257
+ a different soul in this state namespace`).
258
+
259
+ Under v2 that path is no longer your checkout. `oats spawn` fetches the soul
260
+ from its member repo at the confirmed commit into the deployment's
261
+ **per-commit soul cache**, `agents/<name>/souls/<commit12>/` (immutable once
262
+ written; `agents/<name>/soul` is a kernel-swapped pointer to the current one),
263
+ and the instance's `<home>/soul` links **its own commit's directory** — so the
264
+ realpath the hook pins is `<deployment>/agents/<name>/souls/<commit12>`, which
265
+ never equals the 0.24 pin (`<repo>/agents/<name>/soul`) and changes whenever the
266
+ member moves. Two consequences:
267
+
268
+ - **Do not reuse the 0.24 `state-dir`.** Its `owners.json` pins every owner to
269
+ the old path; the first v2 spawn of each soul would be refused with `E_OWNER`.
270
+ Give the rebuilt deployment a fresh `state-dir` (§5) and a fresh
271
+ `bindings-file` if the old one names the old state root. The old `state-dir`
272
+ is **frozen custody**: read-only history (`oats okf inspect --source
273
+ <old-state>/sources/<id>/source.json …` still works against it), never edited,
274
+ never re-pointed at the new soul path. Accepted knowledge is not affected —
275
+ it lives in the bases, not in `state-dir`.
276
+ - **The owner pin is per commit.** OKF 2.1.3 records the realpath at first
277
+ registration and the kernel keeps that commit directory for as long as any
278
+ instance links it, so a running instance's pin stays valid; a *later* spawn of
279
+ the same soul at a newer member commit links a different directory and
280
+ registers under the same owner id → `E_OWNER` again. Until OKF re-bases the
281
+ pin on the owner identity rather than the path (an OKF 2.1.4 item), the
282
+ practical rule is: one `state-dir` per (deployment, soul commit) is safe;
283
+ moving a member that owns knowledge means a fresh `state-dir` for the new
284
+ commit's spawns (the previous one becomes frozen custody, as above). Plan
285
+ knowledge-owning souls' member commits deliberately.
286
+
287
+ ## 8. Re-take a retained messaging seat with `spawn --provider`
288
+
289
+ In 0.24, an instance-specific messaging identity (a retained seat) was pinned in
290
+ `oats-config.yaml` under `souls:`. That home is gone; the fact belongs to the
291
+ **spawn**:
292
+
293
+ ```bash
294
+ oats spawn release-manager --purpose seat --provider oats.aweb identity.source=/abs/path/to/retained/.aw
295
+ ```
296
+
297
+ `--provider <cap> key=value` is repeatable; dotted keys nest. The payload is
298
+ merged after the soul's `messaging:` and the machine's `settings.oats.aweb`, and
299
+ recorded in `instance.json.providers.oats.aweb`, so exactly one instance holds
300
+ the seat while other instances of the soul mint fresh identities.
301
+
302
+ **The value is the path itself.** `oats.aweb` reads `identity.source` as the
303
+ absolute path of the `.aw` directory to retain (it must hold `signing.key`); the
304
+ kernel does not resolve symbolic seat names. Because it is an absolute path it is
305
+ a fact about ONE machine, so its other legal home is `oats-local.yaml`
306
+ (`settings.oats.aweb.identity.source: /abs/path`) — never the workspace file
307
+ (absolute paths are refused there, decision 14). Prefer the spawn form: a
308
+ machine-level setting would give the seat to EVERY instance of every messaging
309
+ soul on that machine, and a seat can be held once. The Desktop's
310
+ confirmed apply carries the same map.
311
+
312
+ ## 9. Spawn, and check drift
313
+
314
+ ```bash
315
+ oats souls # every non-private soul of every confirmed member, with origin and team
316
+ 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
318
+ oats spawn <soul> --purpose x
319
+ oats status # per instance: modules … [member moved since (now @ …)] / [capability no longer present]
320
+ ```
321
+
322
+ ## What disappears
323
+
324
+ | Gone | Replaced by |
325
+ |---|---|
326
+ | `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 |
327
+ | `oats.yaml` | `oats-membership.yaml` |
328
+ | `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>/` |
329
+ | `.agents/capabilities/installed/` and `owned/` | nothing is installed; `<instance>/.oats/modules/<cap>/` per instance; member capabilities under `<repo>/capabilities/` |
330
+ | `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 |
331
+ | lock v1 / v2 | lock v3 (`packages` only, with `url`, `capabilities`, `approved`) |
332
+ | per-soul `source: git:…@v#…`, `repo:`, `path:` | `from: here \| <repo key> \| package` + `packages:` in the workspace |
333
+ | `imports:` of member souls, `exports:` lists | discovery by convention; `private: true` |
334
+ | `stores.<x>.inherit` | `stores:` in the workspace |
335
+ | `teams:` as the messaging payload | `messaging:`; `teams:` are labels |
336
+ | `@revision` on members | none — members are latest; frozen content is a package |
337
+ | ambient-skill exclusion at launch | the harness starts normally; capability skills are copied to `.agents/skills/<cap>/` |
338
+
339
+ ## What is kept
340
+
341
+ Kernel-neutral provider payloads and the `binding` contract; per-version
342
+ executable approval (now in the lock); spawn preview / confirmed apply
343
+ (`decision.revision`, now binding the resolution revision) and idempotency;
344
+ retirement and retention; the official catalog; the canonical-plus-alias
345
+ instance construction (`CLAUDE.md → AGENTS.md`, `.claude/skills →
346
+ ../.agents/skills`); every published Desktop CLI contract, extended as described
347
+ in [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
@@ -0,0 +1,99 @@
1
+ # OATS v0.25.0 — the workspace model (breaking)
2
+
3
+ Kernel/Pi **0.25.0**. Tag `v0.25.0` → 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
+ **This is the 0.25 line: a new deployment model, not a patch to the old one.**
8
+ 0.24.x keeps spawning 0.24.x deployments; there is no converter. The rebuild
9
+ guide is `docs/rebuild-to-v2.md`. Design: `docs/design/2026-09-23-simplified-workspace-model.md`;
10
+ decision record (26 decisions): `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`;
11
+ normative module contracts: `docs/design/2026-09-23-workspace-module-contracts.md`.
12
+
13
+ ## The one idea
14
+
15
+ An organisation has **one workspace**: a Git repo hosting `oats-workspace.yaml`
16
+ that lists its **member repositories**, pins its **packages**, names its
17
+ **teams** and declares **defaults**. Every member carries an
18
+ `oats-membership.yaml` back-link; the handshake is confirmed over the Git
19
+ remotes in the operator's own access context and **membership is the trust**.
20
+ A member exports **souls** (`souls/<name>/soul.yaml`) and **capabilities**
21
+ (`capabilities/<name>/oats.json`) at its latest commit; a soul says where each
22
+ capability comes from (`from: <member repo> | here | package`). Packages are
23
+ consumed only through `packages:` + `oats-lock.json` v3, with **per-version
24
+ executable approval**. A spawn **resolves** the soul (`lib/resolve.mjs`) and
25
+ **materializes** every capability whole into the instance
26
+ (`<home>/.oats/modules/<cap>/`, skills under `<home>/.agents/skills/<cap>/`);
27
+ the harness starts normally. Nothing is installed anywhere.
28
+
29
+ ## Kernel — `workspace-v2` (`workspaceApi: 2`), `instance-modules`, `spawn-provider-payload`
30
+
31
+ - **Declarations** (`docs/*.schema.json`, all `schemaVersion: 2`):
32
+ `oats-workspace.yaml` (members, packages, teams, defaults incl. `byTeam`,
33
+ stores, `messaging` incl. `byTeam.<label>`, external souls),
34
+ `oats-membership.yaml` (`workspace`, `team?`), `soul.yaml` v2
35
+ (`capabilities: { <cap>: { from } | off }`, `team`, `private`,
36
+ `compatibility`, slot payloads), `oats-local.yaml` (the ONLY per-machine
37
+ file: `workspace:` or `standalone:` ref, `clones`, `settings.<cap>`,
38
+ `souls.disabled`).
39
+ - **`oats sync`** — discover → confirm membership → resolve packages → write
40
+ `oats-lock.json` v3; unapproved executables listed, exit 2 non-TTY / prompt
41
+ on a TTY. `oats package add|remove`, `oats workspace status`,
42
+ `oats capabilities`, `oats souls` (origin + team columns).
43
+ - **`oats spawn`** — discovers and resolves over the remotes, fetches the soul
44
+ from its member repo (refreshed per commit), materializes, launches.
45
+ `--preview` works before any apply and reports `modules[]` (with
46
+ `changedSince`), `team`, `resolution`, `workspace`, `soulFetched`; the
47
+ decision binds the resolution revision (a member that moved between preview
48
+ and apply → `E_DECISION_STALE`). `--provider <cap> k=v` records a per-spawn
49
+ provider payload (`instance.json.providers`) — the third payload home after
50
+ the soul and `oats-local.yaml settings`.
51
+ - **`instance.json`** carries `modules{}` (name → from/commit/digest),
52
+ `providers{}`, `workspace{ key, commit, resolution, standalone, soul }`,
53
+ `capabilities[]`.
54
+ - **`oats status`** shows drift per instance (`member moved since …`,
55
+ `capability no longer present`); `--json` `instances[].modules[]`.
56
+ - **`oats onboard [<dir>] --workspace <ref>`** — writes `oats-local.yaml`,
57
+ runs the sync path, prints the taught layout and the **hosting rule** for
58
+ mixed public/private organisations (`onboardApi: 2`). Creates no soul,
59
+ spawns nothing.
60
+ - **Standalone case** — a member whose workspace cannot be read (access
61
+ failure only, never network/timeout) still offers its souls with `from: here`
62
+ capabilities **plus `oats.core`** from the official catalog through the
63
+ operator's own lock; marked `standalone: true` in sync/status/spawn output.
64
+ - **Teams** are labels; per-team provider payload is `messaging.byTeam.<label>`
65
+ (merged by the kernel, stripped before the provider). A store names a
66
+ repository; the root inside it is the provider's binding key.
67
+ - **Scheduled spawns** on a workspace deployment materialize exactly like
68
+ `oats spawn` (the scheduler delegates to the CLI).
69
+ - **Security** (found by adversarial review, all pinned): remote tree names
70
+ fsck'd (no traversal), tag naming a blob refused as a commit, symlinks in
71
+ fetched trees refused except the soul's `CLAUDE.md → AGENTS.md` alias,
72
+ `__proto__`/`constructor`/`prototype` refused at every payload layer and in
73
+ `--provider` flags, `byTeam` reserved outside `workspace.messaging`, package
74
+ executables gated on per-version approval, atomic staging with full
75
+ rollback (no half homes).
76
+
77
+ ## Removed
78
+
79
+ `oats init`, `use`, `install`, `trust`, `list`, `remove`, `migrate`, `catalog`,
80
+ `update`, `config`, `inject` → typed `E_UNKNOWN_COMMAND` naming the
81
+ replacement. `oats-config.yaml` is no longer read as configuration (a leftover
82
+ one is harmless); the `installed/` tier, `oats.yaml`, `lockfileVersion` 2,
83
+ per-soul `source: git:…` lines, `stores.inherit`, `imports`. The `catalog`
84
+ feature name is gone from `version --json`. `docs/configuration.md` now
85
+ describes `oats-local.yaml` only.
86
+
87
+ ## Desktop
88
+
89
+ The Desktop server's `oats catalog` reader now surfaces `E_USAGE` (the verb is
90
+ removed); the Capabilities view's package acquisition flow will follow the new
91
+ DTOs in a later release (Phase F). Everything else in the Desktop is unchanged.
92
+
93
+ ## Known follow-ups
94
+
95
+ - The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose
96
+ in `lib/core.mjs`; folding it into the one pipeline removes the remaining v1
97
+ modules (residue table in `docs/design/2026-09-23-workspace-v2-implementation-plan.md`).
98
+ - The OATS framework repositories themselves convert to the model (six expert
99
+ souls, `oats.core`/`oats.setup` rewritten for the new architecture) in 0.26.
@@ -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.
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