@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.
@@ -31,43 +31,70 @@ instance/.claude/skills -> ../.agents/skills
31
31
 
32
32
  Spawn copies kernel + soul-private + active capability skills into real
33
33
  instance-local directories there. Directory symlinks are not used because
34
- harness recursive discovery may not descend through them. Packages retain
35
- skills in their own artifact; activation selects them for materialization. Config-level `.agents/skills` is not an OATS capability source
34
+ harness recursive discovery may not descend through them. Under the workspace
35
+ model (0.25) every capability is copied whole into
36
+ `instance/.oats/modules/<capability>/` and its skills into
37
+ `instance/.agents/skills/<capability>/<skill>/`; nothing is installed at a
38
+ config level. Config-level `.agents/skills` is not an OATS capability source
36
39
  or an ambient runtime discovery root.
37
40
 
38
- Pi starts spawned sessions with ambient skill and context discovery disabled
39
- and the one instance path explicit; its globally configured extensions remain
40
- enabled. Claude runs provider-native: it reads the instance's `.claude/skills`
41
- and `CLAUDE.md` symlinks, and the operator's own user and project
42
- configuration — skills, plugins, settings — stays in effect. Neither runtime
43
- gets a redirected config home. `composition.materialized.runtimePosture` in
44
- `instance.json` records what each instance actually exposes.
45
- `oats-getting-started` is the sole pre-workspace ambient bootstrap.
46
-
47
- Duplicate skill directory names are errors unless config's `skill-overrides`
48
- selects a source.
41
+ Harnesses start normally (workspace-model decision 13, and the behaviour of
42
+ every launch a 0.25 kernel performs, classic homes included): Pi starts with
43
+ cwd = the instance home, the composed `AGENTS.md` appended to its system
44
+ prompt, and its own skill, context and extension discovery intact — the
45
+ instance's copied skills are found because they sit under cwd; machine-level
46
+ and repo-level skills resolve exactly as without OATS. *(0.24 kernels started
47
+ Pi with ambient skill and context discovery disabled and the one instance path
48
+ explicit; that exclusion is gone.)* Claude runs provider-native: it reads the
49
+ instance's `.claude/skills` and `CLAUDE.md` symlinks, and the operator's own
50
+ user and project configuration — skills, plugins, settings — stays in effect.
51
+ Neither runtime gets a redirected config home.
52
+ `composition.materialized.runtimePosture` in `instance.json` records what each
53
+ instance actually exposes. `oats-getting-started` is the sole pre-workspace
54
+ ambient bootstrap.
55
+
56
+ Duplicate skill directory names within the OATS-composed set are errors
57
+ (`E_SKILL_DUPLICATE`, naming both capabilities) unless the classic config's
58
+ `skill-overrides` selects a source; between a composed skill and an ambient
59
+ repo/machine skill the harness's own precedence decides (decision 16).
49
60
 
50
61
  ## Package locations
51
62
 
63
+ **Workspace model (0.25, current):** nothing is installed. A capability lives
64
+ where its owner keeps it and is copied whole into each instance at spawn:
65
+
66
+ ```text
67
+ <member repo>/capabilities/<name>/oats.json # member-tier capability, latest state (membership is the trust)
68
+ <package repo>/oats-package/oats-package.json # package-tier: versioned via oats-workspace.yaml packages:
69
+ <deployment>/oats-lock.json # lockfileVersion 3: package commit, integrity, per-version approval
70
+ <instance>/.oats/modules/<capability>/ # the copy this instance runs
71
+ ```
72
+
73
+ **Classic 0.24 layout** (still launched by the 0.24 kernel; a 0.25 kernel
74
+ reads none of it as configuration — see [rebuild-to-v2.md](rebuild-to-v2.md)):
75
+
52
76
  ```text
53
77
  <package>/capabilities/<name>/oats.json # the official marketplace (install source, not ambient)
54
78
  <level>/.agents/capabilities/installed/<name>/oats.json # acquired (gitignored, restorable)
55
79
  <level>/.agents/capabilities/owned/<name>/oats.json # authored at this scope (source; committed where the scope is a repo)
56
- <level>/oats-lock.json # external source/integrity/trust
80
+ <level>/oats-lock.json # lockfileVersion 2: external source/integrity/trust
57
81
  ```
58
82
 
59
83
  ## Quick map
60
84
 
61
- | Thing | Canonical location |
62
- |---|---|
63
- | Config | `<level>/oats-config.yaml` |
64
- | Acquisition lock | `<level>/oats-lock.json` |
65
- | Soul operating doc | `soul/AGENTS.md` |
66
- | Soul Claude view | `soul/CLAUDE.md -> AGENTS.md` |
67
- | Soul-private skills | `soul/skills/` |
68
- | Instance operating doc | `instance/AGENTS.md` (generated) |
69
- | Instance skill set | `instance/.agents/skills/` |
70
- | Instance metadata | `instance/instance.json` |
85
+ | Thing | Canonical location (0.25 workspace model) | 0.24 classic |
86
+ |---|---|---|
87
+ | Shared declaration | `oats-workspace.yaml` in the host repo; `oats-membership.yaml` in every member | `oats-config.yaml` chain, `oats.yaml` |
88
+ | Per-machine config | `<deployment>/oats-local.yaml` (uncommitted) | `oats-config.yaml` `settings:` |
89
+ | Acquisition lock | `<deployment>/oats-lock.json` (v3) | `<level>/oats-lock.json` (v2) |
90
+ | Soul source | `<member repo>/souls/<name>/` | `agents/<name>/soul/` |
91
+ | Soul operating doc | `souls/<name>/AGENTS.md` | `soul/AGENTS.md` |
92
+ | Soul Claude view | `souls/<name>/CLAUDE.md -> AGENTS.md` | `soul/CLAUDE.md -> AGENTS.md` |
93
+ | Soul-private skills | `souls/<name>/skills/` | `soul/skills/` |
94
+ | Instance operating doc | `instance/AGENTS.md` (generated) | same |
95
+ | Instance skill set | `instance/.agents/skills/` | same |
96
+ | Instance modules | `instance/.oats/modules/<capability>/` | `.agents/capabilities/installed/` (shared) |
97
+ | Instance metadata | `instance/instance.json` (`modules{}`, `providers{}`, `workspace{}`) | `instance/instance.json` |
71
98
 
72
99
  Symlinks prevent compatibility paths from drifting. Generated regular files
73
100
  separate canonical portable identity from scope-dependent runtime policy.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Purpose:** the one accurate view of every work stream in the redesign, what is on main, what is in flight, who owns it, and what blocks it. Lead: `oats-expert` (redesign lead). Updated whenever anything merges, is returned, or reality changes. Older per-lane boards are superseded by this file.
4
4
 
5
- **Last update:** 2026-09-23 17:40Z · **Workspace model v2 phases A–C ON MAIN (`59ae22df`, PR99) → `v0.25.0` tagged (release run in flight)** · Phase D next (framework repos as the first workspace; six expert souls; `oats.core`/`oats.setup` rewrite) · ⏸ parity pipeline still paused except 10B-0 (→ 0.24.14 when the engineer hands off; note: 0.24.14 must be cut from the 0.24 line, not main).
5
+ **Last update:** 2026-09-23 20:00Z · **0.25.0 + 0.25.1 PUBLISHED** (workspace model A–C + team-review fixes) · Phase D plan next · **Desktop engineer paused by the human** (10B-0 uncommitted foundation preserved; resume is the human's call; 0.24.14 to be cut from a maintenance branch off `e5cdaf95` when its PR lands) · parity pipeline ⏸.
6
6
 
7
7
  Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
8
8
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Status:** Phase1 implementation authorised by the human, using the existing developers under lead supervision/review. The workspace home is confirmed as `oats`; `oats-dev` remains development capabilities. This does not claim completed conversion or authorise unspecified new contracts, credential operations or live deployment mutations.
6
6
 
7
- > **2026-09-23 — direction change, read first.** Phases 1–3 delivered as written (workspace on Git, five souls + central knowledge, all contract-bearing Desktop parity slices, releases 0.24.7–0.24.13). Reviewing the result, the human judged the *declaration model* itself too heavy and, in one sitting, accepted a simplified **workspace model v2**: one workspace per org; reciprocal membership as the only gate and as the trust decision; a soul says `from:` (member repo | `here` | `package`) — a location, never a version; packages are the only versioned thing; **nothing is installed** — every capability is copied whole into the instance at spawn; discovery over Git remotes; teams as labels; harnesses start normally. Record: `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`; worked example: [2026-09-23-simplified-workspace-model.md](2026-09-23-simplified-workspace-model.md). **Consequences for this plan:** P1.3's `oats.yaml` export indexes and P1.4's `oats-config.yaml` activation are superseded (→ `oats-membership.yaml`, derived activation); D1/D4 stand (`oats.core` becomes `from: package` by default; the catalog stays as discovery); D2's "explicit `oats.core` on every soul" is met by the workspace default; D3 gains the `<name>-workspace/` convention and clone-then-spawn. **No migration** — clean v2, the framework's own repos are the first workspace. A Phase 4 implementation plan is proposed to the human before any `lib/`/`bin/` change; the Desktop parity pipeline is paused meanwhile except for the in-flight 10B-0 security fix.
7
+ > **2026-09-23 — direction change, read first.** Phases 1–3 delivered as written (workspace on Git, five souls + central knowledge, all contract-bearing Desktop parity slices, releases 0.24.7–0.24.13). Reviewing the result, the human judged the *declaration model* itself too heavy and, in one sitting, accepted a simplified **workspace model v2**: one workspace per org; reciprocal membership as the only gate and as the trust decision; a soul says `from:` (member repo | `here` | `package`) — a location, never a version; packages are the only versioned thing; **nothing is installed** — every capability is copied whole into the instance at spawn; discovery over Git remotes; teams as labels; harnesses start normally. Record: `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`; worked example: [2026-09-23-simplified-workspace-model.md](2026-09-23-simplified-workspace-model.md). **Consequences for this plan:** P1.3's `oats.yaml` export indexes and P1.4's `oats-config.yaml` activation are superseded (→ `oats-membership.yaml`, derived activation); D1/D4 stand (`oats.core` becomes `from: package` by default; the catalog stays as discovery); D2's "explicit `oats.core` on every soul" is met by the workspace default; D3 gains the `the operator's deployment directory` convention and clone-then-spawn. **No migration** — clean v2, the framework's own repos are the first workspace. A Phase 4 implementation plan is proposed to the human before any `lib/`/`bin/` change; the Desktop parity pipeline is paused meanwhile except for the in-flight 10B-0 security fix.
8
8
 
9
9
  ## Goal and order
10
10
 
@@ -1,6 +1,6 @@
1
1
  # A simpler multi-repo model for OATS — brainstorm with a worked example
2
2
 
3
- **Status:** **ACCEPTED DIRECTION** — every question closed with the human on 2026-09-23; nothing implemented yet. This document is the worked example; the normative record is the Decision concept `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md` and the adoption plan. Implementation is a clean v2 (no migration), planned separately. **Decided so far (human, 2026-09-23):** (1) reciprocal membership stays a hard gate; (2) workspace-tier trust = membership, nothing more; (3) everything in a member is discoverable by default — a soul or capability that wants to stay internal says `private: true` in its own definition; (4) the repo's half of the handshake is a one-line **`oats-membership.yaml`** (replaces `oats.yaml`); (5) **no migration path** — a clean break to v2; (6) a soul names each capability **with where it comes from** (`from: <member repo>` or `from: package`) — a location, never a version; resolution is a handshake check + lookup, not a search; (7) **full per-instance materialization** — every capability (skills, injects, scripts, hooks) is copied into the instance at spawn; a running instance never changes under itself; (8) **nothing is "installed"** — a package is just a second kind of source, fetched at the pinned version and copied in like a member's capability; the lock records exact commit/integrity and the one-time executable approval per version; (9) **discovery and resolution work against Git remotes, never local clones** — the only thing that needs a clone is a soul's work target; the local layout is the operator's, with a taught convention (`<name>-workspace/`); (10) the handshake is *observed* with the operator's own read access to both halves — no access to the workspace repo means no membership from that seat, by design; (11) **members carry no revision** — a member is always its latest state; a team that wants frozen capabilities publishes them as a package and pins that; (12) **one workspace per org, teams are labels** — `team:` on a soul/capability (or a repo default in `oats-membership.yaml`) organises and can supply additive defaults, but never gates, restricts or partitions anything; (13) **harnesses start normally and resolve skills from their own default places** — OATS contributes materialized capability skills into the instance's `.agents/skills/` and stops excluding machine-level or repo-level skills. **All questions closed; ready to be written up as a Decision.**
3
+ **Status:** **ACCEPTED DIRECTION** — every question closed with the human on 2026-09-23; nothing implemented yet. This document is the worked example; the normative record is the Decision concept `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md` and the adoption plan. Implementation is a clean v2 (no migration), planned separately. **Decided so far (human, 2026-09-23):** (1) reciprocal membership stays a hard gate; (2) workspace-tier trust = membership, nothing more; (3) everything in a member is discoverable by default — a soul or capability that wants to stay internal says `private: true` in its own definition; (4) the repo's half of the handshake is a one-line **`oats-membership.yaml`** (replaces `oats.yaml`); (5) **no migration path** — a clean break to v2; (6) a soul names each capability **with where it comes from** (`from: <member repo>` or `from: package`) — a location, never a version; resolution is a handshake check + lookup, not a search; (7) **full per-instance materialization** — every capability (skills, injects, scripts, hooks) is copied into the instance at spawn; a running instance never changes under itself; (8) **nothing is "installed"** — a package is just a second kind of source, fetched at the pinned version and copied in like a member's capability; the lock records exact commit/integrity and the one-time executable approval per version; (9) **discovery and resolution work against Git remotes, never local clones** — the only thing that needs a clone is a soul's work target; the local layout is the operator's — onboarding asks for the directory, no named convention; (10) the handshake is *observed* with the operator's own read access to both halves — no access to the workspace repo means no membership from that seat, by design; (11) **members carry no revision** — a member is always its latest state; a team that wants frozen capabilities publishes them as a package and pins that; (12) **one workspace per org, teams are labels** — `team:` on a soul/capability (or a repo default in `oats-membership.yaml`) organises and can supply additive defaults, but never gates, restricts or partitions anything; (13) **harnesses start normally and resolve skills from their own default places** — OATS contributes materialized capability skills into the instance's `.agents/skills/` and stops excluding machine-level or repo-level skills. **All questions closed; ready to be written up as a Decision.**
4
4
  **Date:** 2026-09-23
5
5
  **Author:** oats-expert, from a conversation with the human about the weight of the current declaration files.
6
6
 
@@ -419,10 +419,10 @@ Northwind adopts it through `external:` with a pinned revision. There is deliber
419
419
 
420
420
  **The only thing that needs a local clone is a soul's work target** — a soul with `work: worktree | checkout | directory` works *in* a repo, so that repo must be on disk. Spawning a soul whose repo is not yet cloned is therefore a *guided clone into the conventional place, then spawn*: a job for the onboarding skill (`oats-setup-expert`), not the kernel.
421
421
 
422
- The **taught default** — what onboarding sets up and what the docs show — is one folder named after the workspace with the member clones inside it:
422
+ **The deployment directory is the operator's choice** (decision 9): an existing folder that already holds the member clones is the usual case; onboarding asks for it (`oats onboard <dir>`) and adds the three entries the kernel needs. Shown here as Ana's `~/northwind/`, with the member clones inside it:
423
423
 
424
424
  ```
425
- ~/northwind-workspace/ ← "<name>-workspace"
425
+ ~/northwind/ ← the directory Ana chose (any name, any place)
426
426
  ├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
427
427
  ├── oats-lock.json ← exact commit + integrity + per-version approval per package
428
428
  ├── agents/ ← instance homes, each self-contained
@@ -510,7 +510,7 @@ Today's `capabilities.layers.messaging.{capability, from, global, souls, setting
510
510
  |---|---|---|
511
511
  | True of every instance of the soul | `soul.yaml` → `messaging:` / `knowledge:` | `messaging: { channels: [northwind-eng] }`, `knowledge: { owns: release-manager }` |
512
512
  | A fact about this machine | `oats-local.yaml` → `settings.<capability>.<key>` (absolute paths refused in the workspace file) | `settings.oats.okf.state-dir: /Users/ana/.oats/okf` |
513
- | A fact about **this spawn** | `oats spawn … --provider <cap> key=value` → `instance.json` `providers.<cap>` (the Desktop's confirmed apply carries the same map) | `--provider oats.aweb identity.source=retained:release-seat` — one instance takes the retained seat; other instances of the soul mint fresh |
513
+ | A fact about **this spawn** | `oats spawn … --provider <cap> key=value` → `instance.json` `providers.<cap>` (the Desktop's confirmed apply carries the same map) | `--provider oats.aweb identity.source=/abs/path/to/retained/.aw` — one instance takes the retained seat; other instances of the soul mint fresh |
514
514
 
515
515
  The provider's `binding` contract (`normalize → bind → check`) runs over the merged payload exactly as today; the provider still enforces its own rules (e.g. a state root outside the work tree).
516
516
 
@@ -565,7 +565,7 @@ OATS is a **skill contributor, not a skill sandbox**. Today the harness is launc
565
565
  - **Duplicate names** (team review): two *composed* capability skills with the same name are still a spawn error naming both (`skill-overrides` picks one). A composed skill and an ambient repo/machine skill with the same name is **not** an error — the harness's precedence decides; the preview lists composed names so a clash is visible.
566
566
 
567
567
  ```
568
- ~/northwind-workspace/agents/release-manager/instances/release-manager-v3/
568
+ ~/northwind/agents/release-manager/instances/release-manager-v3/
569
569
  ├── AGENTS.md ← canonical, composed: soul AGENTS.md + capability injects
570
570
  ├── CLAUDE.md → AGENTS.md ← relative symlink (unchanged from today)
571
571
  ├── .agents/skills/ ← canonical; where Pi (and Codex) look
@@ -307,3 +307,260 @@ Appended, not edited in place; each item names the section it refines. Decision
307
307
  **§6 `oats package remove <id>` (M15).** BOTH branches — the file tracked by the checkout (edited) and untracked/absent (not edited) — answer `E_PACKAGE_MISSING { id, path? }` when `<id>` is not declared in `packages:`. The tracked receipt is `{ action: "remove", id, value: null, previous: <old value>, edited: true, file }`; the untracked receipt is `{ action: "remove", id, value: null, edited: false, file: null, line: null, hint }` — `previous` is absent and `line` is `null` (a removal has no line to add).
308
308
 
309
309
  **§6 features.** `catalog` is no longer advertised in `oats version --json` `features` (the verb is removed; the official catalog is reached through `packages:` + `sync`, not a command).
310
+
311
+ ## Post-0.25.0 clarifications (team review, 2026-09-23)
312
+
313
+ **Capability commands outside an instance home (`oats <ns> <cmd>` from the
314
+ deployment).** Inside an instance home the dispatcher resolves the namespace from
315
+ the home's materialized modules (`instance.json.modules` → `<home>/.oats/modules`);
316
+ that shipped in 0.25.0. Outside a home — the operator acts a knowledge layer needs
317
+ before any instance exists (`oats okf init`, base migration) — the intended rule
318
+ is: **resolve exactly as a spawn of `--soul <name>` would** (`prepareInstance` →
319
+ the soul's Resolution), fetch the namespace's capability into the deployment's
320
+ module store `<deployment>/.oats/modules/<cap>@<commit>/` (the same per-commit
321
+ store capability-defined agents use), and dispatch to that copy with the soul's
322
+ merged payload as `OATS_SETTINGS`. Never "the newest instance's copy" (an
323
+ instance is not an authority for the deployment) and never an unlocked cache
324
+ read (the lock's approval is the gate, as for spawn). `--soul` is required when
325
+ the namespace's capability is not a workspace default. **Status: 0.25.x
326
+ follow-up** — 0.25.0 still answers `E_CAPABILITY_INACTIVE` there (the pre-v2
327
+ chain); the interim is to run the module binary directly with `OATS_SETTINGS`
328
+ and `OATS_CLI_BIN`, as the tarball smoke does.
329
+
330
+ **`work: workspace` is kept.** A coordination soul's `./work` is the deployment
331
+ boundary — the directory holding `oats-local.yaml` (whatever the operator
332
+ named it, member clones beside it or named in `clones:`) — read-only across member
333
+ clones, no branch recorded. The clone map in `oats-local.yaml` (`clones:`) is
334
+ how such a soul finds a member whose clone is elsewhere. **Status: the
335
+ directory link is the intent; 0.25.0's kernel still derives the boundary from
336
+ the classic `team:` scope (`docs/souls-and-instances.md` open thread) — 0.25.x
337
+ follow-up binds it to the `oats-local.yaml` directory.**
338
+
339
+ **`identity.source` (oats.aweb) is the absolute path of the `.aw` directory to
340
+ retain**, given per spawn (`--provider oats.aweb identity.source=/abs/.aw`) or
341
+ per machine (`oats-local.yaml settings.oats.aweb.identity.source`); the kernel
342
+ resolves no symbolic seat names. Absolute paths never enter the workspace file
343
+ (decision 14).
344
+
345
+ **Member capabilities are a code-execution boundary** (decision 2: membership is
346
+ the trust; hooks and scripts of every member's default branch run on every
347
+ operator's machine at spawn). For a mixed public/private organisation the
348
+ recommendation is: **souls only in public members; executable capabilities come
349
+ from packages (approved per version) or from private members.** The onboarding
350
+ skill (Phase E) states this beside the hosting rule (decision 26).
351
+
352
+
353
+ ### 0.25.1 fix round (team review, 2026-09-23) — appended, not edited in place
354
+
355
+ Each item names the section it refines and the review finding it closes. The
356
+ implementation lands in kernel 0.25.1 (`docs/release-notes/v0.25.1.md`); no
357
+ API integer or feature name changes.
358
+
359
+ **§3 slot `none` (L1).** A soul's `none` for a slot **empties the slot and drops
360
+ any layer-bearing capability the WORKSPACE DEFAULTS contributed for that
361
+ layer** — whether it arrived through `defaults.<slot>`, `defaults.capabilities`
362
+ or `defaults.byTeam[team].capabilities`. A layer-bearing capability **the soul
363
+ itself declares** in its own `capabilities:` alongside `none` for that layer is
364
+ `E_SLOT_CONFLICT { reason: "none" }` (spell `<cap>: off` to remove it). This
365
+ replaces the Phase B reading under which a layered default arriving via
366
+ `defaults.capabilities` was itself a conflict: the workspace's choice is a
367
+ default and `none` is the soul's answer to it; only the soul contradicting
368
+ itself is loud.
369
+
370
+ **§2/§5 per-commit soul cache (M1).** `ensureWorkspaceSoul` fetches a soul's
371
+ source at `(repoKey, commit)` into `<deployment>/agents/<name>/souls/<commit12>/`
372
+ — **immutable once written** (staged, then renamed in; never removed by the
373
+ kernel) — and maintains `<deployment>/agents/<name>/soul` as a **symlink to the
374
+ current commit's directory**, swapped atomically (symlink to a temp name +
375
+ rename over) so classic readers (`findAgent`, `doctor`, the classic spawn path)
376
+ keep seeing "current". A spawned home's `<home>/soul` links **its own commit's
377
+ directory** (the realpath of `souls/<commit12>/`), never the swapped pointer:
378
+ a running instance's soul never changes under it (decision 7), a `--preview`
379
+ may fetch a new commit and swap the pointer without touching any directory an
380
+ instance links, and OKF 2's owner pin (`owners.json` = `realpath(<home>/soul)`)
381
+ stays valid for the instance that registered it. `.oats-soul-source.json`
382
+ remains the stamp of "current". A 0.25.0 layout (`agents/<name>/soul` a real
383
+ directory, no `souls/`) is migrated in place on first use: the directory moves
384
+ to `souls/<commit from the stamp, else unknown>/` and the pointer replaces it.
385
+ A soul symlink whose target lies inside the same `agents/<name>/souls/` is the
386
+ one kernel-owned symlink soul readers accept.
387
+
388
+ **§1 transport (M2).** `parseRepoRef(ref).key` is unchanged — `<host>/<path>`
389
+ is the identity everywhere and every comparison is by key. The **fetch url
390
+ honours the form written**: `git@host:org/repo(.git)` and `ssh://…` fetch over
391
+ SSH as written; `https://…` fetches over HTTPS; the bare scheme
392
+ `git:host/org/repo` fetches over HTTPS by default **unless
393
+ `remoteOptions.transport === "ssh"`** (a per-machine choice; `oats-local.yaml`
394
+ may carry it once the schema admits it — reported by lane 3, not landed here).
395
+ The operator's SSH access is therefore used when the operator wrote an SSH ref,
396
+ and a private repo no longer degrades to `not-found` → standalone through an
397
+ unintended HTTPS probe. When the standalone fallback engages the discovery
398
+ carries `standaloneReason`/`hostFailure { code, reason, url }` so the CLI can
399
+ print *why*.
400
+
401
+ **§3/§4 approval re-verified at spawn (M3).** For a `from: package` module
402
+ `resolveSoul` recomputes `executablesDigest` over the package tree **at the
403
+ locked `entry.commit`** and requires equality with `entry.approved.executables`
404
+ → else `E_PACKAGE_UNAPPROVED { reason: "digest-mismatch", approved, actual }`.
405
+ The one digest definition is `lib/packages.mjs#executablesDigestAt(remote, ref,
406
+ commit, path, capabilities)`, shared by `sync` and `resolve`; a hand-edited lock
407
+ (same id/version, different commit, copied approval) can no longer materialize
408
+ and run unapproved hooks. Cached per `(id, commit)` within a process.
409
+
410
+ **§1 peeled commit OIDs (M4).** `observeRemote(ref, { at })` accepts an
411
+ annotated tag's OID (or name) but **records the peeled commit** (`<oid>^{commit}`)
412
+ as `commit` — in its result, in the lock, in `fetchRemoteTree`'s errors and in
413
+ every `instance.json` record. A tag OID is never stored where a commit is
414
+ expected.
415
+
416
+ **§1 listing hygiene (L3, L4).** `listRemoteTree` filters by depth **before**
417
+ asserting entry-name safety, so one unsafe deep name does not blank a member's
418
+ souls (unsafe names at the kept depth are still `E_REMOTE_TREE_UNSAFE`). A git
419
+ child killed for `maxBuffer` (`ENOBUFS`) is not reported as `timeout`; an
420
+ unclassified listing failure is wrapped as `E_REMOTE_UNREADABLE { reason:
421
+ "unknown" }` so discovery records a problem row instead of aborting.
422
+
423
+ **§2 `validateWorkspace` absolute paths (L2).** The absolute-path refusal
424
+ applies to **ref/path fields only** — `members[]`, `packages` values, `stores`
425
+ values, `external[].source` / `external[].soul`, `defaults.*.from` — never to
426
+ `teams.<label>.description` or to the opaque `messaging` payload.
427
+
428
+ **§3 revision (L6).** `Resolution.revision = hash(declRevision, payloadRevision)`
429
+ where `declRevision` covers the declarations (member/package commits, the
430
+ composed capability set, skills, injects) and `payloadRevision` covers the
431
+ payload layers (soul slot payloads ⊕ `oats-local.yaml settings` ⊕
432
+ `--provider`). Both are exposed on the Resolution; decision binding keeps using
433
+ `revision`, so a settings-only difference still refuses a stale apply, while
434
+ `spawn --preview` can say **`changed since: declarations | payload | both`**
435
+ instead of a bare `changedSince`.
436
+
437
+ **§6 operator-level dispatch (B3, implements the rule stated above).** Outside a
438
+ home, with `oats-local.yaml` present, `oats <ns> <cmd> … --soul <name>` runs
439
+ `prepareInstance(dir, name)`, picks the module whose `manifest.command === <ns>`,
440
+ ensures its tree in `<deployment>/.oats/modules/<cap>@<commit12>/` (member: the
441
+ member repo at `module.from.commit`, `module.dir`; package: the lock entry) and
442
+ dispatches to that copy with `OATS_SETTINGS = resolution.payloads[cap]` and
443
+ `OATS_CLI_BIN`. `--soul` absent → `E_BAD_ARGS` naming it; a namespace no module
444
+ provides → `E_UNKNOWN_COMMAND`. Trust is the resolution's (membership;
445
+ `E_PACKAGE_UNAPPROVED` for an unapproved package).
446
+
447
+ **§5/§6 `work: workspace` under v2 (B2, implements the rule stated above).**
448
+ With `prepared` present, a `work: workspace` soul's `./work` links the
449
+ deployment directory (`prepared.deployment`, the one holding `oats-local.yaml`);
450
+ no branch is recorded; the "needs a declared boundary" remedy names
451
+ `oats-local.yaml`, not `oats-config.yaml`. The classic root is unchanged.
452
+
453
+ **Decision 13 reach (L7).** "Harnesses start normally" is a property of the
454
+ 0.25 **launcher**: every `pi` launch a 0.25 kernel performs — module homes and
455
+ classic 0.24 homes alike — starts pi with cwd = home, the composed `AGENTS.md`
456
+ appended, and pi's own skill/context discovery intact. Consequently `oats
457
+ session recompose` refuses a **module home** (`instance.json.modules` present)
458
+ with `E_UNSUPPORTED_MODE` ("re-spawn"); the `session-recompose` feature name
459
+ stays advertised because the verb still serves classic homes
460
+ (`docs/desktop-cli-api.md`).
461
+
462
+ ### 0.25.2 operator-rebuild round (2026-09-24) — appended, not edited in place
463
+
464
+ Source: an operator's first rebuild of a real two-team deployment on 0.25.0,
465
+ following `docs/rebuild-to-v2.md` literally. **The guide is a contract the
466
+ kernel must honour**: where the guide claimed behaviour the kernel lacked, the
467
+ kernel changes; where the guide described keys no provider consumes, the guide
468
+ changes. Findings R1–R10; kernel side in 0.25.2 (`docs/release-notes/v0.25.2.md`).
469
+ No API integer or feature name changes; the only surface additions are
470
+ additive fields (`spawn --preview` `providers` / `settings`, `oats status
471
+ --json instances[].soul`) and the `sync --approve` flag.
472
+
473
+ **§2/§5 member clone lookup (R1).** For a `work: worktree | checkout` soul the
474
+ kernel finds the member clone in this order, first hit wins: (1) `oats spawn
475
+ --repo <abs path>`; (2) `oats-local.yaml` `clones: { <repo key>: <abs path> }`,
476
+ keys normalised through `parseRepoRef(...).key` so any spelling of the same
477
+ repo addresses one entry; (3) the convention `<deployment>/<member name>` where
478
+ `<member name>` is the last segment of the repo key — **a member named `agents`
479
+ is looked for at `<deployment>/agents-repo`** (`<deployment>/agents/` is the
480
+ instance root); (4) none → `E_CLONE_MISSING { repoKey, tried: [...], remedies }`
481
+ naming the three remedies. A directory found by (2) or (3) whose `origin` remote
482
+ resolves to a different repo key → `E_CLONE_MISMATCH { repoKey, path, origin }`
483
+ — the kernel never spawns into a clone that is not the member. This order was
484
+ stated by the guide and `docs/workspaces.md` before 0.25.2 and not implemented;
485
+ it is now normative.
486
+
487
+ **§6 `oats sync` creates `agents/` (R2).** `sync` (and therefore `onboard`,
488
+ which runs the sync body) creates `<deployment>/agents/` when absent. A
489
+ hand-written `oats-local.yaml` needs no `mkdir`.
490
+
491
+ **§5 one "You run on OATS" block (R3).** When `oats.core` resolves as a module
492
+ the composer suppresses the kernel's legacy `oats:kernel:oats` block; the
493
+ module's inject is the one such block. Without `oats.core` (a soul saying `off`)
494
+ the legacy block is composed as before, so no instance is left without the
495
+ briefing.
496
+
497
+ **§5/§6 soul-source drift (R4).** `driftOf` covers `instance.json.workspace.soul`
498
+ as well as `modules`: `oats status` prints `soul: <name> from <member> @ <c7>`
499
+ with `[member moved since …]` when the member's default branch is past the
500
+ recorded commit (`[member unconfirmed]` / `[soul no longer present]` for the
501
+ missing cases); `--json` adds `instances[].soul = { repoKey, commit, current:
502
+ <commit>|null, status: "current"|"moved"|"missing" }`. A moved soul is
503
+ information (decision 17): the instance keeps its own commit directory (M1).
504
+
505
+ **§6 preview payload visibility (R5).** `oats spawn --preview` (text and
506
+ `--json`) reports `providers` — the `--provider <cap> k=v` map exactly as given,
507
+ nested — and `settings.<cap>` — `resolution.payloads[cap]`, the merged payload
508
+ the provider's binding receives (`workspace.messaging` base ⊕ `byTeam[team]` ⊕
509
+ soul slot payload ⊕ `local.settings[cap]` ⊕ `providers[cap]`). Additive fields;
510
+ both empty objects when nothing applies.
511
+
512
+ **§5/§6 `work: workspace` (R6, closed in 0.25.1 as B2).** Documented in the
513
+ guide's §9: a coordination soul's `./work` is the deployment directory.
514
+
515
+ **Provider payload delivery vs provider consumption (R7 — oats.aweb 1.11.2).**
516
+ Decision 23 (`messaging.byTeam`) is **kernel semantics**: the kernel merges and
517
+ delivers; the provider consumes what its binding declares. oats.aweb 1.11.2's
518
+ spawn hook (a) locates the aweb root among `OATS_TEAM_SCOPE`, the home, the
519
+ home's git root, `OATS_CONTEXT` and its git root, and `OATS_WORKSPACE` (under
520
+ v2: the deployment directory) — none of which is a 0.24 team root; and (b)
521
+ resolves the target team from `OATS_TEAM_ID`/`OATS_TEAM_NAME` (the removed
522
+ `oats-config.yaml` `team:` block; empty under v2), else the **active team at
523
+ the root it found** — it does **not** read `team` from `OATS_SETTINGS`. So for
524
+ 1.11.2 `byTeam` is delivered and recorded but a no-op; per-label minting is
525
+ obtained only by placing a per-team `.aw` inside each team's member clone
526
+ (gitignored) so it is found through the work repo, or one `.aw` at the
527
+ deployment directory for a single team. The guide states this (§8b) and
528
+ `docs/workspaces.md` states the general rule ("kernel-merged; whether a
529
+ provider honours it is the provider's"). **oats.aweb follow-up**: read `team`
530
+ (and honour `byTeam`'s result) from the payload; accept the deployment
531
+ directory as a first-class root. The kernel does not paper over this with a
532
+ `team:` env shim — the env block is removed with `oats-config.yaml`, and a
533
+ provider contract is the provider's to grow.
534
+
535
+ **OKF 2.1.3 reads `okf.json`, not a soul payload (R8 — corrects §2's `stores`
536
+ comment and decision 24's `root` example).** `oats.okf` 2.1.3's spawn hook
537
+ reads the soul's knowledge declaration from `<soul>/okf.json` (`{ version: 1,
538
+ owner, owns: ["<base>/<node>"], reads: [...] }`, `lib/config.mjs#validateDeclaration`)
539
+ and its settings from `OATS_SETTINGS`, admitting **only** `bindings-file`,
540
+ `state-dir`, `harvest-runtime`, `harvest-model` (`oats.json#settings`) — any
541
+ other key is `E_CONFIG unknown OATS_SETTINGS property`. Where a base lives
542
+ inside a store repository is the **bindings file's** `bases.<alias>.repository`
543
+ + `root`, not a soul payload key. Therefore: a soul.yaml `knowledge:` payload for
544
+ OKF carries binding settings only (usually nothing — the workspace default
545
+ fills the slot; `none` opts out); `owns`/`reads`/`store`/`root` examples on
546
+ `soul.yaml` are removed from the guide, `workspaces.md`, `souls-and-instances.md`
547
+ and `knowledge.md`; `okf.json` stays in `souls/<name>/` and travels with the
548
+ soul into the per-commit cache (M1). A soul-payload grammar for OKF is an OKF
549
+ follow-up that lands with an `oats.okf` release declaring it in its binding.
550
+ The kernel's part — opaque forwarding of the merged payload — is unchanged and
551
+ correct. §7b's fresh `state-dir` rule is confirmed by the operator's run.
552
+
553
+ **§6 non-interactive approval (R9).** `oats sync --approve <id>@<version>`
554
+ (repeatable) approves exactly the entry the current resolution contains for
555
+ that id and version: the executables digest is always computed by `sync` over
556
+ the fetched tree (`executablesDigestAt`) and recorded — never typed. An
557
+ `--approve` naming an id/version the resolution does not contain → `E_BAD_ARGS`
558
+ (nothing approved); entries not covered stay unapproved (exit `2`). At the
559
+ interactive prompt **Ctrl+D (EOF) is a decline**: exit `2`, entry unapproved —
560
+ never treated as "yes", never a hang.
561
+
562
+ **§6 onboard next steps (R10).** `oats onboard` lists the workspace **host**
563
+ in `next.clone` like any member that lacks a clone at the convention (the host
564
+ is a member; a soul that lives in it may need a work clone). Under an explicit
565
+ `oats-local.yaml` `standalone:` header the next steps say the view is standalone
566
+ and list only that repo.
@@ -15,7 +15,7 @@ A clean v2 of the kernel's declaration, resolution and launch path: read `oats-w
15
15
 
16
16
  ## Work packages
17
17
 
18
- Each is one PR (or two small ones), reviewed by me, on `main`, behind the v2 seam until W7. Order is dependency order; W1–W3 can proceed in parallel lanes.
18
+ Each is one PR (or two small ones), reviewed by me, on `main`, as canonical code from the first phase (no seam — see the approach above). Order is dependency order; W1–W3 can proceed in parallel lanes. **Status (2026-09-23): W1–W8 and W10 shipped in OATS 0.25.0 (PR99 `59ae22df`); W8's last part — folding the classic no-`oats-local.yaml` spawn path into the one pipeline and deleting the v1 residue — is a follow-up; W9/W9b/W11/W12 are Phases D–F.**
19
19
 
20
20
  | # | Package | Delivers | Owner | Size |
21
21
  |---|---|---|---|---|
@@ -28,9 +28,9 @@ Each is one PR (or two small ones), reviewed by me, on `main`, behind the v2 sea
28
28
  | **W7** | **CLI switch-over** | `oats sync`, `oats spawn` (preview/apply unchanged in shape, now fed by W4/W6), `oats capabilities` / `oats souls` with origin + team, `oats workspace status`; `oats status` shows `modules … @ commit` + `member moved since`; `oats version --json` advertises `workspaceApi: 2` + feature `workspace-v2`; **remove the v1 readers** (`oats.yaml`, per-soul `source:`, `oats-config.yaml` activation) — a v1 file at a v2 path errors naming the schema | lead | M |
29
29
  | **W8** | **Delete v1** | Remove `portable-*`, `source-spec`, `capability-provenance` (v1 parts), `prepared-resources`, migration stores/evidence, the installed tier in `packages.mjs`, classic config activation, their tests and docs; `core.mjs` loses everything that only served v1 | lead | L (mostly deletion) |
30
30
  | **W9** | **Framework repos as the first workspace (decisions 18–21)** | `oats-workspace.yaml` v2 in `oats` (drop the six imports; `packages:` pins oats.framework/okf/aweb/jira/linear/authoring/dev **as packages**; `teams:` global/engineering; `defaults`); `oats-membership.yaml` in ALL seven repos; the six soul editions rewritten (`oats.okf: {from: package}` etc. even though the repos are members); **a new expert soul in every package repo** — `okf-expert`, `aweb-expert`, `jira-expert`, `linear-expert`, `authoring-expert`, `dev-expert` (v2 `souls/<name>/`, team global, knows and evolves that capability); `package-catalog.json` kept as the official marketplace (bare-version `packages:` entries resolve through it) | lead (member-repo PRs to their owners; the expert souls' AGENTS.md drafted by me, reviewed by the package owner) | L |
31
- | **W9b** | **`oats.core` and `oats.setup` rewritten for the new architecture (human, 2026-09-23)** | The two official capabilities' skills and injects are rewritten to teach the *new* model, not patched: **`oats.core`** (`oats-operate`, `oats-souls`, the "you run on OATS" inject) — how an agent works inside an instance under this architecture: its home layout (`.oats/modules/`, `.agents/skills/`, `instance.json` modules/providers), the verbs it will actually use (`status`, `spawn --preview/--provider`, `retire`, `session`, `instance events`, `capabilities`, `souls`, `workspace status`), what a workspace/member/package/team is *from the agent's seat*, drift ("member moved since"), how to find and read other souls. **`oats.setup`** (`oats-config`/`oats-packages` → renamed to what they now are: `oats-workspace`, `oats-packages`, `oats-onboarding`) — the whole architecture and its best practices: workspace ↔ repo handshake and why, `oats-workspace.yaml` / `oats-membership.yaml` / `soul.yaml` v2 / `oats-local.yaml` field by field, teams as labels, member-tier vs package-tier and the non-collapse rule, `packages:` + lock v3 + per-version approval, the official catalog vs `git:` refs, `sync`/`package add`, the `<name>-workspace/` convention and clone-then-spawn, private items, external souls, the standalone case, provider payload homes (soul/machine/spawn), what is deliberately NOT versioned and why. Every skill is validated against the shipped CLI (`oats <verb> --help` snapshot test) so they cannot drift from the commands. | lead (swarm + review) | M |
32
- | **W10** | **Docs** | **`docs/rebuild-to-v2.md` — the rebuild guide (ships with the schemas; states that 0.24.x keeps spawning 0.24.x deployments)**; `docs/workspaces.md` rewritten around v2; `souls-and-instances.md`, `packages.md`, `configuration.md` (mostly deleted), `first-team.md` → the `<name>-workspace/` convention; DTO doc § Workspace v2; release notes | lead | M |
33
- | **W11** | **Onboarding skill** | `oats-setup-expert` / `oats.setup`: teach `<name>-workspace/`, `oats sync`, clone-then-spawn; **the hosting rule for mixed public/private organisations (decision 26): ask up front whether any member is private; if so the workspace file is hosted in a private repo that is NOT a public member (a dedicated private `workspace` repo is the honest shape), public contributors get the standalone case with `oats.core` by default (decision 25); `oats onboard` prints the same rule in its next-steps**; the `oats-operate` skill updated for the new verbs | lead | S |
31
+ | **W9b** | **`oats.core` and `oats.setup` rewritten for the new architecture (human, 2026-09-23)** | The two official capabilities' skills and injects are rewritten to teach the *new* model, not patched: **`oats.core`** (`oats-operate`, `oats-souls`, the "you run on OATS" inject) — how an agent works inside an instance under this architecture: its home layout (`.oats/modules/`, `.agents/skills/`, `instance.json` modules/providers), the verbs it will actually use (`status`, `spawn --preview/--provider`, `retire`, `session`, `instance events`, `capabilities`, `souls`, `workspace status`), what a workspace/member/package/team is *from the agent's seat*, drift ("member moved since"), how to find and read other souls. **`oats.setup`** (`oats-config`/`oats-packages` → renamed to what they now are: `oats-workspace`, `oats-packages`, `oats-onboarding`) — the whole architecture and its best practices: workspace ↔ repo handshake and why, `oats-workspace.yaml` / `oats-membership.yaml` / `soul.yaml` v2 / `oats-local.yaml` field by field, teams as labels, member-tier vs package-tier and the non-collapse rule, `packages:` + lock v3 + per-version approval, the official catalog vs `git:` refs, `sync`/`package add`, the deployment directory (the operator's; no naming convention) and clone-then-spawn, private items, external souls, the standalone case, provider payload homes (soul/machine/spawn), what is deliberately NOT versioned and why. Every skill is validated against the shipped CLI (`oats <verb> --help` snapshot test) so they cannot drift from the commands. | lead (swarm + review) | M |
32
+ | **W10** | **Docs** | **`docs/rebuild-to-v2.md` — the rebuild guide (ships with the schemas; states that 0.24.x keeps spawning 0.24.x deployments)**; `docs/workspaces.md` rewritten around v2; `souls-and-instances.md`, `packages.md`, `configuration.md` (mostly deleted), `first-team.md` → the operator's deployment directory; DTO doc § Workspace v2; release notes | lead | M |
33
+ | **W11** | **Onboarding skill** | `oats-setup-expert` / `oats.setup`: ASK for the deployment directory (an existing folder with the operator's clones is the usual case — no named convention, decision 9), `oats sync`, clone-then-spawn; **the hosting rule for mixed public/private organisations (decision 26): ask up front whether any member is private; if so the workspace file is hosted in a private repo that is NOT a public member (a dedicated private `workspace` repo is the honest shape), public contributors get the standalone case with `oats.core` by default (decision 25); `oats onboard` prints the same rule in its next-steps**; the `oats-operate` skill updated for the new verbs | lead | S |
34
34
  | **W12** | **Desktop follow-through** | New kernel DTOs (from W7) consumed the usual way — engineer reads the merged head, files pins, wires: Capabilities/Souls origin + team columns; "installed" removed as a state; spawn preview shows modules (from/commit/hash) + team; Workspaces surface shows membership status | Desktop engineer, after W7 | M |
35
35
 
36
36
  **Team review (Antares, 2026-09-23) folded in:** rebuild guide (W10), **second review (two aweb teams, stores, soul layout, standalone, public/private hosting → decisions 23–26: `messaging.byTeam`, stores = repo + provider `root`, `souls/` only, `oats.core` standalone default, private host rule taught by onboarding),** `--provider` instance payload (W6/W7), drift display (W7), duplicate-name rule (W6), 0.24.x-keeps-working stated everywhere.
@@ -52,7 +52,7 @@ Each phase = one developer-swarm workflow (parallel agents, disjoint files, agai
52
52
  | Risk | Mitigation |
53
53
  |---|---|
54
54
  | Remote access context is subtle (SSH vs HTTPS vs `gh` token; private repos) | W2 uses git itself (`git ls-remote`, `git archive`/shallow fetch) with the operator's configured credential helpers — no new credential store; typed `cannot read` never guesses |
55
- | `core.mjs` entanglement makes W8 risky | W8 is deletion behind a green W7; the seam guarantees nothing live depends on what is deleted; CI + the Northwind fixture + Desktop suites are the gate |
55
+ | `core.mjs` entanglement makes W8 risky | W8 is deletion behind a green W7; `rg` proof of zero importers per module before each deletion (the v1-residue table from the Phase C review); CI + the Northwind fixture + Desktop suites are the gate |
56
56
  | Package approval UX ("asks once") in non-interactive spawns | `oats sync` is where approval is asked; `spawn` refuses `E_PACKAGE_UNAPPROVED` pointing at `sync` — never prompts mid-spawn |
57
57
  | Latest-state members drift between preview and apply | The resolution records the member commit; apply re-observes and refuses `E_DECISION_STALE` if it moved — same mechanism as today's decision revision |
58
58
  | Desktop relying on "installed" | Removed as a state in W12; until then the Capabilities view keeps working on 0.24.x DTOs (contracts unchanged) |
@@ -450,6 +450,26 @@ the pre-fix marker and is never accepted for dispatch.
450
450
 
451
451
  ## Readiness quartet, signatures, enforced policy (`oats readiness`, `readinessApi: 1`, OATS 0.24.8+)
452
452
 
453
+ > **0.25 status — the producers below are the 0.24 tier.** The readiness DTO
454
+ > (`readinessApi: 1`) still ships unchanged, but its four checks are *produced*
455
+ > by the classic observers: `installed` by `oats list` over the
456
+ > `.agents/capabilities/installed/` tier, `trusted` by the per-artifact approval
457
+ > that `oats trust` wrote, `configured` by `oats-config.yaml` activation, and
458
+ > `enrolled` by the `oats.yaml` backlink. On a **workspace deployment**
459
+ > (`oats-local.yaml` present) none of those sources exists: nothing is installed,
460
+ > approval is per package version in `oats-lock.json` v3 (`oats sync`), activation
461
+ > is derived (workspace defaults ⊕ soul `capabilities:`), and membership is
462
+ > `oats-membership.yaml` observed over the remotes. `oats list` and `oats trust`
463
+ > are removed verbs (`E_UNKNOWN_COMMAND`), so a `remedy` naming them cannot be
464
+ > run. Treat the field names and producer strings as the stable wire shape they
465
+ > are; for the workspace-model facts read `oats spawn <soul> --preview --json`
466
+ > (`modules[]` with from/commit/digest — the "installed" and "configured"
467
+ > truth), `oats sync --json` `approvalNeeded[]` (the "trusted" truth) and
468
+ > `oats workspace status --json` (the "enrolled" truth). Re-basing the quartet on
469
+ > those producers is an open thread of [the workspace model](#workspace-model-workspaceapi-2);
470
+ > when it lands it will be announced as a new feature name, not a silent change of
471
+ > `readinessApi: 1`.
472
+
453
473
  `oats readiness [--soul <name>] [--home <abs>] [--verify-signatures] [--policy] [--dir <d>] --json`
454
474
  is the first-run readiness view (frame 09) and the Capabilities readiness rows
455
475
  (frame 04). Every fact is derived from the **same** data `oats inspect` reports
@@ -728,7 +748,7 @@ Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
728
748
  `oats onboard [<dir>] --workspace <repo ref> [--json]`
729
749
 
730
750
  The **bootstrap** of a deployment (decision 9): realizes a workspace on this
731
- machine in the taught `<name>-workspace/` layout. It writes
751
+ machine in the directory the operator chooses (any existing folder). It writes
732
752
  `<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
733
753
  `<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
734
754
  over the directory just written — discover over the remotes, confirm
@@ -1026,6 +1046,18 @@ refreshes the home in place:
1026
1046
  schedule; the receipt's `note` says so. Refuses a retiring home
1027
1047
  (`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
1028
1048
  (`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
1049
+ - **Module homes (0.25+) are `E_UNSUPPORTED_MODE` too.** A home whose
1050
+ `instance.json` carries `modules{}` (spawned on a workspace deployment,
1051
+ `instance-modules`) answers `E_UNSUPPORTED_MODE` ("recompose from
1052
+ materialized modules is not supported yet; re-spawn"): its `AGENTS.md` was
1053
+ composed from the soul at a recorded member commit plus the materialized
1054
+ modules' injects, and the instance never changes under itself (decision 7).
1055
+ The refresh path for such a home is a new spawn (the soul is re-fetched at
1056
+ the member's current commit). `session-recompose` **stays advertised** in
1057
+ `features[]` because the verb still works for classic homes; gate the UI
1058
+ action on the feature AND on the absence of `instance.json.modules`
1059
+ (`oats status --json` `instances[].modules` is non-empty for a module home),
1060
+ and render the typed refusal otherwise.
1029
1061
  - Gate on `features.includes("session-recompose")`. It is an **operator
1030
1062
  action** (the human or the instance's parent), never something a Desktop
1031
1063
  poll or an agent runs on itself.
@@ -12,10 +12,17 @@ the OATS Desktop app (`packages/desktop/` in the framework repo):
12
12
 
13
13
  ## Migrating a deployment that used `oats.web`
14
14
 
15
+ Under the **0.25 workspace model** there is nothing to uninstall: remove
16
+ `oats.web` from `oats-workspace.yaml` `packages:` / `defaults.capabilities`
17
+ and from any `soul.yaml` `capabilities:`, run `oats sync` (the lock v3 entry
18
+ disappears with the declaration), and use the Desktop app (step 3 below).
19
+
20
+ For a **0.24 classic** deployment:
21
+
15
22
  1. Remove the `oats.web` entry from `capabilities.additive` in every
16
23
  `oats-config.yaml` in your config chain.
17
- 2. Remove the `oats.web` entry from `oats-lock.json` at the same scope(s), and
18
- delete any stale installed copy under `.agents/capabilities/installed/`.
24
+ 2. Remove the `oats.web` entry from `oats-lock.json` (v2) at the same scope(s),
25
+ and delete any stale installed copy under `.agents/capabilities/installed/`.
19
26
  3. Use the OATS Desktop app instead: `cd packages/desktop && npm install &&
20
27
  npm run rebuild && npm start` (see `packages/desktop/README.md`).
21
28
 
package/docs/desktop.md CHANGED
@@ -84,10 +84,15 @@ The probe/mutation contract is specified in
84
84
 
85
85
  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
- and choose **Add workspace → Browse**, then point it at an OATS workspace —
88
- a directory containing `agents/`, or `local-agents/` for machine-local
89
- souls, or a team scope whose `oats-config.yaml` declares `team:`. Team scopes
90
- show every member repo's agents under one roster with a workspace switcher.
87
+ and choose **Add workspace → Browse**, then point it at an OATS deployment —
88
+ a directory containing `agents/` (under the 0.25 workspace model that is the
89
+ deployment directory (the operator's choice) holding `oats-local.yaml` and `agents/`;
90
+ under 0.24, an `agents/` root, a `local-agents/` root for machine-local souls,
91
+ or a team scope whose `oats-config.yaml` declares `team:`). *The Desktop's own
92
+ multi-repo roster ("team scopes show every member repo's agents under one
93
+ roster") still keys on the 0.24 `oats-config.yaml` `team:` declaration; reading
94
+ the member set from `oats-local.yaml` / `oats workspace status` is the Phase F
95
+ follow-up named in the [0.25.0 notes](release-notes/v0.25.0.md#desktop).*
91
96
  Added workspaces are remembered and offered as suggestions next time.
92
97
 
93
98
  Local souls (uncommitted, machine-local agents under `local-agents/`) are
@@ -231,10 +231,22 @@ and needs equivalent registration glue when switched to session delivery.
231
231
 
232
232
  ## Shared permission setting
233
233
 
234
- Set `yolo: true` in an `oats-config.yaml` to apply it to that scope. The closest
235
- scope wins; an optional `yolo` in soul.yaml overrides it; `oats spawn --yolo` or
236
- `--no-yolo` overrides both. `oats create` accepts those flags too. Desktop offers
237
- the same per-launch choice. With no setting, native policy is retained.
234
+ The opt-in is per launch or per soul: `oats spawn --yolo` / `--no-yolo`
235
+ (`oats create` accepts the same flags), an optional `yolo` in `soul.yaml`
236
+ (0.24 schema; the v2 `soul.yaml` schema does not carry it — use the spawn flag
237
+ or a launch configuration), and the Desktop's per-launch choice. With no
238
+ setting, native policy is retained.
239
+
240
+ *0.24 classic deployments* may also set `yolo: true` in an `oats-config.yaml`
241
+ to apply it to that scope; the closest scope wins, soul overrides scope, the
242
+ spawn flag overrides both. *Under the workspace model* `oats-config.yaml` is
243
+ not configuration ([configuration.md](configuration.md)); the kernel's
244
+ `composeInstance` still consults the classic chain for the machine-level knobs
245
+ `yolo` and `launch-configs` when such a file happens to sit above the
246
+ deployment, but nothing writes one and the rebuild guide tells you to delete
247
+ it — treat a scope-level `yolo` as a 0.24 feature and prefer the explicit
248
+ spawn flag.
249
+
238
250
  Autonomous or unattended execution is not permission to synthesize `yolo: true`.
239
251
  Explicit user CLI/UI input or a user-selected configuration is the opt-in; explicit
240
252
  `false` remains false.